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