cn.isif.util_plus.http.client.multipart.HttpMultipart.java Source code

Java tutorial

Introduction

Here is the source code for cn.isif.util_plus.http.client.multipart.HttpMultipart.java

Source

/*
 * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.isif.util_plus.http.client.multipart;

import org.apache.http.util.ByteArrayBuffer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import cn.isif.util_plus.http.client.multipart.content.ContentBody;

/**
 * HttpMultipart represents a collection of MIME multipart encoded content bodies. This class is
 * capable of operating either in the strict (RFC 822, RFC 2045, RFC 2046 compliant) or
 * the browser compatible modes.
 *
 * @since 4.0
 */
class HttpMultipart {

    private static ByteArrayBuffer encode(final Charset charset, final String string) {
        ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
        ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
        bab.append(encoded.array(), encoded.position(), encoded.remaining());
        return bab;
    }

    private static void writeBytes(final ByteArrayBuffer b, final OutputStream out) throws IOException {
        out.write(b.buffer(), 0, b.length());
        out.flush();
    }

    private static void writeBytes(final String s, final Charset charset, final OutputStream out)
            throws IOException {
        ByteArrayBuffer b = encode(charset, s);
        writeBytes(b, out);
    }

    private static void writeBytes(final String s, final OutputStream out) throws IOException {
        ByteArrayBuffer b = encode(MIME.DEFAULT_CHARSET, s);
        writeBytes(b, out);
    }

    private static void writeField(final MinimalField field, final OutputStream out) throws IOException {
        writeBytes(field.getName(), out);
        writeBytes(FIELD_SEP, out);
        writeBytes(field.getBody(), out);
        writeBytes(CR_LF, out);
    }

    private static void writeField(final MinimalField field, final Charset charset, final OutputStream out)
            throws IOException {
        writeBytes(field.getName(), charset, out);
        writeBytes(FIELD_SEP, out);
        writeBytes(field.getBody(), charset, out);
        writeBytes(CR_LF, out);
    }

    private static final ByteArrayBuffer FIELD_SEP = encode(MIME.DEFAULT_CHARSET, ": ");
    private static final ByteArrayBuffer CR_LF = encode(MIME.DEFAULT_CHARSET, "\r\n");
    private static final ByteArrayBuffer TWO_DASHES = encode(MIME.DEFAULT_CHARSET, "--");

    private String subType;
    private final Charset charset;
    private final String boundary;
    private final List<FormBodyPart> parts;

    private final HttpMultipartMode mode;

    /**
     * Creates an instance with the specified settings.
     *
     * @param subType  mime subtype - must not be {@code null}
     * @param charset  the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. UTF-8 - is used.
     * @param boundary to use  - must not be {@code null}
     * @param mode     the mode to use
     * @throws IllegalArgumentException if charset is null or boundary is null
     */
    public HttpMultipart(final String subType, final Charset charset, final String boundary,
            HttpMultipartMode mode) {
        super();
        if (subType == null) {
            throw new IllegalArgumentException("Multipart subtype may not be null");
        }
        if (boundary == null) {
            throw new IllegalArgumentException("Multipart boundary may not be null");
        }
        this.subType = subType;
        this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
        this.boundary = boundary;
        this.parts = new ArrayList<FormBodyPart>();
        this.mode = mode;
    }

    /**
     * Creates an instance with the specified settings.
     * Mode is set to {@link HttpMultipartMode#STRICT}
     *
     * @param subType  mime subtype - must not be {@code null}
     * @param charset  the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. UTF-8 - is used.
     * @param boundary to use  - must not be {@code null}
     * @throws IllegalArgumentException if charset is null or boundary is null
     */
    public HttpMultipart(final String subType, final Charset charset, final String boundary) {
        this(subType, charset, boundary, HttpMultipartMode.STRICT);
    }

    public HttpMultipart(final String subType, final String boundary) {
        this(subType, null, boundary);
    }

    public void setSubType(String subType) {
        this.subType = subType;
    }

    public String getSubType() {
        return this.subType;
    }

    public Charset getCharset() {
        return this.charset;
    }

    public HttpMultipartMode getMode() {
        return this.mode;
    }

    public List<FormBodyPart> getBodyParts() {
        return this.parts;
    }

    public void addBodyPart(final FormBodyPart part) {
        if (part == null) {
            return;
        }
        this.parts.add(part);
    }

    public String getBoundary() {
        return this.boundary;
    }

    private void doWriteTo(final HttpMultipartMode mode, final OutputStream out, boolean writeContent)
            throws IOException {
        doWriteTo(mode, out, MultipartEntity.CallBackInfo.DEFAULT, writeContent);
    }

    private void doWriteTo(final HttpMultipartMode mode, final OutputStream out,
            MultipartEntity.CallBackInfo callBackInfo, boolean writeContent) throws IOException {

        callBackInfo.pos = 0;

        ByteArrayBuffer boundary = encode(this.charset, getBoundary());
        for (FormBodyPart part : this.parts) {
            if (!callBackInfo.doCallBack(true)) {
                throw new InterruptedIOException("cancel");
            }
            writeBytes(TWO_DASHES, out);
            callBackInfo.pos += TWO_DASHES.length();
            writeBytes(boundary, out);
            callBackInfo.pos += boundary.length();
            writeBytes(CR_LF, out);
            callBackInfo.pos += CR_LF.length();

            MinimalFieldHeader header = part.getHeader();

            switch (mode) {
            case STRICT:
                for (MinimalField field : header) {
                    writeField(field, out);
                    callBackInfo.pos += encode(MIME.DEFAULT_CHARSET, field.getName() + field.getBody()).length()
                            + FIELD_SEP.length() + CR_LF.length();
                }
                break;
            case BROWSER_COMPATIBLE:
                // Only write Content-Disposition
                // Use content charset
                MinimalField cd = header.getField(MIME.CONTENT_DISPOSITION);
                writeField(cd, this.charset, out);
                callBackInfo.pos += encode(this.charset, cd.getName() + cd.getBody()).length() + FIELD_SEP.length()
                        + CR_LF.length();
                String filename = part.getBody().getFilename();
                if (filename != null) {
                    MinimalField ct = header.getField(MIME.CONTENT_TYPE);
                    writeField(ct, this.charset, out);
                    callBackInfo.pos += encode(this.charset, ct.getName() + ct.getBody()).length()
                            + FIELD_SEP.length() + CR_LF.length();
                }
                break;
            default:
                break;
            }
            writeBytes(CR_LF, out);
            callBackInfo.pos += CR_LF.length();

            if (writeContent) {
                ContentBody body = part.getBody();
                body.setCallBackInfo(callBackInfo);
                body.writeTo(out);
            }
            writeBytes(CR_LF, out);
            callBackInfo.pos += CR_LF.length();
        }
        writeBytes(TWO_DASHES, out);
        callBackInfo.pos += TWO_DASHES.length();
        writeBytes(boundary, out);
        callBackInfo.pos += boundary.length();
        writeBytes(TWO_DASHES, out);
        callBackInfo.pos += TWO_DASHES.length();
        writeBytes(CR_LF, out);
        callBackInfo.pos += CR_LF.length();
        callBackInfo.doCallBack(true);
    }

    /**
     * Writes out the content in the multipart/form encoding. This method
     * produces slightly different formatting depending on its compatibility
     * mode.
     *
     * @see #getMode()
     */
    public void writeTo(final OutputStream out, MultipartEntity.CallBackInfo callBackInfo) throws IOException {
        doWriteTo(this.mode, out, callBackInfo, true);
    }

    /**
     * Determines the total length of the multipart content (content length of
     * individual parts plus that of extra elements required to delimit the parts
     * from one another). If any of the @{link BodyPart}s contained in this object
     * is of a streaming entity of unknown length the total length is also unknown.
     * <p/>
     * This method buffers only a small amount of data in order to determine the
     * total length of the entire entity. The content of individual parts is not
     * buffered.
     *
     * @return total length of the multipart entity if known, <code>-1</code>
     *         otherwise.
     */
    public long getTotalLength() {
        long contentLen = 0;
        for (FormBodyPart part : this.parts) {
            ContentBody body = part.getBody();
            long len = body.getContentLength();
            if (len >= 0) {
                contentLen += len;
            } else {
                return -1;
            }
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            doWriteTo(this.mode, out, false);
            byte[] extra = out.toByteArray();
            return contentLen + extra.length;
        } catch (Throwable ex) {
            // Should never happen
            return -1;
        }
    }

}