1/*
2 * $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 $
3 * $Revision: 480424 $
4 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
5 *
6 * ====================================================================
7 *
8 *  Licensed to the Apache Software Foundation (ASF) under one or more
9 *  contributor license agreements.  See the NOTICE file distributed with
10 *  this work for additional information regarding copyright ownership.
11 *  The ASF licenses this file to You under the Apache License, Version 2.0
12 *  (the "License"); you may not use this file except in compliance with
13 *  the License.  You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation.  For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31package com.android.internal.http.multipart;
32
33import java.io.ByteArrayOutputStream;
34import java.io.IOException;
35import java.io.OutputStream;
36
37import org.apache.http.util.EncodingUtils;
38import org.apache.commons.logging.Log;
39import org.apache.commons.logging.LogFactory;
40
41/**
42 * Abstract class for one Part of a multipart post object.
43 *
44 * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
45 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
46 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
47 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
48 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
49 *
50 * @since 2.0
51 */
52public abstract class Part {
53
54    /** Log object for this class. */
55    private static final Log LOG = LogFactory.getLog(Part.class);
56
57    /**
58     * The boundary
59     * @deprecated use {@link org.apache.http.client.methods.multipart#MULTIPART_BOUNDARY}
60     */
61    protected static final String BOUNDARY = "----------------314159265358979323846";
62
63    /**
64     * The boundary as a byte array.
65     * @deprecated
66     */
67    protected static final byte[] BOUNDARY_BYTES = EncodingUtils.getAsciiBytes(BOUNDARY);
68
69    /**
70     * The default boundary to be used if {@link #setPartBoundary(byte[])} has not
71     * been called.
72     */
73    private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
74
75    /** Carriage return/linefeed */
76    protected static final String CRLF = "\r\n";
77
78    /** Carriage return/linefeed as a byte array */
79    protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF);
80
81    /** Content dispostion characters */
82    protected static final String QUOTE = "\"";
83
84    /** Content dispostion as a byte array */
85    protected static final byte[] QUOTE_BYTES =
86      EncodingUtils.getAsciiBytes(QUOTE);
87
88    /** Extra characters */
89    protected static final String EXTRA = "--";
90
91    /** Extra characters as a byte array */
92    protected static final byte[] EXTRA_BYTES =
93      EncodingUtils.getAsciiBytes(EXTRA);
94
95    /** Content dispostion characters */
96    protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
97
98    /** Content dispostion as a byte array */
99    protected static final byte[] CONTENT_DISPOSITION_BYTES =
100      EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION);
101
102    /** Content type header */
103    protected static final String CONTENT_TYPE = "Content-Type: ";
104
105    /** Content type header as a byte array */
106    protected static final byte[] CONTENT_TYPE_BYTES =
107      EncodingUtils.getAsciiBytes(CONTENT_TYPE);
108
109    /** Content charset */
110    protected static final String CHARSET = "; charset=";
111
112    /** Content charset as a byte array */
113    protected static final byte[] CHARSET_BYTES =
114      EncodingUtils.getAsciiBytes(CHARSET);
115
116    /** Content type header */
117    protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
118
119    /** Content type header as a byte array */
120    protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
121      EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
122
123    /**
124     * Return the boundary string.
125     * @return the boundary string
126     * @deprecated uses a constant string. Rather use {@link #getPartBoundary}
127     */
128    public static String getBoundary() {
129        return BOUNDARY;
130    }
131
132    /**
133     * The ASCII bytes to use as the multipart boundary.
134     */
135    private byte[] boundaryBytes;
136
137    /**
138     * Return the name of this part.
139     * @return The name.
140     */
141    public abstract String getName();
142
143    /**
144     * Returns the content type of this part.
145     * @return the content type, or <code>null</code> to exclude the content type header
146     */
147    public abstract String getContentType();
148
149    /**
150     * Return the character encoding of this part.
151     * @return the character encoding, or <code>null</code> to exclude the character
152     * encoding header
153     */
154    public abstract String getCharSet();
155
156    /**
157     * Return the transfer encoding of this part.
158     * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
159     */
160    public abstract String getTransferEncoding();
161
162    /**
163     * Gets the part boundary to be used.
164     * @return the part boundary as an array of bytes.
165     *
166     * @since 3.0
167     */
168    protected byte[] getPartBoundary() {
169        if (boundaryBytes == null) {
170            // custom boundary bytes have not been set, use the default.
171            return DEFAULT_BOUNDARY_BYTES;
172        } else {
173            return boundaryBytes;
174        }
175    }
176
177    /**
178     * Sets the part boundary.  Only meant to be used by
179     * {@link Part#sendParts(OutputStream, Part[], byte[])}
180     * and {@link Part#getLengthOfParts(Part[], byte[])}
181     * @param boundaryBytes An array of ASCII bytes.
182     * @since 3.0
183     */
184    void setPartBoundary(byte[] boundaryBytes) {
185        this.boundaryBytes = boundaryBytes;
186    }
187
188    /**
189     * Tests if this part can be sent more than once.
190     * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called
191     * more than once.
192     * @since 3.0
193     */
194    public boolean isRepeatable() {
195        return true;
196    }
197
198    /**
199     * Write the start to the specified output stream
200     * @param out The output stream
201     * @throws IOException If an IO problem occurs.
202     */
203    protected void sendStart(OutputStream out) throws IOException {
204        LOG.trace("enter sendStart(OutputStream out)");
205        out.write(EXTRA_BYTES);
206        out.write(getPartBoundary());
207        out.write(CRLF_BYTES);
208    }
209
210    /**
211     * Write the content disposition header to the specified output stream
212     *
213     * @param out The output stream
214     * @throws IOException If an IO problem occurs.
215     */
216    protected void sendDispositionHeader(OutputStream out) throws IOException {
217        LOG.trace("enter sendDispositionHeader(OutputStream out)");
218        out.write(CONTENT_DISPOSITION_BYTES);
219        out.write(QUOTE_BYTES);
220        out.write(EncodingUtils.getAsciiBytes(getName()));
221        out.write(QUOTE_BYTES);
222    }
223
224    /**
225     * Write the content type header to the specified output stream
226     * @param out The output stream
227     * @throws IOException If an IO problem occurs.
228     */
229     protected void sendContentTypeHeader(OutputStream out) throws IOException {
230        LOG.trace("enter sendContentTypeHeader(OutputStream out)");
231        String contentType = getContentType();
232        if (contentType != null) {
233            out.write(CRLF_BYTES);
234            out.write(CONTENT_TYPE_BYTES);
235            out.write(EncodingUtils.getAsciiBytes(contentType));
236            String charSet = getCharSet();
237            if (charSet != null) {
238                out.write(CHARSET_BYTES);
239                out.write(EncodingUtils.getAsciiBytes(charSet));
240            }
241        }
242    }
243
244    /**
245     * Write the content transfer encoding header to the specified
246     * output stream
247     *
248     * @param out The output stream
249     * @throws IOException If an IO problem occurs.
250     */
251     protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
252        LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
253        String transferEncoding = getTransferEncoding();
254        if (transferEncoding != null) {
255            out.write(CRLF_BYTES);
256            out.write(CONTENT_TRANSFER_ENCODING_BYTES);
257            out.write(EncodingUtils.getAsciiBytes(transferEncoding));
258        }
259    }
260
261    /**
262     * Write the end of the header to the output stream
263     * @param out The output stream
264     * @throws IOException If an IO problem occurs.
265     */
266    protected void sendEndOfHeader(OutputStream out) throws IOException {
267        LOG.trace("enter sendEndOfHeader(OutputStream out)");
268        out.write(CRLF_BYTES);
269        out.write(CRLF_BYTES);
270    }
271
272    /**
273     * Write the data to the specified output stream
274     * @param out The output stream
275     * @throws IOException If an IO problem occurs.
276     */
277    protected abstract void sendData(OutputStream out) throws IOException;
278
279    /**
280     * Return the length of the main content
281     *
282     * @return long The length.
283     * @throws IOException If an IO problem occurs
284     */
285    protected abstract long lengthOfData() throws IOException;
286
287    /**
288     * Write the end data to the output stream.
289     * @param out The output stream
290     * @throws IOException If an IO problem occurs.
291     */
292    protected void sendEnd(OutputStream out) throws IOException {
293        LOG.trace("enter sendEnd(OutputStream out)");
294        out.write(CRLF_BYTES);
295    }
296
297    /**
298     * Write all the data to the output stream.
299     * If you override this method make sure to override
300     * #length() as well
301     *
302     * @param out The output stream
303     * @throws IOException If an IO problem occurs.
304     */
305    public void send(OutputStream out) throws IOException {
306        LOG.trace("enter send(OutputStream out)");
307        sendStart(out);
308        sendDispositionHeader(out);
309        sendContentTypeHeader(out);
310        sendTransferEncodingHeader(out);
311        sendEndOfHeader(out);
312        sendData(out);
313        sendEnd(out);
314    }
315
316
317    /**
318     * Return the full length of all the data.
319     * If you override this method make sure to override
320     * #send(OutputStream) as well
321     *
322     * @return long The length.
323     * @throws IOException If an IO problem occurs
324     */
325    public long length() throws IOException {
326        LOG.trace("enter length()");
327        if (lengthOfData() < 0) {
328            return -1;
329        }
330        ByteArrayOutputStream overhead = new ByteArrayOutputStream();
331        sendStart(overhead);
332        sendDispositionHeader(overhead);
333        sendContentTypeHeader(overhead);
334        sendTransferEncodingHeader(overhead);
335        sendEndOfHeader(overhead);
336        sendEnd(overhead);
337        return overhead.size() + lengthOfData();
338    }
339
340    /**
341     * Return a string representation of this object.
342     * @return A string representation of this object.
343     * @see java.lang.Object#toString()
344     */
345    @Override
346    public String toString() {
347        return this.getName();
348    }
349
350    /**
351     * Write all parts and the last boundary to the specified output stream.
352     *
353     * @param out The stream to write to.
354     * @param parts The parts to write.
355     *
356     * @throws IOException If an I/O error occurs while writing the parts.
357     */
358    public static void sendParts(OutputStream out, final Part[] parts)
359        throws IOException {
360        sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
361    }
362
363    /**
364     * Write all parts and the last boundary to the specified output stream.
365     *
366     * @param out The stream to write to.
367     * @param parts The parts to write.
368     * @param partBoundary The ASCII bytes to use as the part boundary.
369     *
370     * @throws IOException If an I/O error occurs while writing the parts.
371     *
372     * @since 3.0
373     */
374    public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
375        throws IOException {
376
377        if (parts == null) {
378            throw new IllegalArgumentException("Parts may not be null");
379        }
380        if (partBoundary == null || partBoundary.length == 0) {
381            throw new IllegalArgumentException("partBoundary may not be empty");
382        }
383        for (int i = 0; i < parts.length; i++) {
384            // set the part boundary before the part is sent
385            parts[i].setPartBoundary(partBoundary);
386            parts[i].send(out);
387        }
388        out.write(EXTRA_BYTES);
389        out.write(partBoundary);
390        out.write(EXTRA_BYTES);
391        out.write(CRLF_BYTES);
392    }
393
394    /**
395     * Return the total sum of all parts and that of the last boundary
396     *
397     * @param parts The parts.
398     * @return The total length
399     *
400     * @throws IOException If an I/O error occurs while writing the parts.
401     */
402    public static long getLengthOfParts(Part[] parts)
403    throws IOException {
404        return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
405    }
406
407    /**
408     * Gets the length of the multipart message including the given parts.
409     *
410     * @param parts The parts.
411     * @param partBoundary The ASCII bytes to use as the part boundary.
412     * @return The total length
413     *
414     * @throws IOException If an I/O error occurs while writing the parts.
415     *
416     * @since 3.0
417     */
418    public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
419        LOG.trace("getLengthOfParts(Parts[])");
420        if (parts == null) {
421            throw new IllegalArgumentException("Parts may not be null");
422        }
423        long total = 0;
424        for (int i = 0; i < parts.length; i++) {
425            // set the part boundary before we calculate the part's length
426            parts[i].setPartBoundary(partBoundary);
427            long l = parts[i].length();
428            if (l < 0) {
429                return -1;
430            }
431            total += l;
432        }
433        total += EXTRA_BYTES.length;
434        total += partBoundary.length;
435        total += EXTRA_BYTES.length;
436        total += CRLF_BYTES.length;
437        return total;
438    }
439}
440