1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient.methods;
31
32 import java.io.File;
33 import java.io.FileNotFoundException;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.util.ArrayList;
37 import java.util.List;
38
39 import org.apache.commons.httpclient.HttpConnection;
40 import org.apache.commons.httpclient.HttpException;
41 import org.apache.commons.httpclient.HttpState;
42 import org.apache.commons.httpclient.methods.multipart.FilePart;
43 import org.apache.commons.httpclient.methods.multipart.Part;
44 import org.apache.commons.httpclient.methods.multipart.StringPart;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48 /***
49 * Implements the HTTP multipart POST method.
50 * <p>
51 * The HTTP multipart POST method is defined in section 3.3 of
52 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
53 * <blockquote>
54 * The media-type multipart/form-data follows the rules of all multipart
55 * MIME data streams as outlined in RFC 1521. The multipart/form-data contains
56 * a series of parts. Each part is expected to contain a content-disposition
57 * header where the value is "form-data" and a name attribute specifies
58 * the field name within the form, e.g., 'content-disposition: form-data;
59 * name="xxxxx"', where xxxxx is the field name corresponding to that field.
60 * Field names originally in non-ASCII character sets may be encoded using
61 * the method outlined in RFC 1522.
62 * </blockquote>
63 * </p>
64 * <p>
65 *
66 * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
67 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
68 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
69 * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
70 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
71 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
72 *
73 * @since 2.0
74 */
75 public class MultipartPostMethod extends ExpectContinueMethod {
76
77 /*** The Content-Type for multipart/form-data. */
78 public static final String MULTIPART_FORM_CONTENT_TYPE =
79 "multipart/form-data";
80
81 /*** Log object for this class. */
82 private static final Log LOG = LogFactory.getLog(MultipartPostMethod.class);
83
84 /*** The parameters for this method */
85 private final List parameters = new ArrayList();
86
87 /***
88 * No-arg constructor.
89 */
90 public MultipartPostMethod() {
91 super();
92 }
93
94 /***
95 * Constructor specifying a URI.
96 *
97 * @param uri either an absolute or relative URI
98 */
99 public MultipartPostMethod(String uri) {
100 super(uri);
101 }
102
103 /***
104 * Returns <tt>true</tt>
105 *
106 * @return <tt>true</tt>
107 *
108 * @since 2.0beta1
109 */
110 protected boolean hasRequestContent() {
111 return true;
112 }
113
114 /***
115 * Returns <tt>"POST"</tt>.
116 * @return <tt>"POST"</tt>
117 */
118 public String getName() {
119 return "POST";
120 }
121
122 /***
123 * Adds a text field part
124 *
125 * @param parameterName The name of the parameter.
126 * @param parameterValue The value of the parameter.
127 */
128 public void addParameter(String parameterName, String parameterValue) {
129 LOG.trace("enter addParameter(String parameterName, String parameterValue)");
130 Part param = new StringPart(parameterName, parameterValue);
131 parameters.add(param);
132 }
133
134 /***
135 * Adds a binary file part
136 *
137 * @param parameterName The name of the parameter
138 * @param parameterFile The name of the file.
139 * @throws FileNotFoundException If the file cannot be found.
140 */
141 public void addParameter(String parameterName, File parameterFile)
142 throws FileNotFoundException {
143 LOG.trace("enter MultipartPostMethod.addParameter(String parameterName, "
144 + "File parameterFile)");
145 Part param = new FilePart(parameterName, parameterFile);
146 parameters.add(param);
147 }
148
149 /***
150 * Adds a binary file part with the given file name
151 *
152 * @param parameterName The name of the parameter
153 * @param fileName The file name
154 * @param parameterFile The file
155 * @throws FileNotFoundException If the file cannot be found.
156 */
157 public void addParameter(String parameterName, String fileName, File parameterFile)
158 throws FileNotFoundException {
159 LOG.trace("enter MultipartPostMethod.addParameter(String parameterName, "
160 + "String fileName, File parameterFile)");
161 Part param = new FilePart(parameterName, fileName, parameterFile);
162 parameters.add(param);
163 }
164
165 /***
166 * Adds a part.
167 *
168 * @param part The part to add.
169 */
170 public void addPart (final Part part) {
171 LOG.trace("enter addPart(Part part)");
172 parameters.add(part);
173 }
174
175 /***
176 * Returns all parts.
177 *
178 * @return an array of containing all parts
179 */
180 public Part[] getParts() {
181 return (Part[]) parameters.toArray(new Part[parameters.size()]);
182 }
183
184 /***
185 * Adds a <tt>Content-Length</tt> request header, as long as no
186 * <tt>Content-Length</tt> request header already exists.
187 *
188 * @param state current state of http requests
189 * @param conn the connection to use for I/O
190 *
191 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
192 * can be recovered from.
193 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
194 * cannot be recovered from.
195 *
196 * @since 3.0
197 */
198 protected void addContentLengthRequestHeader(HttpState state,
199 HttpConnection conn)
200 throws IOException, HttpException {
201 LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
202 + "HttpState, HttpConnection)");
203
204 if (getRequestHeader("Content-Length") == null) {
205 long len = getRequestContentLength();
206 addRequestHeader("Content-Length", String.valueOf(len));
207 }
208 removeRequestHeader("Transfer-Encoding");
209 }
210
211 /***
212 * Adds a <tt>Content-Type</tt> request header.
213 *
214 * @param state current state of http requests
215 * @param conn the connection to use for I/O
216 *
217 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
218 * can be recovered from.
219 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
220 * cannot be recovered from.
221 *
222 * @since 3.0
223 */
224 protected void addContentTypeRequestHeader(HttpState state,
225 HttpConnection conn)
226 throws IOException, HttpException {
227 LOG.trace("enter EntityEnclosingMethod.addContentTypeRequestHeader("
228 + "HttpState, HttpConnection)");
229
230 if (!parameters.isEmpty()) {
231 StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
232 if (Part.getBoundary() != null) {
233 buffer.append("; boundary=");
234 buffer.append(Part.getBoundary());
235 }
236 setRequestHeader("Content-Type", buffer.toString());
237 }
238 }
239
240 /***
241 * Populates the request headers map to with additional
242 * {@link org.apache.commons.httpclient.Header headers} to be submitted to
243 * the given {@link HttpConnection}.
244 *
245 * <p>
246 * This implementation adds tt>Content-Length</tt> and <tt>Content-Type</tt>
247 * headers, when appropriate.
248 * </p>
249 *
250 * <p>
251 * Subclasses may want to override this method to to add additional
252 * headers, and may choose to invoke this implementation (via
253 * <tt>super</tt>) to add the "standard" headers.
254 * </p>
255 *
256 * @param state the {@link HttpState state} information associated with this method
257 * @param conn the {@link HttpConnection connection} used to execute
258 * this HTTP method
259 *
260 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
261 * can be recovered from.
262 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
263 * cannot be recovered from.
264 *
265 * @see #writeRequestHeaders
266 */
267 protected void addRequestHeaders(HttpState state, HttpConnection conn)
268 throws IOException, HttpException {
269 LOG.trace("enter MultipartPostMethod.addRequestHeaders(HttpState state, "
270 + "HttpConnection conn)");
271 super.addRequestHeaders(state, conn);
272 addContentLengthRequestHeader(state, conn);
273 addContentTypeRequestHeader(state, conn);
274 }
275
276 /***
277 * Writes the request body to the given {@link HttpConnection connection}.
278 *
279 * @param state the {@link HttpState state} information associated with this method
280 * @param conn the {@link HttpConnection connection} used to execute
281 * this HTTP method
282 *
283 * @return <tt>true</tt>
284 *
285 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
286 * can be recovered from.
287 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
288 * cannot be recovered from.
289 */
290 protected boolean writeRequestBody(HttpState state, HttpConnection conn)
291 throws IOException, HttpException {
292 LOG.trace("enter MultipartPostMethod.writeRequestBody(HttpState state, "
293 + "HttpConnection conn)");
294 OutputStream out = conn.getRequestOutputStream();
295 Part.sendParts(out, getParts());
296 return true;
297 }
298
299 /***
300 * <p>Return the length of the request body.</p>
301 *
302 * <p>Once this method has been invoked, the request parameters cannot be
303 * altered until the method is {@link #recycle recycled}.</p>
304 *
305 * @return The request content length.
306 */
307 protected long getRequestContentLength() throws IOException {
308 LOG.trace("enter MultipartPostMethod.getRequestContentLength()");
309 return Part.getLengthOfParts(getParts());
310 }
311
312
313 /***
314 * Recycles the HTTP method so that it can be used again.
315 * Note that all of the instance variables will be reset
316 * once this method has been called. This method will also
317 * release the connection being used by this HTTP method.
318 *
319 * @see #releaseConnection()
320 *
321 */
322 public void recycle() {
323 LOG.trace("enter MultipartPostMethod.recycle()");
324 super.recycle();
325 parameters.clear();
326 }
327 }