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
31
32 package org.apache.commons.httpclient.methods.multipart;
33
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.io.OutputStream;
37
38 import org.apache.commons.httpclient.HttpConstants;
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41
42 /***
43 * Abstract class for one Part of a multipart post object.
44 *
45 * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
46 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
47 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
48 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
49 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
50 *
51 * @since 2.0
52 */
53 public abstract class Part {
54
55 /*** Log object for this class. */
56 private static final Log LOG = LogFactory.getLog(Part.class);
57
58
59
60 /*** The boundary */
61 protected static final String BOUNDARY = "----------------314159265358979323846";
62
63 /*** The boundary as a byte array */
64 protected static final byte[] BOUNDARY_BYTES = HttpConstants.getAsciiBytes(BOUNDARY);
65
66 /*** Carriage return/linefeed */
67 protected static final String CRLF = "\r\n";
68
69 /*** Carriage return/linefeed as a byte array */
70 protected static final byte[] CRLF_BYTES = HttpConstants.getAsciiBytes(CRLF);
71
72 /*** Content dispostion characters */
73 protected static final String QUOTE = "\"";
74
75 /*** Content dispostion as a byte array */
76 protected static final byte[] QUOTE_BYTES =
77 HttpConstants.getAsciiBytes(QUOTE);
78
79 /*** Extra characters */
80 protected static final String EXTRA = "--";
81
82 /*** Extra characters as a byte array */
83 protected static final byte[] EXTRA_BYTES =
84 HttpConstants.getAsciiBytes(EXTRA);
85
86 /*** Content dispostion characters */
87 protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
88
89 /*** Content dispostion as a byte array */
90 protected static final byte[] CONTENT_DISPOSITION_BYTES =
91 HttpConstants.getAsciiBytes(CONTENT_DISPOSITION);
92
93 /*** Content type header */
94 protected static final String CONTENT_TYPE = "Content-Type: ";
95
96 /*** Content type header as a byte array */
97 protected static final byte[] CONTENT_TYPE_BYTES =
98 HttpConstants.getAsciiBytes(CONTENT_TYPE);
99
100 /*** Content charset */
101 protected static final String CHARSET = "; charset=";
102
103 /*** Content charset as a byte array */
104 protected static final byte[] CHARSET_BYTES =
105 HttpConstants.getAsciiBytes(CHARSET);
106
107 /*** Content type header */
108 protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
109
110 /*** Content type header as a byte array */
111 protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
112 HttpConstants.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
113
114 /***
115 * Return the boundary string.
116 * @return the boundary string
117 */
118 public static String getBoundary() {
119 return BOUNDARY;
120 }
121
122 /***
123 * Return the name of this part.
124 * @return The name.
125 */
126 public abstract String getName();
127
128 /***
129 * Returns the content type of this part.
130 * @return the content type, or <code>null</code> to exclude the content type header
131 */
132 public abstract String getContentType();
133
134 /***
135 * Return the character encoding of this part.
136 * @return the character encoding, or <code>null</code> to exclude the character
137 * encoding header
138 */
139 public abstract String getCharSet();
140
141 /***
142 * Return the transfer encoding of this part.
143 * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
144 */
145 public abstract String getTransferEncoding();
146
147 /***
148 * Write the start to the specified output stream
149 * @param out The output stream
150 * @throws IOException If an IO problem occurs.
151 */
152 protected void sendStart(OutputStream out) throws IOException {
153 LOG.trace("enter sendStart(OutputStream out)");
154 out.write(EXTRA_BYTES);
155 out.write(BOUNDARY_BYTES);
156 out.write(CRLF_BYTES);
157 }
158
159 /***
160 * Write the content disposition header to the specified output stream
161 *
162 * @param out The output stream
163 * @throws IOException If an IO problem occurs.
164 */
165 protected void sendDispositionHeader(OutputStream out) throws IOException {
166 LOG.trace("enter sendDispositionHeader(OutputStream out)");
167 out.write(CONTENT_DISPOSITION_BYTES);
168 out.write(QUOTE_BYTES);
169 out.write(HttpConstants.getAsciiBytes(getName()));
170 out.write(QUOTE_BYTES);
171 }
172
173 /***
174 * Write the content type header to the specified output stream
175 * @param out The output stream
176 * @throws IOException If an IO problem occurs.
177 */
178 protected void sendContentTypeHeader(OutputStream out) throws IOException {
179 LOG.trace("enter sendContentTypeHeader(OutputStream out)");
180 String contentType = getContentType();
181 if (contentType != null) {
182 out.write(CRLF_BYTES);
183 out.write(CONTENT_TYPE_BYTES);
184 out.write(HttpConstants.getAsciiBytes(contentType));
185 String charSet = getCharSet();
186 if (charSet != null) {
187 out.write(CHARSET_BYTES);
188 out.write(HttpConstants.getAsciiBytes(charSet));
189 }
190 }
191 }
192
193 /***
194 * Write the content transfer encoding header to the specified
195 * output stream
196 *
197 * @param out The output stream
198 * @throws IOException If an IO problem occurs.
199 */
200 protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
201 LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
202 String transferEncoding = getTransferEncoding();
203 if (transferEncoding != null) {
204 out.write(CRLF_BYTES);
205 out.write(CONTENT_TRANSFER_ENCODING_BYTES);
206 out.write(HttpConstants.getAsciiBytes(transferEncoding));
207 }
208 }
209
210 /***
211 * Write the end of the header to the output stream
212 * @param out The output stream
213 * @throws IOException If an IO problem occurs.
214 */
215 protected void sendEndOfHeader(OutputStream out) throws IOException {
216 LOG.trace("enter sendEndOfHeader(OutputStream out)");
217 out.write(CRLF_BYTES);
218 out.write(CRLF_BYTES);
219 }
220
221 /***
222 * Write the data to the specified output stream
223 * @param out The output stream
224 * @throws IOException If an IO problem occurs.
225 */
226 protected abstract void sendData(OutputStream out) throws IOException;
227
228 /***
229 * Return the length of the main content
230 *
231 * @return long The length.
232 * @throws IOException If an IO problem occurs
233 */
234 protected abstract long lengthOfData() throws IOException;
235
236 /***
237 * Write the end data to the output stream.
238 * @param out The output stream
239 * @throws IOException If an IO problem occurs.
240 */
241 protected void sendEnd(OutputStream out) throws IOException {
242 LOG.trace("enter sendEnd(OutputStream out)");
243 out.write(CRLF_BYTES);
244 }
245
246 /***
247 * Write all the data to the output stream.
248 * If you override this method make sure to override
249 * #length() as well
250 *
251 * @param out The output stream
252 * @throws IOException If an IO problem occurs.
253 */
254 public void send(OutputStream out) throws IOException {
255 LOG.trace("enter send(OutputStream out)");
256 sendStart(out);
257 sendDispositionHeader(out);
258 sendContentTypeHeader(out);
259 sendTransferEncodingHeader(out);
260 sendEndOfHeader(out);
261 sendData(out);
262 sendEnd(out);
263 }
264
265
266 /***
267 * Return the full length of all the data.
268 * If you override this method make sure to override
269 * #send(OutputStream) as well
270 *
271 * @return long The length.
272 * @throws IOException If an IO problem occurs
273 */
274 public long length() throws IOException {
275 LOG.trace("enter length()");
276 ByteArrayOutputStream overhead = new ByteArrayOutputStream();
277 sendStart(overhead);
278 sendDispositionHeader(overhead);
279 sendContentTypeHeader(overhead);
280 sendTransferEncodingHeader(overhead);
281 sendEndOfHeader(overhead);
282 sendEnd(overhead);
283 return overhead.size() + lengthOfData();
284 }
285
286 /***
287 * Return a string representation of this object.
288 * @return A string representation of this object.
289 * @see java.lang.Object#toString()
290 */
291 public String toString() {
292 return this.getName();
293 }
294
295 /***
296 * Write all parts and the last boundary to the specified output stream
297 *
298 * @param out The output stream
299 * @param parts The array of parts to be sent
300 *
301 * @throws IOException If an IO problem occurs.
302 */
303 public static void sendParts(OutputStream out, final Part[] parts)
304 throws IOException {
305 LOG.trace("enter sendParts(OutputStream out, Parts[])");
306 if (parts == null) {
307 throw new IllegalArgumentException("Parts may not be null");
308 }
309 for (int i = 0; i < parts.length; i++) {
310 parts[i].send(out);
311 }
312 out.write(EXTRA_BYTES);
313 out.write(BOUNDARY_BYTES);
314 out.write(EXTRA_BYTES);
315 out.write(CRLF_BYTES);
316 }
317
318 /***
319 * Return the total sum of all parts and that of the last boundary
320 *
321 * @param parts The array of parts
322 *
323 * @return the total length
324 *
325 * @throws IOException If an IO problem occurs.
326 */
327 public static long getLengthOfParts(final Part[] parts)
328 throws IOException {
329 LOG.trace("getLengthOfParts(Parts[])");
330 if (parts == null) {
331 throw new IllegalArgumentException("Parts may not be null");
332 }
333 long total = 0;
334 for (int i = 0; i < parts.length; i++) {
335 total += parts[i].length();
336 }
337 total += EXTRA_BYTES.length;
338 total += BOUNDARY_BYTES.length;
339 total += EXTRA_BYTES.length;
340 total += CRLF_BYTES.length;
341 return total;
342 }
343 }