You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ol...@apache.org on 2003/02/12 13:30:45 UTC

cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestWebappMultiPostMethod.java TestWebapp.java

olegk       2003/02/12 04:30:44

  Modified:    httpclient/src/java/org/apache/commons/httpclient/methods
                        MultipartPostMethod.java
               httpclient/src/java/org/apache/commons/httpclient/methods/multipart
                        FilePart.java Part.java StringPart.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestWebapp.java
  Added:       httpclient/src/test/org/apache/commons/httpclient
                        TestWebappMultiPostMethod.java
  Log:
  Bug fix:
  http://nagoya.apache.org/bugzilla/show_bug.cgi?id=14782
  
  Change log:
  - MultipartPost clean-up
  - Part#send(OutputStream out) &  long Part#length() are no longer final
  - Custom character set can be specified for all multipart classes
  - Custom content type can be specified for FilePart derived multipart classes
  
  Contributed by Oleg Kalnichevski
  
  Revision  Changes    Path
  1.9       +28 -30    jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java
  
  Index: MultipartPostMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- MultipartPostMethod.java	2 Feb 2003 04:30:13 -0000	1.8
  +++ MultipartPostMethod.java	12 Feb 2003 12:30:44 -0000	1.9
  @@ -94,6 +94,10 @@
    */
   public class MultipartPostMethod extends GetMethod {
   
  +    /** The Content-Type for multipart/form-data. */
  +    public static final String MULTIPART_FORM_CONTENT_TYPE = 
  +        "multipart/form-data";
  +
       /** Log object for this class. */
       private static final Log LOG = LogFactory.getLog(MultipartPostMethod.class);
   
  @@ -204,6 +208,14 @@
       }
   
       /**
  +     * Return all parts.
  +     * 
  +     * @return an array of containing all parts
  +     */
  +    public Part[] getParts() {
  +        return (Part[])parameters.toArray(new Part[parameters.size()]);
  +    }
  +    /**
        * Add a request header.
        * 
        * @param state the client state
  @@ -218,8 +230,12 @@
           super.addRequestHeaders(state, conn);
           
           if (!parameters.isEmpty()) {
  -            setRequestHeader("Content-Type", 
  -                 "multipart/form-data; boundary=" + Part.getBoundary());
  +            StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
  +            if (Part.getBoundary() != null) {
  +                buffer.append("; boundary=");
  +                buffer.append(Part.getBoundary());
  +            }
  +            setRequestHeader("Content-Type", buffer.toString());
           }
       }
   
  @@ -237,16 +253,7 @@
       throws IOException, HttpException {
           LOG.trace("enter writeRequestBody(HttpState state, HttpConnection conn)");
           OutputStream out = conn.getRequestOutputStream();
  -        
  -        for (Iterator it = parameters.iterator(); it.hasNext();) {
  -            final Part part = (Part) it.next();
  -            part.send(out);
  -        }
  -        
  -        Part.sendLastBoundary(out);
  -
  -        out.flush();
  -        
  +        Part.sendParts(out, getParts());
           return true;
       }
   
  @@ -260,26 +267,17 @@
        */
       protected int getRequestContentLength() {
           LOG.trace("enter getRequestContentLength()");
  -        long length = 0;
  -        
           try {
  -            for (Iterator it = parameters.iterator(); it.hasNext();) {
  -                final Part part = (Part) it.next();
  -            
  -                length += part.length();
  +            long len = Part.getLengthOfParts(getParts());
  +            // Chop the length to the max int value.
  +            if (len <= Integer.MAX_VALUE) {
  +                return (int)len;
  +            } else {
  +                return (Integer.MAX_VALUE);
               }
  -            length += Part.lengthOfLastBoundary();
  -
           } catch (IOException e) {
               // Can't throw an IOException and still override
               throw new RuntimeException(e.toString());
  -        }
  -        
  -        // Chop the length to the max int value.
  -        if (length <= Integer.MAX_VALUE) {
  -            return ((new Long(length)).intValue());
  -        } else {
  -            return (Integer.MAX_VALUE);
           }
       }
   }
  
  
  
  1.11      +142 -71   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java
  
  Index: FilePart.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- FilePart.java	28 Jan 2003 22:25:31 -0000	1.10
  +++ FilePart.java	12 Feb 2003 12:30:44 -0000	1.11
  @@ -82,27 +82,84 @@
    * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
    * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
    * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
  + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    *   
    * @since 2.0 
    *
    */
   public class FilePart extends Part {
   
  +    /** Default content encoding of file attachments. */
  +    public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
  +
  +    /** Default charset of file attachments. */
  +    public static final String DEFAULT_CHARSET = HttpConstants.DEFAULT_CONTENT_CHARSET;
  +
  +    /** Default transfer encoding of file attachments. */
  +    public static final String DEFAULT_TRANSFER_ENCODING = "binary";
  +
       /** Log object for this class. */
       private static final Log LOG = LogFactory.getLog(FilePart.class);
   
  -    /**
  -     * <p>The maximum buffer size.</p>
  -     * TODO: make this configurable
  -     */
  -    private static final int MAX_BUFF_SIZE = 1 * 1024 * 1024;  // 1 MiBs
  -    
  +
  +    /** Attachment's file name */
  +    protected static final String FILE_NAME = "; filename=";
  +
  +    /** Attachment's file name as a byte array */
  +    protected static final byte[] FILE_NAME_BYTES = 
  +      HttpConstants.getAsciiBytes(FILE_NAME);
  +
       /** Name of the file part. */
       private String name;
   
       /** Source of the file part. */
       private PartSource source;
           
  +    /** Content type of the file part. */
  +    private String contentType;
  +
  +    /** Content encoding of the file part. */
  +    private String charset;
  +
  +    /**
  +     * FilePart Constructor.
  +     *
  +     * @param name the name for this part
  +     * @param partSource the source for this part
  +     * @param contentType the content type for this part
  +     * @param charset the charset encoding for this part
  +     */
  +    public FilePart(String name, PartSource partSource, String contentType, String charset) {
  +        LOG.trace("enter FilePart(String, PartSource, String, String)");
  +        if (name == null) {
  +            throw new IllegalArgumentException("Name may not be null");
  +        }
  +        this.name = name;
  +        if (partSource == null) {
  +            throw new IllegalArgumentException("Source may not be null");
  +        }
  +        if (partSource.getLength() < 0) {
  +            throw new IllegalArgumentException("Source length must be >= 0");
  +        }
  +        this.source = partSource;
  +        if (contentType != null) {
  +            this.contentType = contentType;
  +        } else {
  +            this.contentType = DEFAULT_CONTENT_TYPE;
  +        }
  +        this.charset = charset;
  +    }
  +        
  +    /**
  +     * FilePart Constructor.
  +     *
  +     * @param name the name for this part
  +     * @param partSource the source for this part
  +     */
  +    public FilePart(String name, PartSource partSource) {
  +        this(name, partSource, null, null);
  +    }
  +
       /**
        * FilePart Constructor.
        *
  @@ -114,7 +171,23 @@
        */
       public FilePart(String name, File file) 
       throws FileNotFoundException {
  -        this(name, new FilePartSource(file));
  +        this(name, new FilePartSource(file), null, null);
  +    }
  +
  +    /**
  +     * FilePart Constructor.
  +     *
  +     * @param name the name of the file part
  +     * @param file the file to post
  +     * @param contentType the content type for the file
  +     * @param charset the charset encoding of the file
  +     *
  +     * @throws FileNotFoundException if the <i>file</i> is not a normal
  +     * file or if it is not readable.
  +     */
  +    public FilePart(String name, File file, String contentType, String charset) 
  +    throws FileNotFoundException {
  +        this(name, new FilePartSource(file), contentType, charset);
       }
   
        /**
  @@ -129,71 +202,77 @@
        */
       public FilePart(String name, String fileName, File file) 
       throws FileNotFoundException {
  -        this(name, new FilePartSource(fileName, file));
  +        this(name, new FilePartSource(fileName, file), null, null);
       }
       
  -    /**
  +     /**
        * FilePart Constructor.
        *
  -     * @param name the name for this part
  -     * @param partSource the source for this part
  +     * @param name the name of the file part
  +     * @param fileName the file name 
  +     * @param file the file to post
  +     * @param contentType the content type for the file
  +     * @param charset the charset encoding of the file
  +     *
  +     * @throws FileNotFoundException if the <i>file</i> is not a normal
  +     * file or if it is not readable.
        */
  -    public FilePart(String name, PartSource partSource) {
  -
  -        if (partSource.getLength() < 0) {
  -            throw new IllegalArgumentException("fileLength must be >= 0");
  -        }
  -
  -        this.name = name;
  -        this.source = partSource;
  -
  +    public FilePart(String name, String fileName, File file, String contentType, String charset) 
  +    throws FileNotFoundException {
  +        this(name, new FilePartSource(fileName, file), contentType, charset);
       }
  -        
  +    
       /**
  -     * Write the header to the output stream
  -     * @param out The output stream
  -     * @throws IOException If an IO problem occurs
  -     * @see org.apache.commons.httpclient.methods.multipart.Part#sendHeader(OutputStream)
  +     * Return the name.
  +     * @return The name.
  +     * @see org.apache.commons.httpclient.methods.multipart.Part#getName()
        */
  -    protected void sendHeader(OutputStream out) 
  -    throws IOException {
  -        LOG.trace("enter sendHeader(OutputStream out)");
  -        super.sendHeader(out);
  -        sendFilename(out);
  -        sendContentType(out);
  +    public String getName() { 
  +        return this.name; 
       }
  -    
  +
       /**
  -     * Write the filename to the output stream
  -     * @param out The output stream
  -     * @throws IOException If an IO problem occurs
  +     * Return the content type of this part.
  +     * @return String The name.
        */
  -    protected void sendFilename(OutputStream out) 
  -    throws IOException {
  -        LOG.trace("enter sendFilename(OutputStream out)");
  -        String filename = "; filename=\"" + source.getFileName() + "\"";
  -        out.write(HttpConstants.getBytes(filename));
  +    public String getContentType() {
  +        return this.contentType;
       }
   
       /**
  -     * Write the Content-Type header to the output stream
  -     * @param out The output stream.
  -     * @throws IOException If an IO problem occurs
  +     * Return the character encoding of this part.
  +     * @return String The name.
        */
  -    protected void sendContentType(OutputStream out) 
  -    throws IOException {
  -        LOG.trace("enter sendContentType(OutputStream out)");
  -        out.write(CRLF_BYTES);
  -        out.write(HttpConstants.getBytes("Content-Type: application/octet-stream"));
  -    }    
  +    public String getCharSet() {
  +        return this.charset;
  +    }
   
       /**
  -     * Return the name.
  -     * @return The name.
  -     * @see org.apache.commons.httpclient.methods.multipart.Part#getName()
  +     * Return the transfer encoding of this part.
  +     * @return String The name.
        */
  -    public String getName() { 
  -        return name; 
  +
  +    public String getTransferEncoding() {
  +        return DEFAULT_TRANSFER_ENCODING;
  +    }
  +    
  +    /**
  +     * Write the disposition header to the output stream
  +     * @param out The output stream
  +     * @throws IOException If an IO problem occurs
  +     * @see org.apache.commons.httpclient.methods.multipart.Part#sendHeader(OutputStream)
  +     */
  +    protected void sendDispositionHeader(OutputStream out) 
  +    throws IOException {
  +        LOG.trace("enter sendDispositionHeader(OutputStream out)");
  +        super.sendDispositionHeader(out);
  +        String filename = this.source.getFileName();
  +        if (filename != null) {
  +            out.write(FILE_NAME_BYTES);
  +            out.write(QUOTE_BYTES);
  +            out.write(HttpConstants.getAsciiBytes(filename));
  +            out.write(QUOTE_BYTES);
  +        }
       }
       
       /**
  @@ -204,9 +283,6 @@
        */
       protected void sendData(OutputStream out) throws IOException {
           LOG.trace("enter sendData(OutputStream out)");
  -        
  -        byte[] buff;
  -        
           if (lengthOfData() == 0) {
               
               // this file contains no data, so there is nothing to send.
  @@ -214,20 +290,15 @@
               // cause an infinite loop when reading.
               LOG.debug("No data to send.");
               return;
  -            
  -        } else if (lengthOfData() > MAX_BUFF_SIZE) {
  -            buff = new byte[MAX_BUFF_SIZE];
  -        } else {
  -            buff = new byte[(new Long(lengthOfData())).intValue()];
           }
           
  -        InputStream is = source.createInputStream();
  +        byte[] tmp = new byte[4096];
  +        InputStream instream = source.createInputStream();
   
           int len;
  -        while ((len = is.read(buff)) != -1) {
  -            out.write(buff, 0, len);
  +        while ((len = instream.read(tmp)) >= 0) {
  +            out.write(tmp, 0, len);
           }
  -
       }
   
       /**
  @@ -237,8 +308,8 @@
        * @see org.apache.commons.httpclient.methods.multipart.Part#lengthOfData()
        */    
       protected long lengthOfData() throws IOException {
  +        LOG.trace("enter lengthOfData()");
           return source.getLength();
       }    
   
  -}
  -
  +}
  \ No newline at end of file
  
  
  
  1.8       +173 -104  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java
  
  Index: Part.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- Part.java	28 Jan 2003 22:25:31 -0000	1.7
  +++ Part.java	12 Feb 2003 12:30:44 -0000	1.8
  @@ -63,9 +63,11 @@
   
   package org.apache.commons.httpclient.methods.multipart;
   
  +import java.io.IOException;
   import java.io.OutputStream;
  +import java.io.ByteArrayInputStream;
   import java.io.ByteArrayOutputStream;
  -import java.io.IOException;
  +
   import org.apache.commons.httpclient.HttpConstants;
   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;
  @@ -77,6 +79,7 @@
    * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
    * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
  + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    *
    * @since 2.0
    */
  @@ -88,23 +91,59 @@
       //TODO: Make this configurable
       
       /** The boundary */
  -    private static final String BOUNDARY = "----------------314159265358979323846";
  +    protected static final String BOUNDARY = "----------------314159265358979323846";
       
       /** The boundary as a byte array */
  -    private static final byte[] BOUNDARY_BYTES = HttpConstants.getBytes(BOUNDARY);
  +    protected static final byte[] BOUNDARY_BYTES = HttpConstants.getAsciiBytes(BOUNDARY);
       
       /** Carriage return/linefeed */
  -    private static final String CRLF = "\r\n";
  +    protected static final String CRLF = "\r\n";
       
       /** Carriage return/linefeed as a byte array */
  -    protected static final byte[] CRLF_BYTES = HttpConstants.getBytes(CRLF);
  +    protected static final byte[] CRLF_BYTES = HttpConstants.getAsciiBytes(CRLF);
  +    
  +    /** Content dispostion characters */
  +    protected static final String QUOTE = "\"";
       
  +    /** Content dispostion as a byte array */
  +    protected static final byte[] QUOTE_BYTES = 
  +      HttpConstants.getAsciiBytes(QUOTE);
  +
       /** Extra characters */
  -    private static final String EXTRA = "--";
  +    protected static final String EXTRA = "--";
       
       /** Extra characters as a byte array */
  -    private static final byte[] EXTRA_BYTES = HttpConstants.getBytes(EXTRA);
  +    protected static final byte[] EXTRA_BYTES = 
  +      HttpConstants.getAsciiBytes(EXTRA);
  +    
  +    /** Content dispostion characters */
  +    protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
       
  +    /** Content dispostion as a byte array */
  +    protected static final byte[] CONTENT_DISPOSITION_BYTES = 
  +      HttpConstants.getAsciiBytes(CONTENT_DISPOSITION);
  +
  +    /** Content type header */
  +    protected static final String CONTENT_TYPE = "Content-Type: ";
  +
  +    /** Content type header as a byte array */
  +    protected static final byte[] CONTENT_TYPE_BYTES = 
  +      HttpConstants.getAsciiBytes(CONTENT_TYPE);
  +
  +    /** Content charset */
  +    protected static final String CHARSET = "; charset=";
  +
  +    /** Content charset as a byte array */
  +    protected static final byte[] CHARSET_BYTES = 
  +      HttpConstants.getAsciiBytes(CHARSET);
  +
  +    /** Content type header */
  +    protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
  +
  +    /** Content type header as a byte array */
  +    protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES = 
  +      HttpConstants.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
  +
       /**
        * Return the boundary string.
        * @return the boundary string
  @@ -114,40 +153,29 @@
       }
       
       /**
  -     * Write the last boundary to the specified output stream
  -     * @param out The output stream
  -     * @throws IOException If an IO problem occurs.
  +     * Return the name of this part.
  +     * @return String The name.
        */
  -    public static void sendLastBoundary(OutputStream out)
  -    throws IOException {
  -        LOG.trace("enter sendLastBoundary(OutputStream out)");
  -        out.write(EXTRA_BYTES);
  -        out.write(BOUNDARY_BYTES);
  -        out.write(EXTRA_BYTES);
  -        out.write(CRLF_BYTES);
  -    }
  +    public abstract String getName();
       
       /**
  -     * Return the length of the last boundary string
  -     * 
  -     * @return int The length of the last boundary string
  -     * @throws IOException If an IO problem occurs
  +     * Return the content type of this part.
  +     * @return String The name.
        */
  -    public static int lengthOfLastBoundary() throws IOException {
  -        LOG.trace("enter lengthOfLastBoundary()");
  -        ByteArrayOutputStream out = new ByteArrayOutputStream();
  -        
  -        sendLastBoundary(out);
  -        
  -        return out.size();
  -    }
  -    
  +    public abstract String getContentType();
  +
       /**
  -     * Return the name of this part.
  +     * Return the character encoding of this part.
        * @return String The name.
        */
  -    public abstract String getName();
  -    
  +    public abstract String getCharSet();
  +
  +    /**
  +     * Return the transfer encoding of this part.
  +     * @return String The name.
  +     */
  +    public abstract String getTransferEncoding();
  +
       /**
        * Write the start to the specified output stream
        * @param out The output stream
  @@ -161,44 +189,58 @@
       }
       
       /**
  -     * Return the length of the starting data
  -     * @return int The length of the data
  -     * @throws IOException If an IO problem occurs
  +     * Write the content disposition header to the specified output stream
  +     * 
  +     * @param out The output stream
  +     * @throws IOException If an IO problem occurs.
        */
  -    protected int lengthOfStart() throws IOException {
  -        LOG.trace("enter lengthOfStart()");
  -        ByteArrayOutputStream out = new ByteArrayOutputStream();
  -        sendStart(out);
  -        return out.size();
  +    protected void sendDispositionHeader(OutputStream out) throws IOException {
  +        LOG.trace("enter sendDispositionHeader(OutputStream out)");
  +        out.write(CONTENT_DISPOSITION_BYTES);
  +        out.write(QUOTE_BYTES);
  +        out.write(HttpConstants.getAsciiBytes(getName()));
  +        out.write(QUOTE_BYTES);
       }
       
       /**
  -     * Write the header to the specified output stream
  +     * Write the content type header to the specified output stream
        * @param out The output stream
        * @throws IOException If an IO problem occurs.
        */
  -    protected void sendHeader(OutputStream out) throws IOException {
  -        LOG.trace("enter sendHeader(OutputStream out)");
  -        String contentDisposition = "Content-Disposition: form-data; name=\"" 
  -            + getName() + "\"";
  -    
  -        out.write(HttpConstants.getBytes(contentDisposition));
  + 
  +     protected void sendContentTypeHeader(OutputStream out) throws IOException {
  +        LOG.trace("enter sendContentTypeHeader(OutputStream out)");
  +        String contentType = getContentType();
  +        if (contentType != null) {
  +            out.write(CRLF_BYTES);
  +            out.write(CONTENT_TYPE_BYTES);
  +            out.write(HttpConstants.getAsciiBytes(contentType));
  +            String charSet = getCharSet();
  +            if (charSet != null) {
  +                out.write(CHARSET_BYTES);
  +                out.write(HttpConstants.getAsciiBytes(charSet));
  +            }
  +        }
       }
  -    
  +
       /**
  -     * Return the length of the header
  +     * Write the content transfer encoding header to the specified 
  +     * output stream
        * 
  -     * @return long The length.
  -     * @throws IOException If an IO problem occurs
  +     * @param out The output stream
  +     * @throws IOException If an IO problem occurs.
        */
  -    protected int lengthOfHeader() throws IOException {
  -        LOG.trace("enter lengthOfHeader()");
  -        ByteArrayOutputStream out = new ByteArrayOutputStream();
  -        sendHeader(out);
  -        return (out.size());
  + 
  +     protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
  +        LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
  +        String transferEncoding = getTransferEncoding();
  +        if (transferEncoding != null) {
  +            out.write(CRLF_BYTES);
  +            out.write(CONTENT_TRANSFER_ENCODING_BYTES);
  +            out.write(HttpConstants.getAsciiBytes(transferEncoding));
  +        }
       }
  -    
  -    
  +
       /**
        * Write the end of the header to the output stream
        * @param out The output stream
  @@ -211,20 +253,6 @@
       }
       
       /**
  -     * Return the length of the end of header
  -     * 
  -     * @return long The length.
  -     * @throws IOException If an IO problem occurs
  -     */
  -    protected int lengthOfEndOfHeader() throws IOException {
  -        LOG.trace("enter lengthOfEndOfHeader()");
  -        ByteArrayOutputStream out = new ByteArrayOutputStream();
  -        sendEndOfHeader(out);
  -        return out.size();
  -    }
  -    
  -    
  -    /**
        * Write the data to the specified output stream
        * @param out The output stream
        * @throws IOException If an IO problem occurs.
  @@ -250,50 +278,43 @@
       }
       
       /**
  -     * Return the length of the end data
  -     * 
  -     * @return long The length.
  -     * @throws IOException If an IO problem occurs
  -     */
  -    protected int lengthOfEnd() throws IOException {
  -        LOG.trace("enter lengthOfEnd()");
  -        ByteArrayOutputStream out = new ByteArrayOutputStream();
  -        sendEnd(out);
  -        return out.size();
  -    }
  -    
  -    /* The following 2 methods don't need to be final, but they DO need
  -     * to be overridden as a pair, and the only way to make sure of that
  -     * is to make sure they AREN'T overridden. 
  -     */
  -
  -    /**
        * Write all the data to the output stream.
  +     * If you override this method make sure to override 
  +     * #length() as well
  +     * 
        * @param out The output stream
        * @throws IOException If an IO problem occurs.
        */
  -    public final void send(OutputStream out) throws IOException {
  +    public void send(OutputStream out) throws IOException {
           LOG.trace("enter send(OutputStream out)");
           sendStart(out);
  -        sendHeader(out);
  +        sendDispositionHeader(out);
  +        sendContentTypeHeader(out);
  +        sendTransferEncodingHeader(out);
           sendEndOfHeader(out);
           sendData(out);
           sendEnd(out);
       }
  -    
  +
  +
       /**
        * Return the full length of all the data.
  +     * If you override this method make sure to override 
  +     * #send(OutputStream) as well
        * 
        * @return long The length.
        * @throws IOException If an IO problem occurs
        */
  -    public final long length() throws IOException {
  +    public long length() throws IOException {
           LOG.trace("enter length()");
  -        return lengthOfStart()
  -               + lengthOfHeader()
  -               + lengthOfEndOfHeader()
  -               + lengthOfData()
  -               + lengthOfEnd();
  +        ByteArrayOutputStream overhead = new ByteArrayOutputStream();
  +        sendStart(overhead);
  +        sendDispositionHeader(overhead);
  +        sendContentTypeHeader(overhead);
  +        sendTransferEncodingHeader(overhead);
  +        sendEndOfHeader(overhead);
  +        sendEnd(overhead);
  +        return overhead.size() + lengthOfData();
       }
   
       /**
  @@ -304,4 +325,52 @@
       public String toString() {
           return this.getName();
       }
  +
  +
  +    /**
  +     * Write all parts and the last boundary to the specified output stream
  +     * 
  +     * @param out The output stream
  +     * @param parts The array of parts to be sent
  +     * 
  +     * @throws IOException If an IO problem occurs.
  +     */
  +    public static void sendParts(OutputStream out, final Part[] parts)
  +    throws IOException {
  +        LOG.trace("enter sendParts(OutputStream out, Parts[])");
  +        if (parts == null) {
  +            throw new IllegalArgumentException("Parts may not be null"); 
  +        }
  +        for (int i = 0; i < parts.length; i++) {
  +            parts[i].send(out);
  +        }
  +        out.write(EXTRA_BYTES);
  +        out.write(BOUNDARY_BYTES);
  +        out.write(EXTRA_BYTES);
  +        out.write(CRLF_BYTES);
  +    }
  +
  +    /**
  +     * Return the total sum of all parts and that of the last boundary
  +     * 
  +     * @param parts The array of parts
  +     * 
  +     * @throws IOException If an IO problem occurs.
  +     */
  +    public static long getLengthOfParts(final Part[] parts)
  +    throws IOException {
  +        LOG.trace("getLengthOfParts(Parts[])");
  +        if (parts == null) {
  +            throw new IllegalArgumentException("Parts may not be null"); 
  +        }
  +        long total = 0;
  +        for (int i = 0; i < parts.length; i++) {
  +            total += parts[i].length();
  +        }
  +        total += EXTRA_BYTES.length;
  +        total += BOUNDARY_BYTES.length;
  +        total += EXTRA_BYTES.length;
  +        total += CRLF_BYTES.length;
  +        return total;
  +    }        
   }
  
  
  
  1.6       +79 -13    jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java
  
  Index: StringPart.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- StringPart.java	28 Jan 2003 22:25:31 -0000	1.5
  +++ StringPart.java	12 Feb 2003 12:30:44 -0000	1.6
  @@ -66,6 +66,8 @@
   import java.io.OutputStream;
   import java.io.IOException;
   import org.apache.commons.httpclient.HttpConstants;
  +import org.apache.commons.logging.Log;
  +import org.apache.commons.logging.LogFactory;
   
   /**
    * Simple string parameter for a multipart post
  @@ -73,26 +75,69 @@
    * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
    * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
  + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    *
    * @since 2.0
    */
   public class StringPart extends Part {
   
  +    /** Log object for this class. */
  +    private static final Log LOG = LogFactory.getLog(StringPart.class);
  +
  +    /** Default content encoding of string parameters. */
  +    public static final String DEFAULT_CONTENT_TYPE = "text/plain";
  +
  +    /** Default charset of string parameters*/
  +    public static final String DEFAULT_CHARSET = "US-ASCII";
  +
  +    /** Default transfer encoding of string parameters*/
  +    public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
  +
       /** Name of this StringPart. */
       private String name;
   
       /** Contents of this StringPart. */
  -    private String value;
  +    private byte[] content;
  +
  +    /** Charset of this StringPart. */
  +    private String charset;
       
       /**
        * Constructor.
        *
        * @param name The name of the part
        * @param value the string to post
  +     * @param charset the charset to be used to encode the string
        */
  -    public StringPart(String name, String value) {
  +    public StringPart(String name, String value, String charset) {
  +        LOG.trace("enter StringPart(String, String, String)");
  +        if (name == null) {
  +            throw new IllegalArgumentException("Name may not be null");
  +        }
           this.name = name;
  -        this.value = value;
  +        if (charset != null) {
  +            this.charset = charset;
  +        } else {
  +            this.charset = DEFAULT_CHARSET;
  +        }
  +        if (value == null) {
  +            throw new IllegalArgumentException("Value may not be null");
  +        }
  +        if (value.indexOf(0) != -1) {
  +            // See RFC 2048, 2.8. "8bit Data"
  +            throw new IllegalArgumentException("NULs may not be present in string parts");
  +        }
  +        this.content = HttpConstants.getContentBytes(value, this.charset);
  +    }
  +
  +    /**
  +     * Constructor.
  +     *
  +     * @param name The name of the part
  +     * @param value the string to post
  +     */
  +    public StringPart(String name, String value) {
  +        this(name, value, null);
       }
   
       /**
  @@ -102,15 +147,35 @@
       public String getName() {
           return name; 
       }
  -    
  +
       /**
  -     * Write the data to the specified output stream
  -     * @param out The output stream.
  -     * @throws IOException If an IO problem occurs
  -     * @see org.apache.commons.httpclient.methods.multipart.Part#sendData(OutputStream)
  +     * Return the content type of this part.
  +     * @return String The name.
  +     */
  +    public String getContentType() {
  +        return DEFAULT_CONTENT_TYPE;
  +    }
  +
  +    /**
  +     * Return the character encoding of this part.
  +     * @return String The name.
        */
  +    public String getCharSet() {
  +        return this.charset;
  +    }
  +
  +    /**
  +     * Return the transfer encoding of this part.
  +     * @return String The name.
  +     */
  +
  +    public String getTransferEncoding() {
  +        return DEFAULT_TRANSFER_ENCODING;
  +    }
  +    
       protected void sendData(OutputStream out) throws IOException {
  -        out.write(HttpConstants.getBytes(value));    
  +        LOG.trace("enter sendData(OutputStream)");
  +        out.write(this.content);
       }
       
       /**
  @@ -120,6 +185,7 @@
        * @see org.apache.commons.httpclient.methods.multipart.Part#lengthOfData()
        */
       protected long lengthOfData() throws IOException {
  -        return HttpConstants.getBytes(value).length;
  +        LOG.trace("enter lengthOfData()");
  +        return this.content.length;
       }
   }
  
  
  
  1.7       +5 -4      jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java
  
  Index: TestWebapp.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- TestWebapp.java	1 Feb 2003 16:10:48 -0000	1.6
  +++ TestWebapp.java	12 Feb 2003 12:30:44 -0000	1.7
  @@ -100,6 +100,7 @@
           suite.addTest(TestWebappBasicAuth.suite());
           suite.addTest(TestWebappCookie.suite());
           suite.addTest(TestWebappPostMethod.suite());
  +        suite.addTest(TestWebappMultiPostMethod.suite());
           suite.addTest(TestWebappNoncompliant.suite());
           return suite;
       }
  
  
  
  1.1                  jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java
  
  Index: TestWebappMultiPostMethod.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappMultiPostMethod.java,v 1.1 2003/02/12 12:30:44 olegk Exp $
   * $Revision: 1.1 $
   * $Date: 2003/02/12 12:30:44 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient;
  
  import junit.framework.*;
  import org.apache.commons.httpclient.methods.*;
  import org.apache.commons.httpclient.methods.multipart.*;
  import java.io.*;
  
  /**
   * Webapp tests specific to the MultiPostMethod.
   *
   * @author <a href="oleg@ural.ru">Oleg Kalnichevski</a>
   */
  public class TestWebappMultiPostMethod extends TestWebappBase {
  
      HttpClient httpClient; 
      final static String paramsUrl = "http://" + host + ":" + port
          + "/" + context + "/params";
      final static String bodyUrl = "http://" + host + ":" + port
          + "/" + context + "/body";
  
      public TestWebappMultiPostMethod(String testName) {
          super(testName);
      }
  
      public static Test suite() {
          TestSuite suite = new TestSuite(TestWebappMultiPostMethod.class);
          return suite;
      }
  
      public static void main(String args[]) {
          String[] testCaseName = { TestWebappMultiPostMethod.class.getName() };
          junit.textui.TestRunner.main(testCaseName);
      }
  
      public void setUp() {
          httpClient = new HttpClient();
      }
  
      // ------------------------------------------------------------------ Tests
      
      /**
       * Test that the body consisting of a string part can be posted.
       */
  
      public void testPostStringPart() throws Exception {
          MultipartPostMethod method = new MultipartPostMethod(bodyUrl);
          method.addPart(new StringPart("param", "Hello", "ISO-8859-1"));
  
          httpClient.executeMethod(method);
  
          assertEquals(200,method.getStatusCode());
          String body = method.getResponseBodyAsString();
          assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param\"") >= 0);
          assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >= 0);
          assertTrue(body.indexOf("Content-Transfer-Encoding: 8bit") >= 0);
          assertTrue(body.indexOf("Hello") >= 0);
      }
  
  
      /**
       * Test that the body consisting of a file part can be posted.
       */
      public void testPostFilePart() throws Exception {
          MultipartPostMethod method = new MultipartPostMethod(bodyUrl);
          byte[] content = new byte[] {'H', 'e', 'l', 'l', 'o' };
          method.addPart(
            new FilePart(
              "param1", 
              new ByteArrayPartSource("filename.txt", content), 
              "text/plain", 
              "ISO-8859-1"));
  
          httpClient.executeMethod(method);
  
          assertEquals(200,method.getStatusCode());
          String body = method.getResponseBodyAsString();
          assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param1\"; filename=\"filename.txt\"") >= 0);
          assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >= 0);
          assertTrue(body.indexOf("Content-Transfer-Encoding: binary") >= 0);
          assertTrue(body.indexOf("Hello") >= 0);
      }
  }
  
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org