View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.entity.mime;
29  
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.io.OutputStream;
33  import java.nio.ByteBuffer;
34  import java.nio.CharBuffer;
35  import java.nio.charset.Charset;
36  import java.util.List;
37  
38  import org.apache.http.entity.mime.content.ContentBody;
39  import org.apache.http.util.Args;
40  import org.apache.http.util.ByteArrayBuffer;
41  
42  /**
43   * HttpMultipart represents a collection of MIME multipart encoded content bodies. This class is
44   * capable of operating either in the strict (RFC 822, RFC 2045, RFC 2046 compliant) or
45   * the browser compatible modes.
46   *
47   * @since 4.3
48   */
49  abstract class AbstractMultipartForm {
50  
51      private static ByteArrayBuffer encode(
52              final Charset charset, final String string) {
53          final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
54          final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
55          bab.append(encoded.array(), encoded.position(), encoded.remaining());
56          return bab;
57      }
58  
59      private static void writeBytes(
60              final ByteArrayBuffer b, final OutputStream out) throws IOException {
61          out.write(b.buffer(), 0, b.length());
62      }
63  
64      private static void writeBytes(
65              final String s, final Charset charset, final OutputStream out) throws IOException {
66          final ByteArrayBuffer b = encode(charset, s);
67          writeBytes(b, out);
68      }
69  
70      private static void writeBytes(
71              final String s, final OutputStream out) throws IOException {
72          final ByteArrayBuffer b = encode(MIME.DEFAULT_CHARSET, s);
73          writeBytes(b, out);
74      }
75  
76      protected static void writeField(
77              final MinimalField field, final OutputStream out) throws IOException {
78          writeBytes(field.getName(), out);
79          writeBytes(FIELD_SEP, out);
80          writeBytes(field.getBody(), out);
81          writeBytes(CR_LF, out);
82      }
83  
84      protected static void writeField(
85              final MinimalField field, final Charset charset, final OutputStream out) throws IOException {
86          writeBytes(field.getName(), charset, out);
87          writeBytes(FIELD_SEP, out);
88          writeBytes(field.getBody(), charset, out);
89          writeBytes(CR_LF, out);
90      }
91  
92      private static final ByteArrayBuffer FIELD_SEP = encode(MIME.DEFAULT_CHARSET, ": ");
93      private static final ByteArrayBuffer CR_LF = encode(MIME.DEFAULT_CHARSET, "\r\n");
94      private static final ByteArrayBuffer TWO_DASHES = encode(MIME.DEFAULT_CHARSET, "--");
95  
96      private final String subType;
97      protected final Charset charset;
98      private final String boundary;
99  
100     /**
101      * Creates an instance with the specified settings.
102      *
103      * @param subType MIME subtype - must not be {@code null}
104      * @param charset the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
105      * @param boundary to use  - must not be {@code null}
106      * @throws IllegalArgumentException if charset is null or boundary is null
107      */
108     public AbstractMultipartForm(final String subType, final Charset charset, final String boundary) {
109         super();
110         Args.notNull(subType, "Multipart subtype");
111         Args.notNull(boundary, "Multipart boundary");
112         this.subType = subType;
113         this.charset = charset != null ? charset : MIME.DEFAULT_CHARSET;
114         this.boundary = boundary;
115     }
116 
117     public AbstractMultipartForm(final String subType, final String boundary) {
118         this(subType, null, boundary);
119     }
120 
121     public String getSubType() {
122         return this.subType;
123     }
124 
125     public Charset getCharset() {
126         return this.charset;
127     }
128 
129     public abstract List<FormBodyPart> getBodyParts();
130 
131     public String getBoundary() {
132         return this.boundary;
133     }
134 
135     void doWriteTo(
136         final OutputStream out,
137         final boolean writeContent) throws IOException {
138 
139         final ByteArrayBuffer boundary = encode(this.charset, getBoundary());
140         for (final FormBodyPart part: getBodyParts()) {
141             writeBytes(TWO_DASHES, out);
142             writeBytes(boundary, out);
143             writeBytes(CR_LF, out);
144 
145             formatMultipartHeader(part, out);
146 
147             writeBytes(CR_LF, out);
148 
149             if (writeContent) {
150                 part.getBody().writeTo(out);
151             }
152             writeBytes(CR_LF, out);
153         }
154         writeBytes(TWO_DASHES, out);
155         writeBytes(boundary, out);
156         writeBytes(TWO_DASHES, out);
157         writeBytes(CR_LF, out);
158     }
159 
160     /**
161       * Write the multipart header fields; depends on the style.
162       */
163     protected abstract void formatMultipartHeader(
164         final FormBodyPart part,
165         final OutputStream out) throws IOException;
166 
167     /**
168      * Writes out the content in the multipart/form encoding. This method
169      * produces slightly different formatting depending on its compatibility
170      * mode.
171      */
172     public void writeTo(final OutputStream out) throws IOException {
173         doWriteTo(out, true);
174     }
175 
176     /**
177      * Determines the total length of the multipart content (content length of
178      * individual parts plus that of extra elements required to delimit the parts
179      * from one another). If any of the @{link BodyPart}s contained in this object
180      * is of a streaming entity of unknown length the total length is also unknown.
181      * <p/>
182      * This method buffers only a small amount of data in order to determine the
183      * total length of the entire entity. The content of individual parts is not
184      * buffered.
185      *
186      * @return total length of the multipart entity if known, <code>-1</code>
187      *   otherwise.
188      */
189     public long getTotalLength() {
190         long contentLen = 0;
191         for (final FormBodyPart part: getBodyParts()) {
192             final ContentBody body = part.getBody();
193             final long len = body.getContentLength();
194             if (len >= 0) {
195                 contentLen += len;
196             } else {
197                 return -1;
198             }
199         }
200         final ByteArrayOutputStream out = new ByteArrayOutputStream();
201         try {
202             doWriteTo(out, false);
203             final byte[] extra = out.toByteArray();
204             return contentLen + extra.length;
205         } catch (final IOException ex) {
206             // Should never happen
207             return -1;
208         }
209     }
210 
211 }