| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| * |
| */ |
| |
| package org.apache.manifoldcf.agents.output.solr; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| 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 org.apache.http.entity.mime.content.ContentBody; |
| import org.apache.http.entity.mime.MinimalField; |
| import org.apache.http.entity.mime.FormBodyPart; |
| import org.apache.http.entity.mime.HttpMultipartMode; |
| import org.apache.http.entity.mime.MIME; |
| import org.apache.http.entity.mime.Header; |
| import org.apache.http.util.ByteArrayBuffer; |
| |
| /** |
| * 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 |
| */ |
| public class ModifiedHttpMultipart { |
| |
| 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()); |
| } |
| |
| 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 final 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. US-ASCII - 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 ModifiedHttpMultipart(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. US-ASCII - is used. |
| * @param boundary to use - must not be {@code null} |
| * @throws IllegalArgumentException if charset is null or boundary is null |
| */ |
| public ModifiedHttpMultipart(final String subType, final Charset charset, final String boundary) { |
| this(subType, charset, boundary, HttpMultipartMode.STRICT); |
| } |
| |
| public ModifiedHttpMultipart(final String subType, final String boundary) { |
| this(subType, null, boundary); |
| } |
| |
| 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 { |
| |
| ByteArrayBuffer boundary = encode(this.charset, getBoundary()); |
| for (FormBodyPart part: this.parts) { |
| writeBytes(TWO_DASHES, out); |
| writeBytes(boundary, out); |
| writeBytes(CR_LF, out); |
| |
| Header header = part.getHeader(); |
| |
| switch (mode) { |
| case STRICT: |
| for (MinimalField field: header) { |
| writeField(field, this.charset, out); |
| } |
| break; |
| case BROWSER_COMPATIBLE: |
| // Only write Content-Disposition |
| // Use content charset |
| MinimalField cd = part.getHeader().getField(MIME.CONTENT_DISPOSITION); |
| writeField(cd, this.charset, out); |
| String filename = part.getBody().getFilename(); |
| if (filename != null) { |
| MinimalField ct = part.getHeader().getField(MIME.CONTENT_TYPE); |
| writeField(ct, this.charset, out); |
| } |
| break; |
| } |
| writeBytes(CR_LF, out); |
| |
| if (writeContent) { |
| part.getBody().writeTo(out); |
| } |
| writeBytes(CR_LF, out); |
| } |
| writeBytes(TWO_DASHES, out); |
| writeBytes(boundary, out); |
| writeBytes(TWO_DASHES, out); |
| writeBytes(CR_LF, out); |
| } |
| |
| /** |
| * 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) throws IOException { |
| doWriteTo(this.mode, out, 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 (IOException ex) { |
| // Should never happen |
| return -1; |
| } |
| } |
| |
| } |