You are viewing a plain text version of this content. The canonical link for it is here.
Posted to soap-dev@ws.apache.org by Wouter Cloetens <wc...@raleigh.ibm.com> on 2000/07/30 11:10:53 UTC

Multipart Mime support (85KB patch)

diff -u -P -r xml-soap/java/samples/mime/foo.txt xml-soap-mime/java/samples/mime/foo.txt
--- xml-soap/java/samples/mime/foo.txt	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/samples/mime/foo.txt	Tue Jul 25 19:12:16 2000
@@ -0,0 +1 @@
+bar!
diff -u -P -r xml-soap/java/samples/mime/mimesoaptest.cmd xml-soap-mime/java/samples/mime/mimesoaptest.cmd
--- xml-soap/java/samples/mime/mimesoaptest.cmd	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/samples/mime/mimesoaptest.cmd	Sun Jul 30 12:14:44 2000
@@ -0,0 +1,19 @@
+@echo off
+
+set URL=http://localhost/servlet/rpcrouter
+rem set URL=http://localhost/apache-soap/rpcrouter
+rem set URL=http://localhost:81/apache-soap/rpcrouter
+rem set URL="queue://dmzqm/esrv.proto2a.q?persistence=1&targetclient=1"
+rem set URL="topic://eat/my/shorts"
+
+rem set ACTION=sendFile
+rem set ACTION=loopFile
+set ACTION=getFileVector
+rem set ACTION=getFileArray
+
+set BASEDIR=/home/projects/xml-soap-mime/java/samples/mime
+rem set FILELIST=%BASEDIR%/foo.txt
+rem set FILELIST=%BASEDIR%/fields.gif
+set FILELIST=%BASEDIR%/fields.gif %BASEDIR%/foo.txt
+
+java -nojit mimesoaptest %URL% %ACTION% %FILELIST% 2>err | tee -a out
diff -u -P -r xml-soap/java/samples/mime/mimesoaptest.java xml-soap-mime/java/samples/mime/mimesoaptest.java
--- xml-soap/java/samples/mime/mimesoaptest.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/samples/mime/mimesoaptest.java	Sun Jul 30 12:25:34 2000
@@ -0,0 +1,149 @@
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import org.w3c.dom.*;
+import org.apache.soap.util.xml.*;
+import org.apache.soap.util.mime.*;
+import org.apache.soap.*;
+import org.apache.soap.transport.*;
+import org.apache.soap.encoding.*;
+import org.apache.soap.encoding.soapenc.*;
+import org.apache.soap.rpc.*;
+import com.ibm.eservice.transport.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+public class mimesoaptest
+{
+  public static void main(String[] args) throws Exception
+  {
+    String encodingStyleURI = Constants.NS_URI_SOAP_ENC;
+    URL url = new URL(args[0]);
+    SOAPMappingRegistry smr = new SOAPMappingRegistry();
+    BeanSerializer beanSer = new BeanSerializer();
+
+    try
+    {
+        // Build the call.
+        Call call = new Call();
+
+        call.setSOAPMappingRegistry(smr);
+        call.setTargetObjectURI("urn:mimetest");
+        call.setMethodName(args[1]);
+        call.setEncodingStyleURI(encodingStyleURI);
+
+        if(url.getProtocol().equals("queue") || url.getProtocol().equals("topic"))
+        {
+            SOAPJMSConnection qs = new SOAPMQJMSConnection();
+            call.setSOAPTransport(qs);
+        }
+
+        Vector params = new Vector();
+        if(args[1].equals("sendFile") || args[1].equals("loopFile"))
+            for(int i = 2; i < args.length; i++)
+            {
+                DataSource ds = new ByteArrayDataSource(new File(args[i]), null);
+                DataHandler dh = new DataHandler(ds);
+                params.addElement(new Parameter("addedfile", javax.activation.DataHandler.class, dh, null));
+            }
+        else
+        {
+            String s[] = new String[args.length - 2];
+            for(int i = 2; i < args.length; i++)
+            {
+                s[i - 2] = args[i];
+            }
+            params.addElement(new Parameter("filenames", String[].class, s, null));
+        }
+        call.setParams(params);
+
+        // System.out.println("Call: " + call);
+        // System.out.println("Context: " + RPCContext.getRPCContext());
+
+        // Invoke the call.
+        Response resp;
+    
+        try
+        {
+          resp = call.invoke(url, "");
+        }
+        catch (SOAPException e)
+        {
+          System.err.println("Caught SOAPException (" +
+                             e.getFaultCode() + "): " +
+                             e.getMessage());
+          e.printStackTrace();
+          return;
+        }
+    
+        // Check the response.
+        if (!resp.generatedFault())
+        {
+            Parameter ret = resp.getReturnValue();
+            if(ret == null)
+                System.out.println("No response.");
+            else
+            {
+                // System.out.println("Response: " + resp);
+
+                printObject(ret.getValue());
+            }
+        }
+        else
+        {
+          Fault fault = resp.getFault();
+    
+          System.err.println("Generated fault: ");
+          System.err.println ("  Fault Code   = " + fault.getFaultCode());
+          System.err.println ("  Fault String = " + fault.getFaultString());
+        }
+    }
+    catch(Exception e)
+    {
+        e.printStackTrace();
+    }
+  }
+
+  public static void printObject(Object obj)
+  {
+      if(obj instanceof DataHandler)
+      {
+          DataHandler dh = (DataHandler)obj;
+          Object o;
+          try
+          {
+              o = dh.getContent();
+          }
+          catch(IOException ioe)
+          {
+              o = ioe;
+          }
+          System.out.println("DataHandler, name=" + dh.getName()
+                             + ", type=" + dh.getContentType()
+                             + ", content: (" + o.getClass().getName()
+                             + ")\n" + o);
+          DataSource ds = dh.getDataSource();
+          String fname = "" + System.currentTimeMillis() + ".out";
+          System.out.println(" Writing to " + fname);
+          try
+          {
+              ByteArrayDataSource bds = new ByteArrayDataSource(ds.getInputStream(), dh.getContentType());
+              bds.writeTo(new FileOutputStream(fname));
+          }
+          catch(IOException ioe)
+          {
+              System.out.println(ioe);
+              ioe.printStackTrace(System.err);
+          }
+      }
+      else if(obj instanceof Object[])
+      {
+          Object[] s = (Object[])obj;
+          for(int i = 0; i < s.length; i++)
+              printObject(s[i]);
+      }
+      else
+          System.out.println(obj);
+  }
+}
diff -u -P -r xml-soap/java/samples/mime/mimetest.java xml-soap-mime/java/samples/mime/mimetest.java
--- xml-soap/java/samples/mime/mimetest.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/samples/mime/mimetest.java	Sun Jul 30 12:26:52 2000
@@ -0,0 +1,53 @@
+/**
+ * NOTE!!! This service is a *huge* security hole and is provided for
+ * demonstration purposes only.
+ */
+
+import java.io.*;
+import java.util.*;
+import javax.activation.*;
+import org.apache.soap.util.mime.*;
+
+public class mimetest
+{
+    public mimetest()
+    {
+    }
+
+    public static String sendFile(DataHandler dh) throws IOException
+    {
+        StringBuffer sb = new StringBuffer("Received attachment:\n");
+        sb.append("Content type: ").append(dh.getContentType());
+        sb.append("\nName: ").append(dh.getName());
+        Object o = dh.getContent();
+        sb.append("\nContent class: ").append(o.getClass().getName());
+        sb.append("\nContent: ").append(o.toString());
+        return sb.toString();
+    }
+
+    public static DataHandler loopFile(DataHandler dh) throws IOException
+    {
+        return dh;
+    }
+
+    public static Vector getFileVector(String[] fname) throws IOException
+    {
+        Vector res = new Vector();
+
+        for(int i = 0; i < fname.length; i++)
+        {
+            DataSource ds = new ByteArrayDataSource(new File(fname[i]), null);
+            res.addElement(new DataHandler(ds));
+        }
+        return res;
+    }
+
+    public static DataHandler[] getFileArray(String[] fname) throws IOException
+    {
+        Vector v = getFileVector(fname);
+        DataHandler[] dhs = new DataHandler[v.size()];
+        for(int i = 0; i < dhs.length; i++)
+            dhs[i] = (DataHandler)v.elementAt(i);
+        return dhs;
+    }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/encoding/soapenc/MimePartSerializer.java xml-soap-mime/java/src/org/apache/soap/encoding/soapenc/MimePartSerializer.java
--- xml-soap/java/src/org/apache/soap/encoding/soapenc/MimePartSerializer.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/src/org/apache/soap/encoding/soapenc/MimePartSerializer.java	Sun Jul 30 11:53:42 2000
@@ -0,0 +1,279 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2000 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 acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "SOAP" 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 name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * 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 and was
+ * originally based on software copyright (c) 2000, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.soap.encoding.soapenc;
+
+import java.beans.*;
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+import org.w3c.dom.*;
+import org.apache.soap.util.*;
+import org.apache.soap.util.xml.*;
+import org.apache.soap.util.mime.*;
+import org.apache.soap.*;
+import org.apache.soap.rpc.*;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * A <code>MimePartSerializer</code> can be used to serialize
+ * Java's InputStream, JavaMail's MimeBodyPart and
+ * Java Activation Framework's DataSource and DataHandler objects
+ * from/to multipart Mime attachments to the SOAP message.<p>
+ * Inside the SOAP message body, the reference looks like:<br>
+ * <code>&lt;attachment href="cid:foo"&gt;</code><br>
+ * where "foo" is the name of the Content-ID of the mime part.<p>
+ * The class always deserializes to a DataHandler, which provides
+ * an InputStream, a DataSource with a Content-Type, the content
+ * as an object, and allows to write the data to an OutputStream.
+ * 
+ * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
+ */
+public class MimePartSerializer implements Serializer, Deserializer
+{
+  private static final QName mimepartQName = new QName(Constants.NS_URI_SOAP_ENC, "attachment");
+
+  public void marshall(String inScopeEncStyle, Class javaType, Object src,
+                       Object context, Writer sink, NSStack nsStack,
+                       XMLJavaMappingRegistry xjmr)
+    throws IllegalArgumentException, IOException
+  {
+    nsStack.pushScope();
+
+    if ((src != null) &&
+        !(src instanceof InputStream) &&
+        !(src instanceof DataSource) &&
+        !(src instanceof MimeBodyPart) &&
+        !(src instanceof DataHandler))
+      throw new IllegalArgumentException("Tried to pass a '" +
+			src.getClass().toString() + "' to MimePartSerializer");
+
+    if (src == null)
+    {
+        SoapEncUtils.generateNullStructure(inScopeEncStyle, Object.class,
+                                           "attachment", sink, nsStack, xjmr);
+    }
+    else
+    {
+        // get a MimeBodyPart out of the various possible input types
+        DataSource ds = null;
+        DataHandler dh = null;
+        MimeBodyPart bp = null;
+        if(src instanceof InputStream)
+            ds = new ByteArrayDataSource((InputStream)src, "application/octet-stream");
+        else if(src instanceof DataSource)
+            ds = (DataSource)ds;
+        if(ds != null)
+            dh = new DataHandler(ds);
+        else if(src instanceof DataHandler)
+            dh = (DataHandler)src;
+        if(dh != null)
+        {
+            bp = new MimeBodyPart();
+            try
+            {
+                bp.setDataHandler(dh);
+            }
+            catch(MessagingException me)
+            {
+                throw new IllegalArgumentException("Invalid InputStream/DataSource/DataHandler: " + me);
+            }
+            // set some Mime headers. Assume that a passed MimeBodyPart
+            // already has these set to appropriate values
+            try
+            {
+                MimeType ctype = new MimeType(dh.getContentType());
+                bp.setHeader("Content-Type", ctype.toString());
+                if(dh.getDataSource() instanceof ByteArrayDataSource)
+                    bp.setHeader("Content-Length", String.valueOf(((ByteArrayDataSource)dh.getDataSource()).getSize()));
+                if(ctype.match("application/octet-stream") ||
+                   ctype.getPrimaryType().equals("image") ||
+                   ctype.getPrimaryType().equals("audio") ||
+                   ctype.getPrimaryType().equals("video"))
+                    bp.setHeader("Content-Transfer-Encoding", "8bit");
+            }
+            catch(MessagingException me)
+            {
+                throw new IllegalArgumentException("Invalid InputStream/DataSource/DataHandler metadata: " + me);
+            }
+            catch(MimeTypeParseException mtpe)
+            {
+                throw new IllegalArgumentException("Invalid Mime type \"" + dh.getContentType() + "\": " + mtpe);
+            }
+        }
+        else if(src instanceof MimeBodyPart)
+        {
+            bp = (MimeBodyPart)src;
+        }
+        // by now we must logically have a valid MimeBodyPart
+
+        // set a unique content-ID
+        String cid = null;
+        try
+        {
+            cid = bp.getContentID();
+        }
+        catch(MessagingException me)
+        {
+        }
+        if(cid == null)
+        {
+            cid = MimeUtils.getUniqueValue();
+            try
+            {
+                bp.setHeader("Content-ID", cid);
+            }
+            catch(MessagingException me)
+            {
+                throw new IllegalArgumentException("Could not set Content-ID: " + me);
+            }
+        }
+
+        // get multipart context
+        RPCContext ctx = RPCContext.getRPCContext();
+
+        // add the part to the context
+        try
+        {
+            ctx.addBodyPart(bp);
+        }
+        catch(MessagingException me)
+        {
+            throw new IllegalArgumentException("Could not add attachment: " + me);
+        }
+
+        // Now write the XML element.
+        sink.write('<' + context.toString());
+
+        // Get prefixes for the needed namespaces.
+        String xsiNSPrefix = nsStack.getPrefixFromURI(Constants.NS_URI_SCHEMA_XSI, sink);
+
+        String elementTypeNSPrefix = nsStack.getPrefixFromURI(mimepartQName.getNamespaceURI(), sink);
+        sink.write(' ' + xsiNSPrefix + ':' + Constants.ATTR_TYPE + "=\"" +
+                   elementTypeNSPrefix + ':' +
+                   mimepartQName.getLocalPart() + '"');
+
+        if(inScopeEncStyle == null
+           || !inScopeEncStyle.equals(Constants.NS_URI_SOAP_ENC))
+        {
+            // Determine the prefix associated with the NS_URI_SOAP_ENV
+            // namespace URI.
+            String soapEnvNSPrefix = nsStack.getPrefixFromURI(Constants.NS_URI_SOAP_ENV, sink);
+
+            sink.write(' ' + soapEnvNSPrefix + ':' +
+                       Constants.ATTR_ENCODING_STYLE + "=\"" +
+                       Constants.NS_URI_SOAP_ENC + '"');
+        }
+
+        // Write the reference.
+        sink.write(" href=\"cid:" + cid + '"');
+
+        sink.write("/>");
+    }
+
+    nsStack.popScope();
+  }
+
+  public Bean unmarshall(String inScopeEncStyle, QName elementType, Node src,
+                         XMLJavaMappingRegistry xjmr)
+    throws IllegalArgumentException
+  {
+      // get multipart context
+      RPCContext ctx = RPCContext.getRPCContext();
+
+      Element paramEl = (Element)src;
+
+      DataHandler dh = null;
+      if(!SoapEncUtils.isNull(paramEl))
+      {
+          String cid = paramEl.getAttribute("href");
+
+          try
+          {
+              MimeBodyPart bp = null;
+              if(cid != null && cid.startsWith("cid:"))
+              {
+                  cid = cid.substring(4);
+                  try
+                  {
+                      bp = (MimeBodyPart)ctx.getBodyPart(cid);
+                  }
+                  catch(NullPointerException npe)
+                  {
+                  }
+                  catch(ClassCastException cce)
+                  {
+                  }
+              }
+              if(bp == null)
+                  throw new IllegalArgumentException("Attachment tag \"" + paramEl.getTagName()
+                                                     + "\" refers to a Mime attachment with Content-ID \""
+                                                     + cid + "\" which could not be found.");
+              else
+                  dh = bp.getDataHandler();
+          }
+          catch(MessagingException me)
+          {
+              throw new IllegalArgumentException("Failed to read attachment for tag \"" + paramEl.getTagName()
+                                                 + "\" with Content-ID \"" + cid + "\": " + me);
+          }
+      }
+      return new Bean(javax.activation.DataHandler.class, dh);
+  }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/encoding/SOAPMappingRegistry.java xml-soap-mime/java/src/org/apache/soap/encoding/SOAPMappingRegistry.java
--- xml-soap/java/src/org/apache/soap/encoding/SOAPMappingRegistry.java	Thu Jul 27 06:13:54 2000
+++ xml-soap-mime/java/src/org/apache/soap/encoding/SOAPMappingRegistry.java	Sat Jul 29 20:23:46 2000
@@ -91,10 +91,12 @@
   private static final QName shortQName = new QName(schemaURI, "short");
   private static final QName byteQName = new QName(schemaURI, "byte");
   private static final QName arrayQName = new QName(soapEncURI, "Array");
+  private static final QName mimepartQName = new QName(soapEncURI, "attachment");
 
   private final ParameterSerializer paramSer = new ParameterSerializer();
   private final ArraySerializer arraySer = new ArraySerializer();
   private final VectorSerializer vectorSer = new VectorSerializer();
+  private final MimePartSerializer partSer = new MimePartSerializer();
   private final XMLParameterSerializer xmlParamSer =
     new XMLParameterSerializer();
 
@@ -119,6 +121,18 @@
     mapTypes(Constants.NS_URI_LITERAL_XML, RPCConstants.Q_ELEM_PARAMETER,
              Parameter.class, xmlParamSer, xmlParamSer);
 
+    // Register inputstreams, data handlers, data sources as Mime parts.
+    // The order is important! When deserialing an array of these objects,
+    // DataHandler must be the target class.
+    mapTypes(soapEncURI, mimepartQName, javax.mail.internet.MimeBodyPart.class,
+             partSer, partSer);
+    mapTypes(soapEncURI, mimepartQName, java.io.InputStream.class,
+             partSer, partSer);
+    mapTypes(soapEncURI, mimepartQName, javax.activation.DataSource.class,
+             partSer, partSer);
+    mapTypes(soapEncURI, mimepartQName, javax.activation.DataHandler.class,
+             partSer, partSer);
+
     try {
       Class XMISerializer = 
 	Class.forName("org.apache.soap.util.xml.XMISerializer");
@@ -137,6 +151,7 @@
     } catch (IllegalAccessException iae) {
     } catch (InstantiationException ie) {
     } catch (ClassNotFoundException cnfe) {
+    } catch (NoClassDefFoundError ncdfe) {
 
       // If the class can't be loaded, continue without it...
 
diff -u -P -r xml-soap/java/src/org/apache/soap/rpc/RPCContext.java xml-soap-mime/java/src/org/apache/soap/rpc/RPCContext.java
--- xml-soap/java/src/org/apache/soap/rpc/RPCContext.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/src/org/apache/soap/rpc/RPCContext.java	Sat Jul 29 16:29:50 2000
@@ -0,0 +1,318 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2000 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 acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "SOAP" 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 name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * 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 and was
+ * originally based on software copyright (c) 2000, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.soap.rpc;
+
+import java.io.*;
+import java.util.*;
+import org.apache.soap.util.*;
+import org.apache.soap.*;
+import org.apache.soap.encoding.*;
+import org.apache.soap.server.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/**
+ * <code>Call</code> and <code>Response</code> is done here.
+ *
+ * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
+ */
+public class RPCContext
+{
+    protected MimeMultipart parts;
+    protected static Hashtable contextTable = new Hashtable();
+
+    // TODO: put this in Constants
+    public static final String HEADERVAL_MULTIPART_CONTENT_SUBTYPE = "related";
+
+    private static final String[] ignoreHeaders = {"Message-ID"};
+
+    /**
+     * Get the current RPCMessage context (temp kludge).<p>
+     * The structure of the code currently doesn't expose any context
+     * to the [de]serializers. The Mime parts, currently embedded in
+     * this calss, need to be
+     * accessible to the [de]serializers. Until this situation is
+     * fundamentally resolved, a kludgy mechanism is introduced here,
+     * allowing any class to extract a reference to the current
+     * RPCContext using the current thread.<p>
+     * This mechanism assumes that only one call or response is being
+     * handled at any time, and only within the context of one thread.
+     */
+    public static RPCContext getRPCContext()
+    {
+        Thread me = Thread.currentThread();
+        Object o = contextTable.get(me);
+        if(o != null && o instanceof RPCContext)
+            return (RPCContext)o;
+        else
+        {
+            RPCContext ctx = new RPCContext();
+            ctx.setRPCContext();
+            return ctx;
+        }
+    }
+
+    public void setRPCContext()
+    {
+        Thread me = Thread.currentThread();
+        contextTable.put(me, this);
+    }
+
+    public RPCContext()
+    {
+        parts = null;
+    }
+
+    public void readMultipart(DataSource ds) throws MessagingException
+    {
+        parts = new MimeMultipart(ds);
+    }
+
+    /**
+     * Get the specified Part.  Parts are numbered starting at 0.
+     *
+     * @param index	the index of the desired Part
+     * @return		the Part
+     * @exception         IndexOutOfBoundsException if no such Part exists
+     */
+    public BodyPart getBodyPart(int index) throws IndexOutOfBoundsException
+    {
+      /* Actually, this method never throws a MessagingException. In case a
+       * future implementation does, catch it and throw an IndexOutOfBoundsException
+       */
+        if (parts == null)
+        {
+            throw new IndexOutOfBoundsException();
+        }
+        try
+        {
+            return parts.getBodyPart(index);
+        }
+        catch (MessagingException me)
+        {
+            me.printStackTrace();
+            throw new IndexOutOfBoundsException(me.getMessage());
+        }
+    }
+
+    /**
+     * Get the Mimepart referred to by the given ContentID (CID).
+     * Returns null if the part is not found.
+     *
+     * @param  CID      the ContentID of the desired part
+     * @return          the Part
+     */
+    public BodyPart getBodyPart(String CID)
+    {
+        if (parts == null)
+        {
+            return null;
+        }
+        try
+        {
+            return parts.getBodyPart(CID);
+        }
+        catch (MessagingException me)
+        {
+            return null;
+        }
+        catch (NullPointerException npe)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Adds a Part.  The BodyPart is appended to
+     * the list of existing Parts.
+     *
+     * @param  part  The Part to be appended
+     * @exception    MessagingException
+     * @exception    IllegalWriteException if the underlying
+     *		     implementation does not support modification
+     *		     of existing values
+     */
+    public void addBodyPart(BodyPart part) throws MessagingException
+    {
+        if (parts == null)
+            parts = new MimeMultipart(HEADERVAL_MULTIPART_CONTENT_SUBTYPE);
+        parts.addBodyPart(part);
+    }
+
+    /**
+     * Adds a BodyPart at position <code>index</code>.
+     * If <code>index</code> is not the last one in the list,
+     * the subsequent parts are shifted up. If <code>index</code>
+     * is larger than the number of parts present, the
+     * BodyPart is appended to the end.
+     *
+     * @param           part  The BodyPart to be inserted
+     * @param           index Location where to insert the part
+     * @exception       MessagingException
+     * @exception       IllegalWriteException if the underlying
+     *                  implementation does not support modification
+     *		        of existing values
+     */
+    public void addBodyPart(BodyPart part, int index) throws MessagingException
+    {
+        if (parts == null)
+            parts = new MimeMultipart(HEADERVAL_MULTIPART_CONTENT_SUBTYPE);
+        parts.addBodyPart(part, index);
+    }
+
+    /**
+     * Set the MultiPart Mime subtype. This method should be invoked only on a new
+     * MimeMultipart object created by the client. The default subtype
+     * of such a multipart object is "related".<p>
+     *
+     * @param           subtype		Subtype
+     * @exception       MessagingException
+     */
+    public void setSubType(String subtype) throws MessagingException
+    {
+        if (parts == null)
+            parts = new MimeMultipart(subtype);
+        else
+            parts.setSubType(subtype);
+    }
+
+    /**
+     * Return the number of enclosed BodyPart objects.
+     *
+     * @return		number of parts
+     */
+    public int getCount() throws MessagingException
+    {
+        if (parts == null)
+            return 0;
+        else
+            return parts.getCount();
+    }
+
+    /**
+     * Return the content-type
+     *
+     * @return          content type of the Mime multipart
+     */
+    public String getContentType()
+    {
+        if (parts == null)
+            return null;
+        else
+            return parts.getContentType();
+    }
+
+    /**
+     * Encode the whole multipart and write to an OutputStream
+     *
+     * @param       os        stream to write to
+     * @exception   IOException
+     * @exception   MessagingException
+     */
+    public void writeTo(OutputStream os) throws IOException, MessagingException
+    {
+        Session session = Session.getDefaultInstance(new Properties(), null);
+        MimeMessage msg = new MimeMessage(session);
+        msg.setContent(parts);
+        msg.saveChanges();
+        msg.writeTo(os, ignoreHeaders);
+    }
+
+    public String toString()
+    {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+
+        pw.print("[Parts={");
+
+        if (parts != null)
+        {
+            try
+            {
+                for (int i = 0; i < getCount(); i++)
+                {
+                    if (i > 0)
+                    {
+                        pw.print(", ");
+                    }
+
+                    BodyPart bp = getBodyPart(i);
+                    if(bp instanceof MimeBodyPart)
+                    {
+                        MimeBodyPart mbp = (MimeBodyPart)bp;
+                        pw.print("[cid:" + mbp.getContentID()
+                                 + " type: " + mbp.getContentType()
+                                 + " enc: " + mbp.getEncoding() + "]");
+                    }
+                    else
+                        pw.print("[bodypart]");
+                }
+            }
+            catch(MessagingException me)
+            {
+                me.printStackTrace();
+            }
+        }
+
+        pw.print("}]");
+
+        return sw.toString();
+    }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/server/ServiceManagerClient.java xml-soap-mime/java/src/org/apache/soap/server/ServiceManagerClient.java
--- xml-soap/java/src/org/apache/soap/server/ServiceManagerClient.java	Thu Jul 20 12:24:52 2000
+++ xml-soap-mime/java/src/org/apache/soap/server/ServiceManagerClient.java	Sat Jul 29 17:20:16 2000
@@ -154,7 +154,7 @@
 			ServiceManagerClient.class.getName () +
 			" url operation arguments");
     System.err.println ("where");
-    System.err.println ("\turl is the IBM-SOAP router's URL whose services" +
+    System.err.println ("\turl is the Apache-SOAP router's URL whose services" +
 			" are managed");
     System.err.println ("\toperation and arguments are:");
     System.err.println ("\t\tdeploy deployment-descriptor-file.xml");
diff -u -P -r xml-soap/java/src/org/apache/soap/transport/http/rpcrouter.java xml-soap-mime/java/src/org/apache/soap/transport/http/rpcrouter.java
--- xml-soap/java/src/org/apache/soap/transport/http/rpcrouter.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/src/org/apache/soap/transport/http/rpcrouter.java	Sun Jul 30 11:57:14 2000
@@ -0,0 +1,479 @@
+package org.apache.soap.transport.http;
+
+import java.io.*;
+import java.util.*;
+import java.beans.Beans;
+import java.lang.reflect.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import org.w3c.dom.*;
+
+import org.apache.soap.util.Bean;
+import org.apache.soap.util.MethodUtils;
+import org.apache.soap.util.IOUtils;
+import org.apache.soap.util.StringUtils;
+import org.apache.soap.util.xml.XMLParserLiaison;
+import org.apache.soap.util.xml.XercesParserLiaison;
+import org.apache.soap.util.mime.*;
+import org.apache.soap.*;
+import org.apache.soap.encoding.*;
+import org.apache.soap.rpc.*;
+import org.apache.soap.server.*;
+import org.apache.soap.server.ServiceManager;
+import org.apache.soap.server.RPCRouter;
+
+public class rpcrouter extends HttpServlet {
+    public void service(HttpServletRequest request,HttpServletResponse response)
+        throws IOException, ServletException
+        {
+            try
+            {
+                OutputStream outStream = response.getOutputStream ();
+                PrintWriter out = new PrintWriter (new OutputStreamWriter (outStream));
+
+                // get servlet init parameters to initialize bean properties
+                Properties p = new java.util.Properties();
+                Enumeration e = request.getParameterNames();
+                while (e.hasMoreElements()) {
+                    String name = (String) e.nextElement();
+                    p.put(name, request.getParameter(name));
+                }
+
+                // get ServiceManager bean
+                org.apache.soap.server.ServiceManager serviceManager =
+                    (ServiceManager)request.getAttribute("serviceManager");
+                if (serviceManager == null) {
+                    try {
+                        serviceManager =
+                            (ServiceManager) Beans.instantiate(this.getClass().getClassLoader(),
+                                                               "org.apache.soap.server.ServiceManager");
+                    } catch (Exception ex) {
+                        throw new ServletException("Can't create BEAN of class org.apache.soap.server.ServiceManager: "+ ex.getMessage());
+                    }
+                    //request.setAttribute("serviceManager", serviceManager);
+                    ((com.sun.server.http.HttpServiceRequest)request).setAttribute("serviceManager", serviceManager);
+                }
+                // com.ibm.servlet.util.BeansUtil.setProperties(serviceManager, p);
+                com.sun.server.util.BeansUtil.setProperties(serviceManager, p);
+
+                // get RPCRouter bean
+                org.apache.soap.server.RPCRouter rpcRouter= (org.apache.soap.server.RPCRouter)
+                    request.getAttribute("rpcRouter");
+                if (rpcRouter == null) {
+                    try {
+                        rpcRouter = (org.apache.soap.server.RPCRouter) Beans.instantiate(this.getClass().getClassLoader(), "org.apache.soap.server.RPCRouter");
+                    } catch (Exception ex) {
+                        throw new ServletException("Can't create BEAN of class org.apache.soap.server.RPCRouter: "+ ex.getMessage());
+                    }
+                    //request.setAttribute("rpcRouter", rpcRouter);
+                    ((com.sun.server.http.HttpServiceRequest)request).setAttribute("rpcRouter", rpcRouter);
+                }
+                // com.ibm.servlet.util.BeansUtil.setProperties(rpcRouter, p);
+                com.sun.server.util.BeansUtil.setProperties(rpcRouter, p);
+
+                rpcRouter.setServiceManager (serviceManager);
+
+                Response resp = null;
+                String targetID = null;
+
+                // Query the relevant header fields.
+                String requestMethod = request.getMethod ();
+                String contentType = request.getContentType ();
+                int contentLength = request.getContentLength ();
+
+                Call call = null;
+                RPCContext ctx = new RPCContext();
+                try
+                {
+                    if (requestMethod == null ||
+                        !requestMethod.equals (Constants.HEADER_POST))
+                    {
+                        throw new SOAPException (Constants.FAULT_CODE_PROTOCOL,
+                                                 "Can only respond to '" +
+                                     Constants.HEADER_POST + "' requests.");
+                    }
+                    else if (contentType == null
+                             || !(contentType.equals (Constants.HEADERVAL_CONTENT_TYPE)
+                                  || contentType.startsWith ("multipart/related")))
+                    {
+                        throw new SOAPException (Constants.FAULT_CODE_PROTOCOL,
+                                                 "Content type must be: '" +
+                                                 Constants.HEADERVAL_CONTENT_TYPE + "'.");
+                    }
+                    else if (contentLength < 0)
+                    {
+                        throw new SOAPException (Constants.FAULT_CODE_PROTOCOL,
+                                                 "Content length must be specified.");
+                    }
+                    else
+                    {
+                        System.err.println (">>(" + new Date() +
+                                            ") Processing SOAP request...");
+
+                        Reader payloadReader;
+                        if(contentType.startsWith("multipart/related"))
+                        {
+                            ServletInputStream stream = request.getInputStream();
+                            byte[] buf = new byte[contentLength];
+                            int totNRead = 0, nRead;
+                            do
+                                nRead = stream.read(buf, totNRead, buf.length - totNRead);
+                            while (nRead >= 0 && (totNRead += nRead) < buf.length);
+                            if (totNRead < buf.length)
+                                throw new SOAPException (Constants.FAULT_CODE_PROTOCOL,
+                                                         "Premature end of stream. Data is truncated. Read "
+                                                         + totNRead + " bytes successfully, expected " + contentLength);
+
+                            ByteArrayDataSource ds = new ByteArrayDataSource(buf, contentType);
+
+                            ctx.readMultipart(ds);
+                            //      if (mmp.getCount() < 1 ||
+                            BodyPart soapPart = ctx.getBodyPart(0);
+                            ContentType ctype = new ContentType(soapPart.getContentType());
+                            String charset = ctype.getParameter("charset");
+                            if (charset == null || charset.equals(""))
+                                charset = "iso-8859-1";
+                            if (!(Constants.HEADERVAL_CONTENT_TYPE).equals(ctype.getBaseType()))
+                            {
+                                throw new SOAPException (Constants.FAULT_CODE_PROTOCOL,
+                                                         "Content type of first MIME part must be: '" +
+                                                         Constants.HEADERVAL_CONTENT_TYPE + "'.");
+                            }
+                            payloadReader = new InputStreamReader((InputStream)(soapPart.getContent()), charset);
+                        }
+                        else
+                        {
+                            Reader requestReader = request.getReader ();
+                            char[] payload       = new char[contentLength];
+                            int    offset        = 0;
+
+                            while (offset < contentLength) {
+                                offset += requestReader.read (payload, offset, contentLength - offset);
+                            }
+                            payloadReader = new CharArrayReader (payload);
+                        }
+
+                        ctx.setRPCContext();
+                        // System.out.println("Read request content, context = " + ctx);
+
+                        // Parse the incoming request stream.
+                        org.apache.soap.util.xml.XMLParserLiaison xpl = new XercesParserLiaison ();
+                        Document callDoc = xpl.read("- SOAP HTTP RPC Call Envelope -",
+                                                    payloadReader);
+                        Element payloadEl = null;
+
+                        if (callDoc == null) {
+                            throw new SOAPException(Constants.FAULT_CODE_PROTOCOL,
+                                                    "DOM parsing error.");
+                        }
+
+                        // get the call out
+                        try {
+                            payloadEl = callDoc.getDocumentElement ();
+                            Envelope callEnv = Envelope.unmarshall (payloadEl);
+                            call = rpcRouter.extractCallFromEnvelope (callEnv);
+                            targetID = call.getTargetObjectURI ();
+                        } catch (IllegalArgumentException iae) {
+                            String msg = iae.getMessage ();
+                            String faultCode = (msg != null
+                                                && msg.equals(Constants.ERR_MSG_VERSION_MISMATCH))
+                                ? Constants.FAULT_CODE_VERSION_MISMATCH
+                                : Constants.FAULT_CODE_CLIENT;
+
+                            throw new SOAPException (faultCode, msg, iae);
+                        }
+
+                        // call on a valid method name?
+                        if (!rpcRouter.validCall (call)) {
+                            throw new SOAPException (Constants.FAULT_CODE_SERVER,
+                                                     "Method '" + call.getMethodName () +
+                                                     "' is not supported.");
+                        }
+
+                        // lifecycle stuff of target object: what's the class to instantiate?
+                        DeploymentDescriptor dd = serviceManager.query (targetID);
+                        int scope = dd.getScope ();
+                        byte providerType = dd.getProviderType ();
+                        String className;
+                        Object targetObject = null;
+                        if (providerType == DeploymentDescriptor.PROVIDER_JAVA) {
+                            className = dd.getProviderClass ();
+                        } else {
+                            // for scripts, we need a new BSF manager basically
+                            className = "com.ibm.bsf.BSFManager";
+                        }
+
+                        // determine the scope and lock object to use to manage the lifecycle
+                        // of the service providing object
+                        // NOTE: removed for JSP 0.9 support
+                        // int[] iScopes = {PageContext.PAGE_SCOPE, PageContext.REQUEST_SCOPE,
+                        //			    PageContext.SESSION_SCOPE,
+                        //			    PageContext.APPLICATION_SCOPE};
+                        // int iScope = iScopes[scope];
+                        // Object[] scopeLocks = {pageContext, request, session, application};
+                        // Object scopeLock = scopeLocks[scope];
+
+                        boolean freshObject = false;
+
+                        // find the target object on which the requested method should
+                        // be invoked
+                        if (targetID.equals (ServerConstants.SERVICE_MANAGER_SERVICE_NAME)) {
+                            targetObject = serviceManager;
+                        } else {
+                            // locate (or create) the target object and invoke the method
+                            // synchronized (scopeLock) {
+                            // NOTE: only request scope supported
+                            synchronized (request) {
+                                // targetObject = pageContext.getAttribute (targetID, iScope);
+                                targetObject = ((com.sun.server.http.HttpServiceRequest)request).getAttribute (targetID);
+                                //targetObject = request.getAttribute (targetID);
+                                if (targetObject == null) {
+                                    try {
+                                        Class c = Class.forName (className);
+                                        if (dd.getIsStatic ()) {
+                                            targetObject = c;
+                                        } else {
+                                            targetObject = c.newInstance ();
+                                        }
+                                        freshObject = true;
+                                    } catch (Exception ex) {
+                                        String msg;
+                                        if (providerType == DeploymentDescriptor.PROVIDER_JAVA) {
+                                            msg = "Unable to resolve target object: " + ex.getMessage ();
+                                        } else {
+                                            msg = "Unable to load BSF: script services not available " +
+                                                "without BSF: " + ex.getMessage ();
+                                        }
+                                        throw new SOAPException (RPCConstants.FAULT_CODE_SERVER_BAD_TARGET_OBJECT_URI,
+                                                                 msg, ex);
+                                    }
+                                }
+                                // pageContext.setAttribute (targetID, targetObject, iScope);
+                                ((com.sun.server.http.HttpServiceRequest)request).setAttribute (targetID, targetObject);
+                                //request.setAttribute (targetID, targetObject);
+                            }
+                        }
+
+                        // if script provider type and first time to it, then load and
+                        // exec the script
+                        if (providerType != DeploymentDescriptor.PROVIDER_JAVA &&
+                            freshObject) {
+                            // find the class that provides the BSF services (done
+                            // this way via reflection to avoid a static dependency on BSF)
+                            Class bc = Class.forName ("org.apache.soap.server.InvokeBSF");
+
+                            // get the script string to exec
+                            String script = dd.getScriptFilenameOrString ();
+                            if (providerType == DeploymentDescriptor.PROVIDER_SCRIPT_FILE) {
+                                String fileName = getServletContext().getRealPath (script);
+                                script = IOUtils.getStringFromReader (new FileReader (fileName));
+                            }
+
+                            // exec it
+                            Class[] sig = {DeploymentDescriptor.class,
+                            Object.class,
+                            String.class};
+                            Method m = MethodUtils.getMethod (bc, "init", sig, true);
+                            m.invoke (null, new Object[] {dd, targetObject, script});
+                        }
+
+                        // build a new context for the response. The call has already been rebuilt
+                        // and its parameters hold references to DataHandlers, so we can discard
+                        // the MimeMultipart and BodyPart objects
+                        ctx = new RPCContext();
+                        ctx.setRPCContext();
+
+                        // invoke the method on the target object
+                        resp = rpcRouter.invoke (call, targetObject);
+                    }
+                }
+                catch (SOAPException se)
+                {
+                    // Wipe context for fault response, so it's not wrapped as a multipart
+                    // containing the source's parts in the response.
+                    ctx = new RPCContext();
+                    ctx.setRPCContext();
+
+                    Fault fault = new Fault ();
+                    String faultCode = se.getFaultCode ();
+                    String faultString = se.getMessage ();
+                    boolean returnSOAPResponse = true;
+
+                    if (faultCode == null || faultCode.startsWith (Constants.FAULT_CODE_SERVER))
+                    {
+                        response.setStatus (ServerConstants.SC_INTERNAL_SERVER_ERROR);
+                    }
+                    else if (faultCode.startsWith (Constants.FAULT_CODE_CLIENT))
+                    {
+                        response.setStatus (ServerConstants.SC_BAD_REQUEST);
+                    }
+                    else if (faultCode.startsWith (Constants.FAULT_CODE_PROTOCOL))
+                    {
+                        response.setStatus (ServerConstants.SC_BAD_REQUEST);
+                        returnSOAPResponse = false;
+                    }
+
+                    if (returnSOAPResponse)
+                    {
+                        fault.setFaultCode (faultCode);
+                        fault.setFaultString (faultString);
+                        fault.setFaultActorURI (request.getRequestURI ());
+
+                        resp = new Response (null,
+                                             null,
+                                             fault,
+                                             null,
+                                             null,
+                                             null);
+                    }
+                    else
+                    {
+                        out.println (faultString);
+                    }
+                }
+                catch (Throwable t)
+                {
+                    t.printStackTrace ();
+                    t.printStackTrace (new PrintWriter (out));
+                }
+
+                // Send it out.
+                if (resp != null)
+                {
+                    try
+                    {
+                        try
+                        {
+                            // Build an envelope containing the response.
+                            Envelope respEnvelope = resp.buildEnvelope ();
+                            SOAPMappingRegistry smr = (call != null
+                                                       ? call.getSOAPMappingRegistry ()
+                                                       : new SOAPMappingRegistry());
+
+                            StringWriter sw = new StringWriter ();
+                            respEnvelope.marshall (sw, smr);
+                            if (ctx.getCount() == 0)
+                            {
+                                response.setContentType (Constants.HEADERVAL_CONTENT_TYPE);
+                                out.print (sw.toString ());
+                            }
+                            else
+                            {
+                                // Add the SOAP response part.
+                                ByteArrayDataSource ds = new ByteArrayDataSource (sw.toString (),
+                                                                                  Constants.HEADERVAL_CONTENT_TYPE);
+                                DataHandler dh = new DataHandler(ds);
+                                MimeBodyPart bp = new MimeBodyPart();
+                                bp.setDataHandler (dh);
+                                bp.setHeader ("Content-Length", String.valueOf(ds.getSize()));
+                                bp.setHeader("Content-ID", MimeUtils.getUniqueValue());
+                                ctx.addBodyPart (bp, 0);
+
+                                // Print the whole response to a byte array.
+                                ByteArrayOutputStream multipartPayload = new ByteArrayOutputStream();
+                                ctx.writeTo (multipartPayload);
+                                byte[] content = multipartPayload.toByteArray();
+
+                                // Now strip off the headers. (Grmbl).
+                                int i;
+                                StringBuffer namebuf = new StringBuffer();
+                                StringBuffer valuebuf = new StringBuffer();
+                                boolean parsingName = true;
+                                for (i = 0; i < content.length; i++)
+                                {
+                                    if (content[i] == '\n')
+                                    {
+                                        if (namebuf.length() == 0)
+                                            break;
+                                        String name = namebuf.toString();
+                                        if (name.equals("Content-Type"))
+                                            response.setContentType(valuebuf.toString());
+                                        else
+                                            response.setHeader(name, valuebuf.toString());
+                                        namebuf = new StringBuffer();
+                                        valuebuf = new StringBuffer();
+                                        parsingName = true;
+                                    }
+                                    else if (content[i] != '\r')
+                                    {
+                                        if (parsingName)
+                                        {
+                                            if (content[i] == ':')
+                                            {
+                                                parsingName = false;
+                                                i++;
+                                            }
+                                            else
+                                                namebuf.append((char)content[i]);
+                                        }
+                                        else
+                                            valuebuf.append((char)content[i]);
+                                    }
+                                }
+                                outStream.write(content, i, content.length - i);
+                                outStream.flush();
+                            }
+
+                            out.println (StringUtils.lineSeparator);
+                        }
+                        catch (IllegalArgumentException iae)
+                        {
+                            throw new SOAPException (Constants.FAULT_CODE_SERVER, iae.getMessage(), iae);
+                        }
+                        catch (IOException ioe)
+                        {
+                            throw new SOAPException (Constants.FAULT_CODE_SERVER, ioe.getMessage(), ioe);
+                        }
+                        catch (MessagingException me)
+                        {
+                            throw new SOAPException (Constants.FAULT_CODE_SERVER, me.getMessage(), me);
+                        }
+                    }
+                    catch (SOAPException se)
+                    {
+                        // Wipe context for fault response, so it's not wrapped as a multipart
+                        // containing the source's parts in the response.
+                        ctx = new RPCContext();
+                        ctx.setRPCContext();
+
+                        Fault fault = new Fault ();
+                        String faultCode = se.getFaultCode ();
+                        String faultString = se.getMessage ();
+
+                        if (faultCode == null || faultCode.startsWith (Constants.FAULT_CODE_SERVER))
+                        {
+                            response.setStatus (ServerConstants.SC_INTERNAL_SERVER_ERROR);
+                        }
+                        else if (faultCode.startsWith (Constants.FAULT_CODE_CLIENT))
+                        {
+                            response.setStatus (ServerConstants.SC_BAD_REQUEST);
+                        }
+                        else if (faultCode.startsWith (Constants.FAULT_CODE_PROTOCOL))
+                        {
+                            response.setStatus (ServerConstants.SC_BAD_REQUEST);
+                        }
+
+                        out.println (faultString);
+                    }
+                    catch (Throwable t)
+                    {
+                        t.printStackTrace ();
+                        t.printStackTrace (new PrintWriter (out));
+                    }
+                }
+                out.flush ();
+            }
+            catch(Throwable t)
+            {
+                if (t.getMessage() == null)
+                    throw new ServletException("Unhandled exception caught in service method.");
+                else
+                    throw new ServletException("Unhandled exception caught in service method: " + t.getMessage());
+            }
+        }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java xml-soap-mime/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java
--- xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java	Tue May 30 05:24:14 2000
+++ xml-soap-mime/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java	Sun Jul 30 11:56:36 2000
@@ -63,9 +63,14 @@
 import org.w3c.dom.*;
 import org.apache.soap.util.net.*;
 import org.apache.soap.util.xml.*;
+import org.apache.soap.util.mime.*;
 import org.apache.soap.*;
 import org.apache.soap.encoding.*;
 import org.apache.soap.transport.*;
+import org.apache.soap.rpc.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
 
 /**
  * <code>SOAPHTTPConnection</code> is an implementation of the
@@ -105,9 +110,42 @@
       headers.put (Constants.HEADER_SOAP_ACTION, 
 		   (action != null) ? ('\"' + action + '\"') : "");
 
-      HTTPUtils.Response response = HTTPUtils.post (sendTo, headers,
-               Constants.HEADERVAL_CONTENT_TYPE,
-               payloadSW.toString ());
+      HTTPUtils.Response response;
+      try
+      {
+          RPCContext ctx = RPCContext.getRPCContext();
+          if (ctx.getCount() == 0)
+          {
+              response = HTTPUtils.post (sendTo, headers,
+                                         Constants.HEADERVAL_CONTENT_TYPE,
+                                         payloadSW.toString ());
+          }
+          else
+          {
+              // StringWriter multipartPayloadSW = new StringWriter();
+              ByteArrayOutputStream multipartPayloadSW = new ByteArrayOutputStream();
+              ByteArrayDataSource ds = new ByteArrayDataSource (payloadSW.toString(),
+                                                                Constants.HEADERVAL_CONTENT_TYPE);
+              DataHandler dh = new DataHandler(ds);
+              MimeBodyPart bp = new MimeBodyPart();
+              bp.setDataHandler (dh);
+              bp.setHeader ("Content-Length", String.valueOf(ds.getSize()));
+              bp.setHeader("Content-ID", MimeUtils.getUniqueValue());
+              ctx.addBodyPart (bp, 0);
+              ctx.writeTo (multipartPayloadSW);
+              response = HTTPUtils.post (sendTo, headers,
+                                         null,
+                                         multipartPayloadSW.toByteArray());
+          }
+      }
+      catch (MessagingException me)
+      {
+          throw new IOException ("Failed to encode mime multipart: " + me);
+      }
+      catch (UnsupportedEncodingException uee)
+      {
+          throw new IOException ("Failed to encode mime multipart: " + uee);
+      }
 
       responseReader = response.content;
       responseHeaders = response.headers;
diff -u -P -r xml-soap/java/src/org/apache/soap/util/mime/ByteArrayDataSource.java xml-soap-mime/java/src/org/apache/soap/util/mime/ByteArrayDataSource.java
--- xml-soap/java/src/org/apache/soap/util/mime/ByteArrayDataSource.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/src/org/apache/soap/util/mime/ByteArrayDataSource.java	Sun Jul 30 11:43:20 2000
@@ -0,0 +1,212 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2000 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 acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "SOAP" 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 name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * 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 and was
+ * originally based on software copyright (c) 2000, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.soap.util.mime;
+
+import java.io.*;
+import javax.activation.*;
+
+/**
+ * This class implements a typed DataSource from
+ * 	an InputStream,
+ *	a byte array,
+ * 	a String,
+ *      a File.
+ *
+ * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
+ */
+
+public class ByteArrayDataSource implements DataSource
+{
+    private byte[] data; // data
+    private String type; // content-type
+
+    /**
+     * Create a datasource from a File.
+     */
+    public ByteArrayDataSource(File f, String type) throws IOException
+    {
+        this(new FileInputStream(f), type);
+        if(this.type == null)
+            this.type = FileTypeMap.getDefaultFileTypeMap().getContentType(f);
+    }
+
+    /**
+     * Create a datasource from an input stream.
+     */
+    public ByteArrayDataSource(InputStream is, String type) throws IOException
+    {
+        this.type = type;
+
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+        byte buf[] = new byte[4096];
+        int len;
+        while (true)
+        {
+            len = is.read(buf);
+            if (len < 0)
+                break;
+            os.write(buf, 0, len);
+        }
+        /*
+        int ch;
+        while ((ch = is.read()) != -1)
+            // XXX : must be made more efficient by
+            // doing buffered reads, rather than one byte reads
+            os.write(ch);
+        */
+        data = os.toByteArray();
+    }
+
+    /**
+     * Create a datasource from a byte array.
+     */
+    public ByteArrayDataSource(byte[] data, String type)
+    {
+        this.type = type;
+        this.data = data;
+    }
+
+    /**
+     * Create a datasource from a String. This method assumes that the
+     * String only contains ASCII characters. For a different encoding,
+     * encode the String into a byte array and call the byte array
+     * constructor instead.
+     */
+    public ByteArrayDataSource(String data, String type)
+    {
+        this.type = type;
+
+        try
+        {
+            this.data = data.getBytes("iso-8859-1");
+        }
+        catch (UnsupportedEncodingException uex)
+        {
+        }
+    }
+
+    /**
+     * Return an InputStream to read the content.
+     */
+    public InputStream getInputStream() throws IOException
+    {
+        if (data == null)
+            throw new IOException("No data.");
+        return new ByteArrayInputStream(data);
+    }
+
+    /**
+     * This DataSource cannot return an OutputStream.
+     */
+    public OutputStream getOutputStream() throws IOException
+    {
+        throw new IOException("getOutputStream() not supported.");
+    }
+
+    /**
+     * Get the content type.
+     */
+    public String getContentType()
+    {
+        return type;
+    }
+
+    /**
+     * Set the content type.
+     */
+    public void setContentType(String type)
+    {
+        this.type = type;
+    }
+
+    /**
+     * getName() is not supported.
+     */
+    public String getName()
+    {
+        return "";
+    }
+
+    /**
+     * Write the content to an OutputStream.
+     */
+    public void writeTo(OutputStream os) throws IOException
+    {
+        os.write(data);
+    }
+
+    /**
+     * Return the content as a byte array.
+     */
+    public byte[] toByteArray()
+    {
+        return data;
+    }
+
+    /**
+     * Return the size of the byte array.
+     */
+    public int getSize()
+    {
+        if (data == null)
+            return -1;
+        else
+            return data.length;
+    }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/util/mime/MimeUtils.java xml-soap-mime/java/src/org/apache/soap/util/mime/MimeUtils.java
--- xml-soap/java/src/org/apache/soap/util/mime/MimeUtils.java	Sun Feb  7 00:28:16 2106
+++ xml-soap-mime/java/src/org/apache/soap/util/mime/MimeUtils.java	Sun Jul 30 11:55:30 2000
@@ -0,0 +1,97 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2000 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 acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "SOAP" 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 name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * 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 and was
+ * originally based on software copyright (c) 2000, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.soap.util.mime;
+
+import javax.mail.internet.InternetAddress;
+
+/**
+ * Mime-related utility functions.
+ *
+ * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
+ */
+
+public class MimeUtils
+{
+    /**
+     * Get a unique value.
+     *
+     * Similar to javax.mail.internet.UniqueValue, this implementation
+     * generates a unique value by concatenating a newly
+     * created object's <code>hashCode()</code>, the current
+     * time (in milliseconds), and this system's hostname generated by
+     * <code>InternetAddress.getLocalAddress()</code>.
+     */
+    public static String getUniqueValue()
+    {
+        String suffix = null;
+
+        InternetAddress addr = InternetAddress.getLocalAddress(null);
+        if (addr != null)
+            suffix = addr.getAddress();
+        else {
+            suffix = "user@localhost";
+        }
+
+        StringBuffer s = new StringBuffer();
+
+        // Unique string is <hashcode>.<currentTime>.apache-soap.<suffix>
+        s.append(s.hashCode()).append('.').append(System.currentTimeMillis()).
+            append(".apache-soap.").append(suffix);
+        return s.toString();
+    }
+}
diff -u -P -r xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java xml-soap-mime/java/src/org/apache/soap/util/net/HTTPUtils.java
--- xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java	Sat Jul 29 14:14:06 2000
+++ xml-soap-mime/java/src/org/apache/soap/util/net/HTTPUtils.java	Sun Jul 30 11:20:34 2000
@@ -61,11 +61,20 @@
 import java.io.*;
 import java.util.*;
 
+import org.apache.soap.rpc.*;
+import org.apache.soap.util.mime.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
 /**
  * A bunch of utility stuff for doing HTTP things.
+ * <p>
+ * 2000/07/30 W. Cloetens     added Multipart Mime support
  *
  * @author Sanjiva Weerawarana (sanjiva@watson.ibm.com)
  * @author Matthew J. Duftler (duftler@us.ibm.com)
+ * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
  */
 public class HTTPUtils {
   private static final String HTTP_VERSION = "1.0";
@@ -109,90 +118,234 @@
    */
   public static Response post (URL url, Hashtable headers,
 			       String contentType, String content)
-               throws IllegalArgumentException {
-    PrintWriter out = null;
-    BufferedReader in = null;
-    try {
-      int port = url.getPort ();
-
-      if (port < 0)  // No port given..use HTTP default which is pry 80 :-)
-        port = HTTP_DEFAULT_PORT;
-
-      Socket s = new Socket (url.getHost (), port);
-
-      out = new PrintWriter (s.getOutputStream ());
-      in = new BufferedReader (new InputStreamReader (s.getInputStream ()));
-    } catch (Exception e) {
-      throw new IllegalArgumentException ("error opening socket: " +
-					  e.getMessage ());
-    }
+      throws IllegalArgumentException, IOException
+  {
+      return post (url, headers, contentType, content.getBytes(), false);
+  }
 
-    /* send it out */
-    out.print (HTTP_POST + " " + url.getFile() + " HTTP/" + HTTP_VERSION + 
-	       "\r\n");
-    out.print (HEADER_HOST + ": " + url.getHost () + ':' + url.getPort () +
-	       "\r\n");
-    out.print (HEADER_CONTENT_TYPE + ": " + contentType + "\r\n");
-    out.print (HEADER_CONTENT_LENGTH + ": " + content.length () + "\r\n");
-    for (Enumeration e = headers.keys (); e.hasMoreElements (); ) {
-      Object key = e.nextElement ();
-      out.print (key + ": " + headers.get (key) + "\r\n");
-    }
-    out.print ("\r\n");
-    out.print (content);
-    out.print ("\r\n\r\n");
-    out.flush ();
-    //    out.close ();
-
-    /* read the status line */
-    int statusCode = 0;
-    String statusString = null;
-    try {
-      StringTokenizer st = new StringTokenizer (in.readLine ());
-      st.nextToken (); // ignore version part
-      statusCode = Integer.parseInt (st.nextToken ());
-      StringBuffer sb = new StringBuffer ();
-      while (st.hasMoreTokens ()) {
-	sb.append (st.nextToken ());
-	if (st.hasMoreTokens ()) {
-	  sb.append (" ");
-	}
-      }
-      statusString = sb.toString ();
-    } catch (Exception e) {
-      throw new IllegalArgumentException ("error parsing HTTP status line: " +
-					  e.getMessage ());
-    }
+  public static Response post (URL url, Hashtable headers,
+                               String contentType, byte[] content)
+      throws IllegalArgumentException, IOException
+  {
+      return post (url, headers, contentType, content, true);
+  }
 
-    /* get the headers */
-    Hashtable respHeaders = new Hashtable ();
-    int respContentLength = -1;
-    String respContentType = null;
-    try {
-      String line = null;
-      while ((line = in.readLine ()) != null) {
-	if (line.length () == 0) {
-	  break;
-	}
-	int colonIndex = line.indexOf (':');
-	String fieldName = line.substring (0, colonIndex);
-	String fieldValue = line.substring (colonIndex + 1).trim ();
-	if (fieldName.equals (HEADER_CONTENT_LENGTH)) {
-	  respContentLength = Integer.parseInt (fieldValue);
-	} else if (fieldName.equals (HEADER_CONTENT_TYPE)) {
-	  respContentType = fieldValue;
-	} else {
-	  respHeaders.put (fieldName, fieldValue);
-	}
-      }
-    } catch (Exception e) {
-      throw new IllegalArgumentException ("error reading HTTP headers: " +
-					  e.getMessage ());
-    }
+  /**
+   * POST something to the given URL. The headers are put in as 
+   * HTTP headers, the content length is calculated and the content
+   * is sent as content. The content is a byte array to avoid
+   * codepage conversion. The content may include extra headers
+   * and must include an empty line between header and body section
+   * if the hasHeaders argument is true.
+   * The content type must be provided in the header hashtable or
+   * header part of the content if the contentType parameter is
+   * null.
+   *
+   * @param url the url to post to
+   * @param headers additional headers to send as HTTP headers
+   * @param contentType type of the content
+   * @param content the body of the post
+   * @param hasHeaders indicates content contains headers
+   */
+  public static Response post (URL url, Hashtable headers,
+                               String contentType, byte[] content,
+                               boolean hasHeaders)
+      throws IllegalArgumentException, IOException
+  {
+      PrintWriter out = null;
+      OutputStream outStream = null;
+      InputStream inStream = null;
+      BufferedReader in = null;
+      try
+      {
+          int port = url.getPort ();
+
+          if (port < 0)  // No port given..use HTTP default which is pry 80 :-)
+            port = HTTP_DEFAULT_PORT;
+
+          Socket s = new Socket (url.getHost (), port);
+
+          outStream = s.getOutputStream ();
+          out = new PrintWriter (outStream);
+          inStream = s.getInputStream ();
+      }
+      catch (Exception e)
+      {
+          throw new IllegalArgumentException ("error opening socket: " +
+                                              e.getMessage ());
+      }
+
+      /* send it */
+      out.print (HTTP_POST + " " + url.getFile() + " HTTP/" + HTTP_VERSION +
+                 "\r\n");
+      out.print (HEADER_HOST + ": " + url.getHost () + ':' + url.getPort () +
+                 "\r\n");
+      if (contentType != null)
+          out.print (HEADER_CONTENT_TYPE + ": " + contentType + "\r\n");
+
+      /* If the content contains headers, we need to know the content length
+       * without them. Read until the end of the content's header block. */
+      int contentLength;
+      if (!hasHeaders)
+      {
+          contentLength = content.length;
+      }
+      else
+      {
+          if (content.length >= 2
+              && content[0] == '\r'
+              && content[1] == '\n')
+              contentLength = content.length - 2;
+          else
+          {
+              int i;
+              for (i = 3; i < content.length; i++)
+                  if (content[i] == '\n'
+                      && content[i - 1] == '\r'
+                      && content[i - 2] == '\n'
+                      && content[i - 3] == '\r')
+                      break;
+              contentLength = content.length - i;
+          }
+      }
+
+      out.print (HEADER_CONTENT_LENGTH + ": " + contentLength + "\r\n");
+      for (Enumeration e = headers.keys (); e.hasMoreElements (); )
+      {
+          Object key = e.nextElement ();
+          out.print (key + ": " + headers.get (key) + "\r\n");
+      }
+      if (!hasHeaders)
+      {
+          out.print("\r\n");
+      }
+      out.flush();
+      outStream.write (content);
+      outStream.flush();
+      out.print("\r\n\r\n");
+      out.flush ();
+
+      /* read the status line */
+      int statusCode = 0;
+      String statusString = null;
+      StringBuffer linebuf = new StringBuffer ();
+      int b = 0;
+      while (b != '\n' && b != -1)
+      {
+          b = inStream.read ();
+          if (b != '\n' && b != '\r' && b != -1)
+              linebuf.append ((char)b);
+      }
+      String line = linebuf.toString ();
+      try
+      {
+          StringTokenizer st = new StringTokenizer (line);
+          st.nextToken (); // ignore version part
+          statusCode = Integer.parseInt (st.nextToken ());
+          StringBuffer sb = new StringBuffer ();
+          while (st.hasMoreTokens ())
+          {
+              sb.append (st.nextToken ());
+              if (st.hasMoreTokens ())
+              {
+                  sb.append (" ");
+              }
+          }
+          statusString = sb.toString ();
+      }
+      catch (Exception e)
+      {
+          throw new IllegalArgumentException ("error parsing HTTP status line: " +
+                                              e.getMessage ());
+      }
+
+      /* read the entire response (following the status line) into a byte array */
+      ByteArrayDataSource ds = new ByteArrayDataSource(inStream, null);
+
+      /* get the headers */
+      in = new BufferedReader (new InputStreamReader (ds.getInputStream()));
+      Hashtable respHeaders = readHeaderBlock(in);
+      int respContentLength = getContentLength(respHeaders);
+      String respContentType = getContentType(respHeaders);
+
+      /* set the DataSource's Content-Type now we have it. */
+      ds.setContentType(respContentType);
+
+      /* if it's multipart, extract context */
+      try
+      {
+          if((new ContentType(respContentType)).getPrimaryType().equals("multipart"))
+          {
+              RPCContext ctx = new RPCContext();
+              ctx.setRPCContext();
+              ctx.readMultipart(ds);
+
+              MimeBodyPart soapPart = (MimeBodyPart)ctx.getBodyPart(0);
+              in = new BufferedReader (new InputStreamReader (soapPart.getDataHandler().getInputStream()));
+
+              /* The SOAP headers are in the HTTP header, not in the SOAP mime part.
+               * We only want the Content-Type and Content-Length. */
+              respContentLength = -1;
+              String len = soapPart.getHeader (HEADER_CONTENT_LENGTH, null);
+              if (len != null)
+              {
+                  try
+                  {
+                      respContentLength = Integer.parseInt (len);
+                  }
+                  catch (NumberFormatException nfe)
+                  {
+                      throw new IllegalArgumentException("Non-numeric content length \"" + len + "\" in SOAP response: " + nfe);
+                  }
+              }
+              respContentType = soapPart.getContentType();
+          }
+      }
+      catch (MessagingException me)
+      {
+          throw new IllegalArgumentException("Error parsing response: " + me);
+      }
+
+      /* all done here! */
+      return new Response (statusCode, statusString, respHeaders,
+                           respContentLength, respContentType, in);
+  }
+
+  private static Hashtable readHeaderBlock(BufferedReader in) throws IllegalArgumentException
+  {
+      Hashtable respHeaders = new Hashtable ();
+      String respContentType = null;
+      try {
+          String line = null;
+          while ((line = in.readLine ()) != null) {
+              if (line.length () == 0) {
+                  break;
+              }
+              int colonIndex = line.indexOf (':');
+              String fieldName = line.substring (0, colonIndex);
+              String fieldValue = line.substring (colonIndex + 1).trim ();
+              respHeaders.put (fieldName, fieldValue);
+          }
+      } catch (Exception e) {
+          throw new IllegalArgumentException ("error reading HTTP headers: " +
+                                              e.getMessage ());
+      }
+      return respHeaders;
+  }
+
+  private static int getContentLength(Hashtable respHeaders)
+  {
+      String len = (String)respHeaders.get (HEADER_CONTENT_LENGTH);
+      if (len == null)
+          return -1;
+      respHeaders.remove(HEADER_CONTENT_LENGTH);
+      return Integer.parseInt (len);
+  }
 
-      
-    /* all done */
-    return new Response (statusCode, statusString, respHeaders, 
-			 respContentLength, respContentType, in);
+  private static String getContentType(Hashtable respHeaders)
+  {
+      String type = (String)respHeaders.get (HEADER_CONTENT_TYPE);
+      respHeaders.remove(HEADER_CONTENT_LENGTH);
+      return type;
   }
 }