001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v 1.16 2005/01/14 21:16:40 olegk Exp $
003: * $Revision: 480424 $
004: * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient.methods.multipart;
032:
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.io.OutputStream;
036:
037: import org.apache.commons.httpclient.util.EncodingUtil;
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040:
041: /**
042: * Abstract class for one Part of a multipart post object.
043: *
044: * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
045: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
046: * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
047: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
048: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
049: *
050: * @since 2.0
051: */
052: public abstract class Part {
053:
054: /** Log object for this class. */
055: private static final Log LOG = LogFactory.getLog(Part.class);
056:
057: /**
058: * The boundary
059: * @deprecated use {@link org.apache.commons.httpclient.params.HttpMethodParams#MULTIPART_BOUNDARY}
060: */
061: protected static final String BOUNDARY = "----------------314159265358979323846";
062:
063: /**
064: * The boundary as a byte array.
065: * @deprecated
066: */
067: protected static final byte[] BOUNDARY_BYTES = EncodingUtil
068: .getAsciiBytes(BOUNDARY);
069:
070: /**
071: * The default boundary to be used if {@link #setBoundaryBytes(byte[])) has not
072: * been called.
073: */
074: private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
075:
076: /** Carriage return/linefeed */
077: protected static final String CRLF = "\r\n";
078:
079: /** Carriage return/linefeed as a byte array */
080: protected static final byte[] CRLF_BYTES = EncodingUtil
081: .getAsciiBytes(CRLF);
082:
083: /** Content dispostion characters */
084: protected static final String QUOTE = "\"";
085:
086: /** Content dispostion as a byte array */
087: protected static final byte[] QUOTE_BYTES = EncodingUtil
088: .getAsciiBytes(QUOTE);
089:
090: /** Extra characters */
091: protected static final String EXTRA = "--";
092:
093: /** Extra characters as a byte array */
094: protected static final byte[] EXTRA_BYTES = EncodingUtil
095: .getAsciiBytes(EXTRA);
096:
097: /** Content dispostion characters */
098: protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
099:
100: /** Content dispostion as a byte array */
101: protected static final byte[] CONTENT_DISPOSITION_BYTES = EncodingUtil
102: .getAsciiBytes(CONTENT_DISPOSITION);
103:
104: /** Content type header */
105: protected static final String CONTENT_TYPE = "Content-Type: ";
106:
107: /** Content type header as a byte array */
108: protected static final byte[] CONTENT_TYPE_BYTES = EncodingUtil
109: .getAsciiBytes(CONTENT_TYPE);
110:
111: /** Content charset */
112: protected static final String CHARSET = "; charset=";
113:
114: /** Content charset as a byte array */
115: protected static final byte[] CHARSET_BYTES = EncodingUtil
116: .getAsciiBytes(CHARSET);
117:
118: /** Content type header */
119: protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
120:
121: /** Content type header as a byte array */
122: protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES = EncodingUtil
123: .getAsciiBytes(CONTENT_TRANSFER_ENCODING);
124:
125: /**
126: * Return the boundary string.
127: * @return the boundary string
128: * @deprecated uses a constant string. Rather use {@link #getPartBoundary}
129: */
130: public static String getBoundary() {
131: return BOUNDARY;
132: }
133:
134: /**
135: * The ASCII bytes to use as the multipart boundary.
136: */
137: private byte[] boundaryBytes;
138:
139: /**
140: * Return the name of this part.
141: * @return The name.
142: */
143: public abstract String getName();
144:
145: /**
146: * Returns the content type of this part.
147: * @return the content type, or <code>null</code> to exclude the content type header
148: */
149: public abstract String getContentType();
150:
151: /**
152: * Return the character encoding of this part.
153: * @return the character encoding, or <code>null</code> to exclude the character
154: * encoding header
155: */
156: public abstract String getCharSet();
157:
158: /**
159: * Return the transfer encoding of this part.
160: * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
161: */
162: public abstract String getTransferEncoding();
163:
164: /**
165: * Gets the part boundary to be used.
166: * @return the part boundary as an array of bytes.
167: *
168: * @since 3.0
169: */
170: protected byte[] getPartBoundary() {
171: if (boundaryBytes == null) {
172: // custom boundary bytes have not been set, use the default.
173: return DEFAULT_BOUNDARY_BYTES;
174: } else {
175: return boundaryBytes;
176: }
177: }
178:
179: /**
180: * Sets the part boundary. Only meant to be used by
181: * {@link Part#sendParts(OutputStream, Part[], byte[])}
182: * and {@link Part#getLengthOfParts(Part[], byte[])}
183: * @param boundaryBytes An array of ASCII bytes.
184: * @since 3.0
185: */
186: void setPartBoundary(byte[] boundaryBytes) {
187: this .boundaryBytes = boundaryBytes;
188: }
189:
190: /**
191: * Tests if this part can be sent more than once.
192: * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called
193: * more than once.
194: * @since 3.0
195: */
196: public boolean isRepeatable() {
197: return true;
198: }
199:
200: /**
201: * Write the start to the specified output stream
202: * @param out The output stream
203: * @throws IOException If an IO problem occurs.
204: */
205: protected void sendStart(OutputStream out) throws IOException {
206: LOG.trace("enter sendStart(OutputStream out)");
207: out.write(EXTRA_BYTES);
208: out.write(getPartBoundary());
209: out.write(CRLF_BYTES);
210: }
211:
212: /**
213: * Write the content disposition header to the specified output stream
214: *
215: * @param out The output stream
216: * @throws IOException If an IO problem occurs.
217: */
218: protected void sendDispositionHeader(OutputStream out)
219: throws IOException {
220: LOG.trace("enter sendDispositionHeader(OutputStream out)");
221: out.write(CONTENT_DISPOSITION_BYTES);
222: out.write(QUOTE_BYTES);
223: out.write(EncodingUtil.getAsciiBytes(getName()));
224: out.write(QUOTE_BYTES);
225: }
226:
227: /**
228: * Write the content type header to the specified output stream
229: * @param out The output stream
230: * @throws IOException If an IO problem occurs.
231: */
232: protected void sendContentTypeHeader(OutputStream out)
233: throws IOException {
234: LOG.trace("enter sendContentTypeHeader(OutputStream out)");
235: String contentType = getContentType();
236: if (contentType != null) {
237: out.write(CRLF_BYTES);
238: out.write(CONTENT_TYPE_BYTES);
239: out.write(EncodingUtil.getAsciiBytes(contentType));
240: String charSet = getCharSet();
241: if (charSet != null) {
242: out.write(CHARSET_BYTES);
243: out.write(EncodingUtil.getAsciiBytes(charSet));
244: }
245: }
246: }
247:
248: /**
249: * Write the content transfer encoding header to the specified
250: * output stream
251: *
252: * @param out The output stream
253: * @throws IOException If an IO problem occurs.
254: */
255: protected void sendTransferEncodingHeader(OutputStream out)
256: throws IOException {
257: LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
258: String transferEncoding = getTransferEncoding();
259: if (transferEncoding != null) {
260: out.write(CRLF_BYTES);
261: out.write(CONTENT_TRANSFER_ENCODING_BYTES);
262: out.write(EncodingUtil.getAsciiBytes(transferEncoding));
263: }
264: }
265:
266: /**
267: * Write the end of the header to the output stream
268: * @param out The output stream
269: * @throws IOException If an IO problem occurs.
270: */
271: protected void sendEndOfHeader(OutputStream out) throws IOException {
272: LOG.trace("enter sendEndOfHeader(OutputStream out)");
273: out.write(CRLF_BYTES);
274: out.write(CRLF_BYTES);
275: }
276:
277: /**
278: * Write the data to the specified output stream
279: * @param out The output stream
280: * @throws IOException If an IO problem occurs.
281: */
282: protected abstract void sendData(OutputStream out)
283: throws IOException;
284:
285: /**
286: * Return the length of the main content
287: *
288: * @return long The length.
289: * @throws IOException If an IO problem occurs
290: */
291: protected abstract long lengthOfData() throws IOException;
292:
293: /**
294: * Write the end data to the output stream.
295: * @param out The output stream
296: * @throws IOException If an IO problem occurs.
297: */
298: protected void sendEnd(OutputStream out) throws IOException {
299: LOG.trace("enter sendEnd(OutputStream out)");
300: out.write(CRLF_BYTES);
301: }
302:
303: /**
304: * Write all the data to the output stream.
305: * If you override this method make sure to override
306: * #length() as well
307: *
308: * @param out The output stream
309: * @throws IOException If an IO problem occurs.
310: */
311: public void send(OutputStream out) throws IOException {
312: LOG.trace("enter send(OutputStream out)");
313: sendStart(out);
314: sendDispositionHeader(out);
315: sendContentTypeHeader(out);
316: sendTransferEncodingHeader(out);
317: sendEndOfHeader(out);
318: sendData(out);
319: sendEnd(out);
320: }
321:
322: /**
323: * Return the full length of all the data.
324: * If you override this method make sure to override
325: * #send(OutputStream) as well
326: *
327: * @return long The length.
328: * @throws IOException If an IO problem occurs
329: */
330: public long length() throws IOException {
331: LOG.trace("enter length()");
332: if (lengthOfData() < 0) {
333: return -1;
334: }
335: ByteArrayOutputStream overhead = new ByteArrayOutputStream();
336: sendStart(overhead);
337: sendDispositionHeader(overhead);
338: sendContentTypeHeader(overhead);
339: sendTransferEncodingHeader(overhead);
340: sendEndOfHeader(overhead);
341: sendEnd(overhead);
342: return overhead.size() + lengthOfData();
343: }
344:
345: /**
346: * Return a string representation of this object.
347: * @return A string representation of this object.
348: * @see java.lang.Object#toString()
349: */
350: public String toString() {
351: return this .getName();
352: }
353:
354: /**
355: * Write all parts and the last boundary to the specified output stream.
356: *
357: * @param out The stream to write to.
358: * @param parts The parts to write.
359: *
360: * @throws IOException If an I/O error occurs while writing the parts.
361: */
362: public static void sendParts(OutputStream out, final Part[] parts)
363: throws IOException {
364: sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
365: }
366:
367: /**
368: * Write all parts and the last boundary to the specified output stream.
369: *
370: * @param out The stream to write to.
371: * @param parts The parts to write.
372: * @param partBoundary The ASCII bytes to use as the part boundary.
373: *
374: * @throws IOException If an I/O error occurs while writing the parts.
375: *
376: * @since 3.0
377: */
378: public static void sendParts(OutputStream out, Part[] parts,
379: byte[] partBoundary) throws IOException {
380:
381: if (parts == null) {
382: throw new IllegalArgumentException("Parts may not be null");
383: }
384: if (partBoundary == null || partBoundary.length == 0) {
385: throw new IllegalArgumentException(
386: "partBoundary may not be empty");
387: }
388: for (int i = 0; i < parts.length; i++) {
389: // set the part boundary before the part is sent
390: parts[i].setPartBoundary(partBoundary);
391: parts[i].send(out);
392: }
393: out.write(EXTRA_BYTES);
394: out.write(partBoundary);
395: out.write(EXTRA_BYTES);
396: out.write(CRLF_BYTES);
397: }
398:
399: /**
400: * Return the total sum of all parts and that of the last boundary
401: *
402: * @param parts The parts.
403: * @return The total length
404: *
405: * @throws IOException If an I/O error occurs while writing the parts.
406: */
407: public static long getLengthOfParts(Part[] parts)
408: throws IOException {
409: return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
410: }
411:
412: /**
413: * Gets the length of the multipart message including the given parts.
414: *
415: * @param parts The parts.
416: * @param partBoundary The ASCII bytes to use as the part boundary.
417: * @return The total length
418: *
419: * @throws IOException If an I/O error occurs while writing the parts.
420: *
421: * @since 3.0
422: */
423: public static long getLengthOfParts(Part[] parts,
424: byte[] partBoundary) throws IOException {
425: LOG.trace("getLengthOfParts(Parts[])");
426: if (parts == null) {
427: throw new IllegalArgumentException("Parts may not be null");
428: }
429: long total = 0;
430: for (int i = 0; i < parts.length; i++) {
431: // set the part boundary before we calculate the part's length
432: parts[i].setPartBoundary(partBoundary);
433: long l = parts[i].length();
434: if (l < 0) {
435: return -1;
436: }
437: total += l;
438: }
439: total += EXTRA_BYTES.length;
440: total += partBoundary.length;
441: total += EXTRA_BYTES.length;
442: total += CRLF_BYTES.length;
443: return total;
444: }
445: }
|