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