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.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.nio.charset.Charset;
34  import java.util.Random;
35  
36  import org.apache.http.Header;
37  import org.apache.http.HttpEntity;
38  import org.apache.http.entity.mime.content.ContentBody;
39  import org.apache.http.message.BasicHeader;
40  import org.apache.http.protocol.HTTP;
41  
42  /**
43   * Multipart/form coded HTTP entity consisting of multiple body parts.
44   *
45   * @since 4.0
46   */
47  public class MultipartEntity implements HttpEntity {
48  
49      /**
50       * The pool of ASCII chars to be used for generating a multipart boundary.
51       */
52      private final static char[] MULTIPART_CHARS =
53          "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
54              .toCharArray();
55  
56      private final HttpMultipart multipart;
57      private final Header contentType;
58  
59      // @GuardedBy("dirty") // we always read dirty before accessing length
60      private long length;
61      private volatile boolean dirty; // used to decide whether to recalculate length
62  
63      /**
64       * Creates an instance using the specified parameters
65       * @param mode the mode to use, may be {@code null}, in which case {@link HttpMultipartMode#STRICT} is used
66       * @param boundary the boundary string, may be {@code null}, in which case {@link #generateBoundary()} is invoked to create the string
67       * @param charset the character set to use, may be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
68       */
69      public MultipartEntity(
70              HttpMultipartMode mode,
71              String boundary,
72              Charset charset) {
73          super();
74          if (boundary == null) {
75              boundary = generateBoundary();
76          }
77          if (mode == null) {
78              mode = HttpMultipartMode.STRICT;
79          }
80          this.multipart = new HttpMultipart("form-data", charset, boundary, mode);
81          this.contentType = new BasicHeader(
82                  HTTP.CONTENT_TYPE,
83                  generateContentType(boundary, charset));
84          this.dirty = true;
85      }
86  
87      /**
88       * Creates an instance using the specified {@link HttpMultipartMode} mode.
89       * Boundary and charset are set to {@code null}.
90       * @param mode the desired mode
91       */
92      public MultipartEntity(final HttpMultipartMode mode) {
93          this(mode, null, null);
94      }
95  
96      /**
97       * Creates an instance using mode {@link HttpMultipartMode#STRICT}
98       */
99      public MultipartEntity() {
100         this(HttpMultipartMode.STRICT, null, null);
101     }
102 
103     protected String generateContentType(
104             final String boundary,
105             final Charset charset) {
106         StringBuilder buffer = new StringBuilder();
107         buffer.append("multipart/form-data; boundary=");
108         buffer.append(boundary);
109         if (charset != null) {
110             buffer.append("; charset=");
111             buffer.append(charset.name());
112         }
113         return buffer.toString();
114     }
115 
116     protected String generateBoundary() {
117         StringBuilder buffer = new StringBuilder();
118         Random rand = new Random();
119         int count = rand.nextInt(11) + 30; // a random size from 30 to 40
120         for (int i = 0; i < count; i++) {
121             buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
122         }
123         return buffer.toString();
124     }
125 
126     public void addPart(final FormBodyPart bodyPart) {
127         this.multipart.addBodyPart(bodyPart);
128         this.dirty = true;
129     }
130 
131     public void addPart(final String name, final ContentBody contentBody) {
132         addPart(new FormBodyPart(name, contentBody));
133     }
134 
135     public boolean isRepeatable() {
136         for (FormBodyPart part: this.multipart.getBodyParts()) {
137             ContentBody body = part.getBody();
138             if (body.getContentLength() < 0) {
139                 return false;
140             }
141         }
142         return true;
143     }
144 
145     public boolean isChunked() {
146         return !isRepeatable();
147     }
148 
149     public boolean isStreaming() {
150         return !isRepeatable();
151     }
152 
153     public long getContentLength() {
154         if (this.dirty) {
155             this.length = this.multipart.getTotalLength();
156             this.dirty = false;
157         }
158         return this.length;
159     }
160 
161     public Header getContentType() {
162         return this.contentType;
163     }
164 
165     public Header getContentEncoding() {
166         return null;
167     }
168 
169     public void consumeContent()
170         throws IOException, UnsupportedOperationException{
171         if (isStreaming()) {
172             throw new UnsupportedOperationException(
173                     "Streaming entity does not implement #consumeContent()");
174         }
175     }
176 
177     public InputStream getContent() throws IOException, UnsupportedOperationException {
178         throw new UnsupportedOperationException(
179                     "Multipart form entity does not implement #getContent()");
180     }
181 
182     public void writeTo(final OutputStream outstream) throws IOException {
183         this.multipart.writeTo(outstream);
184     }
185 
186 }