Index: docs/libcurl/Makefile.am
===================================================================
RCS file: /cvsroot/curl/curl/docs/libcurl/Makefile.am,v
retrieving revision 1.28
diff -u -r1.28 Makefile.am
--- docs/libcurl/Makefile.am	12 May 2008 21:43:28 -0000	1.28
+++ docs/libcurl/Makefile.am	31 Jul 2008 18:04:10 -0000
@@ -19,7 +19,11 @@
  libcurl-tutorial.3 curl_easy_reset.3 curl_easy_escape.3		 \
  curl_easy_unescape.3 curl_multi_setopt.3 curl_multi_socket.3		 \
  curl_multi_timeout.3 curl_formget.3 curl_multi_assign.3		 \
- curl_easy_pause.3 curl_easy_recv.3 curl_easy_send.3
+ curl_easy_pause.3 curl_easy_recv.3 curl_easy_send.3			 \
+ curl_mime_decode_context.3 curl_mime_delete.3				 \
+ curl_mime_delete_context.3 curl_mime_encode_context.3 curl_mime_get.3	 \
+ curl_mime_getopt.3 curl_mime_header.3 curl_mime_new.3 curl_mime_put.3	 \
+ curl_mime_setopt.3 curl_cid_index.3 curl_cid_lookup.3 curl_cid_delete.3
 
 HTMLPAGES = curl_easy_cleanup.html curl_easy_getinfo.html		  \
  curl_easy_init.html curl_easy_perform.html curl_easy_setopt.html	  \
@@ -37,8 +41,13 @@
  curl_share_strerror.html curl_global_init_mem.html libcurl-tutorial.html \
  curl_easy_reset.html curl_easy_escape.html curl_easy_unescape.html	  \
  curl_multi_setopt.html curl_multi_socket.html curl_multi_timeout.html	  \
- curl_formget.html curl_multi_assign.html curl_easy_pause.html \
- curl_easy_recv.html curl_easy_send.html
+ curl_formget.html curl_multi_assign.html curl_easy_pause.html		  \
+ curl_easy_recv.html curl_easy_send.html				  \
+ curl_mime_decode_context.html curl_mime_delete.html			  \
+ curl_mime_delete_context.html curl_mime_encode_context.html		  \
+ curl_mime_get.html curl_mime_getopt.html curl_mime_header.html		  \
+ curl_mime_new.html curl_mime_put.html curl_mime_setopt.html		  \
+ curl_cid_index.html curl_cid_lookup.html curl_cid_delete.html
 
 PDFPAGES = curl_easy_cleanup.pdf curl_easy_getinfo.pdf curl_easy_init.pdf \
  curl_easy_perform.pdf curl_easy_setopt.pdf curl_easy_duphandle.pdf	  \
@@ -55,8 +64,13 @@
  curl_share_strerror.pdf curl_global_init_mem.pdf libcurl-tutorial.pdf	  \
  curl_easy_reset.pdf curl_easy_escape.pdf curl_easy_unescape.pdf	  \
  curl_multi_setopt.pdf curl_multi_socket.pdf curl_multi_timeout.pdf	  \
- curl_formget.pdf curl_multi_assign.pdf curl_easy_pause.pdf \
- curl_easy_recv.pdf curl_easy_send.pdf
+ curl_formget.pdf curl_multi_assign.pdf curl_easy_pause.pdf		  \
+ curl_easy_recv.pdf curl_easy_send.pdf					  \
+ curl_mime_decode_context.pdf curl_mime_delete.pdf			  \
+ curl_mime_delete_context.pdf curl_mime_encode_context.pdf		  \
+ curl_mime_get.pdf curl_mime_getopt.pdf curl_mime_header.pdf		  \
+ curl_mime_new.pdf curl_mime_put.pdf curl_mime_setopt.pdf		  \
+ curl_cid_index.pdf curl_cid_lookup.pdf curl_cid_delete.pdf
 
 CLEANFILES = $(HTMLPAGES) $(PDFPAGES)
 
Index: lib/Makefile.inc
===================================================================
RCS file: /cvsroot/curl/curl/lib/Makefile.inc,v
retrieving revision 1.19
diff -u -r1.19 Makefile.inc
--- lib/Makefile.inc	31 Mar 2008 12:09:43 -0000	1.19
+++ lib/Makefile.inc	29 Jul 2008 20:04:02 -0000
@@ -9,7 +9,7 @@
   http_negotiate.c http_ntlm.c inet_pton.c strtoofft.c strerror.c	\
   hostares.c hostasyn.c hostip4.c hostip6.c hostsyn.c hostthre.c	\
   inet_ntop.c parsedate.c select.c gtls.c sslgen.c tftp.c splay.c	\
-  strdup.c socks.c ssh.c nss.c qssl.c
+  strdup.c socks.c ssh.c nss.c qssl.c mime.c
 
 HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h	\
   progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h	\
@@ -20,4 +20,4 @@
   strtoofft.h strerror.h inet_ntop.h curlx.h memory.h setup.h		\
   transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h gtls.h	\
   tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h nssg.h	\
-  qssl.h
+  qssl.h mime.h
Index: lib/base64.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/base64.c,v
retrieving revision 1.45
diff -u -r1.45 base64.c
--- lib/base64.c	5 Nov 2007 09:45:09 -0000	1.45
+++ lib/base64.c	4 Aug 2008 15:43:25 -0000
@@ -49,7 +49,7 @@
 #include "memdebug.h"
 
 /* ---- Base64 Encoding/Decoding Table --- */
-static const char table64[]=
+const char Curl_table64[]=
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
 static void decodeQuantum(unsigned char *dest, const char *src)
@@ -59,8 +59,8 @@
   char *found;
 
   for(i = 0; i < 4; i++) {
-    if((found = strchr(table64, src[i])) != NULL)
-      x = (x << 6) + (unsigned int)(found - table64);
+    if((found = strchr(Curl_table64, src[i])) != NULL)
+      x = (x << 6) + (unsigned int)(found - Curl_table64);
     else if(src[i] == '=')
       x = (x << 6);
   }
@@ -210,21 +210,21 @@
     switch(inputparts) {
     case 1: /* only one byte read */
       snprintf(output, 5, "%c%c==",
-               table64[obuf[0]],
-               table64[obuf[1]]);
+               Curl_table64[obuf[0]],
+               Curl_table64[obuf[1]]);
       break;
     case 2: /* two bytes read */
       snprintf(output, 5, "%c%c%c=",
-               table64[obuf[0]],
-               table64[obuf[1]],
-               table64[obuf[2]]);
+               Curl_table64[obuf[0]],
+               Curl_table64[obuf[1]],
+               Curl_table64[obuf[2]]);
       break;
     default:
       snprintf(output, 5, "%c%c%c%c",
-               table64[obuf[0]],
-               table64[obuf[1]],
-               table64[obuf[2]],
-               table64[obuf[3]] );
+               Curl_table64[obuf[0]],
+               Curl_table64[obuf[1]],
+               Curl_table64[obuf[2]],
+               Curl_table64[obuf[3]] );
       break;
     }
     output += 4;
Index: lib/base64.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/base64.h,v
retrieving revision 1.18
diff -u -r1.18 base64.h
--- lib/base64.h	3 Jan 2007 23:04:41 -0000	1.18
+++ lib/base64.h	4 Aug 2008 15:46:47 -0000
@@ -22,6 +22,9 @@
  *
  * $Id: base64.h,v 1.18 2007-01-03 23:04:41 bagder Exp $
  ***************************************************************************/
+
+extern const char Curl_table64[];
+
 size_t Curl_base64_encode(struct SessionHandle *data,
                           const char *input, size_t size, char **str);
 size_t Curl_base64_decode(const char *source, unsigned char **outptr);
Index: lib/formdata.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/formdata.c,v
retrieving revision 1.105
diff -u -r1.105 formdata.c
--- lib/formdata.c	8 Jul 2008 21:16:18 -0000	1.105
+++ lib/formdata.c	30 Jul 2008 10:14:32 -0000
@@ -249,7 +249,7 @@
 
 /***************************************************************************
  *
- * ContentTypeForFilename()
+ * Curl_ContentTypeForFilename()
  *
  * Provides content type for filename if one of the known types (else
  * (either the prevtype or the default is returned).
@@ -257,8 +257,8 @@
  * Returns some valid contenttype for filename.
  *
  ***************************************************************************/
-static const char * ContentTypeForFilename (const char *filename,
-                                            const char *prevtype)
+const char * Curl_ContentTypeForFilename (const char *filename,
+                                          const char *prevtype)
 {
   const char *contenttype = NULL;
   unsigned int i;
@@ -734,7 +734,7 @@
              !form->contenttype ) {
           /* our contenttype is missing */
           form->contenttype
-            = strdup(ContentTypeForFilename(form->value, prevtype));
+            = strdup(Curl_ContentTypeForFilename(form->value, prevtype));
           if(!form->contenttype) {
             return_value = CURL_FORMADD_MEMORY;
             break;
@@ -933,7 +933,7 @@
 
 #ifdef CURL_DOES_CONVERSIONS
 /*
- * Curl_formcovert() is used from http.c, this converts any
+ * Curl_formconvert() is used from http.c, this converts any
    form items that need to be sent in the network encoding.
    Returns CURLE_OK on success.
  */
Index: lib/formdata.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/formdata.h,v
retrieving revision 1.27
diff -u -r1.27 formdata.h
--- lib/formdata.h	31 Mar 2008 10:02:25 -0000	1.27
+++ lib/formdata.h	22 Jul 2008 17:12:45 -0000
@@ -97,5 +97,8 @@
 
 CURLcode Curl_formconvert(struct SessionHandle *, struct FormData *);
 
+const char * Curl_ContentTypeForFilename (const char *filename,
+                                          const char *prevtype);
+
 #endif
 
--- docs/libcurl/curl_cid_delete.3
+++ docs/libcurl/curl_cid_delete.3
@@ -0,0 +1,37 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_cid_delete 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_cid_delete - deletes a cid index table
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "void curl_cid_delete(curl_cids * " cids ");"
+
+.SH DESCRIPTION
+This function releases all resources associated with the given \fIcids\fP
+index table.
+
+.SH "SEE ALSO"
+.BR curl_cid_index "(3), " curl_cid_lookup "(3)"

--- docs/libcurl/curl_cid_index.3
+++ docs/libcurl/curl_cid_index.3
@@ -0,0 +1,45 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_cid_index 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_cid_index - create a content-id index from a MIME structure
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_cids * curl_cid_index(curl_mimepart * " top_part ");"
+
+.SH DESCRIPTION
+Creates an fast indexing table targetting subparts of the \fItop_part\fP
+MIME tree structure. This table can be subsequently used within calls
+to curl_cid_lookup() to get the part structure associated with a specific
+content ID or mid/cid URI.
+.PP
+The returned table should be freed after use by calling curl_cid_delete().
+
+.SH "RETURN VALUE"
+A pointer to the newly allocated CID indexing table, or NULL if an
+error occurred.
+
+.SH "SEE ALSO"
+.BR curl_cid_lookup "(3), " curl_cid_delete "(3)"

--- docs/libcurl/curl_cid_lookup.3
+++ docs/libcurl/curl_cid_lookup.3
@@ -0,0 +1,46 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id: curl_easy_unescape.3,v 1.3 2008-04-09 11:27:47 bagder Exp $
+.\" **************************************************************************
+.\"
+.TH curl_cid_lookup 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_cid_lookup - fast search a MIME part by its content or message ID
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_mimepart *curl_cid_lookup(CURL *" curl ", curl_cids *" cids ","
+.BI " char * " id ", size_t " idlength ");"
+.ad
+.SH DESCRIPTION
+This function performs a fast index lookup of the \fIidlength\fP-byte long
+\fIid\fP in table \fIcids\fP and returns the associated MIME part.
+If \fIidlength\fP is zero, \fIid\fP is a null-terminated string.
+The \fIid\fP string may be specified as a cid: or mid: URI or as an identifier.
+In the later case, it may be surrounded with '<' and '>'.
+The \fIcurl\fP parameter is used for unescaping URIs: it may be specified as
+NULL, at the price of some extra overhead.
+
+.SH "RETURN VALUE"
+A pointer to the identified MIME part or NULL if not found or if an
+error occurred.
+.SH "SEE ALSO"
+.BR curl_cid_index "(3), " curl_cid_delete "(3)"

--- docs/libcurl/curl_mime_decode_context.3
+++ docs/libcurl/curl_mime_decode_context.3
@@ -0,0 +1,83 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_decode_context 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_decode_context - create a MIME decoding context
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_mimectl * curl_mime_encode_context(curl_mime_parse_callback " callback
+.BI ", void * " arg ");"
+
+.SH DESCRIPTION
+Creates a MIME decoding context. This context will be fed with MIME textual
+data using subsequent calls to curl_mime_put() and these data will be parsed
+into a MIME part tree.
+.PP
+The \fIcallback\fP argument is the address of a procedure called during
+parsing to override default parsing choices, such as part types, whether a
+part should be considered as "non-MIME" (body only), etc. The \fIarg\fP
+argument is passed unchanged to the callback procedure within each call.
+.PP
+The \fIcallback\fP procedure prototype is:
+.PP
+.BI "CURLMIMEcode parse_callback(curl_mimectl * " ctl ","
+.BI " CURLMIMEparse " func ", curl_mimepart * " part ", void * " userdata ");"
+.PP
+where:
+.IP ctl
+is the decoding context handle
+.IP part
+is the MIME part currently being parsed
+.IP userdata
+is the curl_mime_decode_context()'s \fIarg\fP argument
+.IP func
+is one of the following token, telling the callback function why it is called:
+.RS
+.IP CURLMIME_PART_NEW
+The \fIpart\fP has just been created: the callback procedure may now
+decide not to parse it as a structured MIME part. In this case, the whole part
+data (including possible "headers") will be fed into the part body.
+
+.IP CURLMIME_PART_BODY
+All headers have been parsed and stored in the header list of the \fIpart\fP.
+The callback may now set the \fIpart\fP's type depending on some criteria,
+such as the value of an eventual "Content-Length" header, attachment filename,
+etc. If not set at this time, the part type defaults to CURLMIME_TYPE_BUFFER
+with a dynamically allocated buffer.
+.IP CURLMIME_PART_END
+Data parsing for the given \fIpart\fP is over: this is the last time the
+procedure is called for that \fIpart\fP. Clean-up functions may be performed
+at this time.
+.RE
+.PP
+The callback procedure must return CURL_MIME_OK (zero) if successful, else a
+CURL_MIME_\fIxxx\fP error code: this later case will abort the parsing process.
+.SH "RETURN VALUE"
+A pointer to the newly allocated MIME decoding context, or NULL if an
+error occurred.
+
+.SH "SEE ALSO"
+.BR curl_mime_put "(3), " curl_mime_encode_context "(3), "
+.BR curl_mime_delete_context "(3)"

--- docs/libcurl/curl_mime_delete.3
+++ docs/libcurl/curl_mime_delete.3
@@ -0,0 +1,49 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_delete 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_delete - deletes a MIME document part and its associated resources
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "CURLMIMEcode curl_mime_delete(curl_mimepart * " part ");"
+
+.SH DESCRIPTION
+This function releases all resources associated with the given \fIpart\fP and
+its subparts. Dynamically allocated buffers are freed, files are closed,
+temporary files are deleted and callback procedures are informed of the
+part's termination. Part and subparts storage is freed.
+.PP
+Upon return, the MIME part handle is invalid and must not be subsequently
+dereferenced.
+.PP
+If the given \fIpart\fP is a subpart of another MIME part,
+\fIcurl_mime_delete\fP unlinks it prior to deletion, in order to preserve
+the parent part's integrity.
+
+.SH "RETURN VALUE"
+A CURL_MIME_\fIxxx\fP error code if some occurred, else CURL_MIME_OK (zero).
+
+.SH "SEE ALSO"
+.BR curl_mime_new "(3)"

--- docs/libcurl/curl_mime_delete_context.3
+++ docs/libcurl/curl_mime_delete_context.3
@@ -0,0 +1,44 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_delete_context 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_delete_context - deletes a MIME encoding/decoding context
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_mimepart * curl_mime_delete_context(curl_mimectl * " context ");"
+
+.SH DESCRIPTION
+This function releases all resources associated with the given \fIcontext\fP
+and returns the top MIME part previously attached to the \fIcontext\fP.
+.PP
+The caller is responsible to dispose of the returned MIME part tree when no
+longer needed.
+
+.SH "RETURN VALUE"
+The \fIcontext\fP's top MIME part address, or NULL if undefined or if an
+error occurred.
+
+.SH "SEE ALSO"
+.BR curl_mime_encode_context "(3), " curl_mime_decode_context "(3)"

--- docs/libcurl/curl_mime_encode_context.3
+++ docs/libcurl/curl_mime_encode_context.3
@@ -0,0 +1,48 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_encode_context 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_encode_context - create a MIME encoding context
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_mimectl * curl_mime_encode_context(curl_mimepart * " top_part ");"
+
+.SH DESCRIPTION
+Creates an encoding context for the MIME tree structure at the given
+\fItop_part\fP. The returned context handle can be subsequently used
+within calls to curl_mime_get() to obtain the MIME-formatted equivalent of
+the target tree structure.
+.PP
+Although not detected, having the same part tree attached to more
+than one context at a time is not supported and can lead in various
+misfunctionings, like wrongly encoded data or program crash.
+
+.SH "RETURN VALUE"
+A pointer to the newly allocated MIME encoding context, or NULL if an
+error occurred.
+
+.SH "SEE ALSO"
+.BR curl_mime_get "(3), " curl_mime_decode_context "(3), "
+.BR curl_mime_delete_context "(3)"

--- docs/libcurl/curl_mime_get.3
+++ docs/libcurl/curl_mime_get.3
@@ -0,0 +1,53 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_get 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_get - obtains a text MIME chunk from an encoding context
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "int curl_mime_get(curl_mimectl * " context ", char * " buf ","
+.BI " size_t " buflen ");"
+
+.SH DESCRIPTION
+This function gets the next MIME text chunk from an encoding context: the
+part tree associated with the \fIcontext\fP is formatted as MIME text
+"on the fly" and the result is stored into the \fIbuflen\fP-byte long
+buffer at \fIbuf\fP. If the buffer is too short to hold the entire MIME
+document, subsequent chunks may be obtained via consecutive calls to
+curl_mime_get().
+.PP
+This process may lead in some part tree alteration: files are open and
+read when needed, callback procedures are called to get part bodies and
+part headers may be altered when missing, incomplete or inconsistent. The
+tree structure is not supposed to be altered by the caller between subsequent
+curl_mime_get() calls.
+
+.SH "RETURN VALUE"
+If the returned value is positive, it is the number of characters encoded
+into the buffer. If zero, it signals the encoding is over. Else this is a
+negative error number (-CURL_MIME_\fIxxx\fP).
+
+.SH "SEE ALSO"
+.BR curl_mime_encode_context "(3), " curl_mime_delete_context "(3)"

--- docs/libcurl/curl_mime_getopt.3
+++ docs/libcurl/curl_mime_getopt.3
@@ -0,0 +1,167 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_getopt 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_getopt - get options of a curl MIME part
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "CURLMIMEcode curl_mime_setopt(curl_mimepart * " part ","
+.BI "CURLMIMEoption " option ", void * " value ");"
+
+.SH DESCRIPTION
+curl_mime_getopt() picks various MIME part parameters and options as
+currently set for the given MIME \fIpart\fP and returns them in the
+storage pointed to by \fIvalue\fP. The effective type of storage at
+\fIvalue\fP depends on the option fetched.
+
+.SH "MIME STRUCTURE OPTIONS"
+The following options require the \fIvalue\fP-pointed storage to be of type
+\fIcurl_mimepart *\fP.
+.IP CURLMIME_FIRSTCHILD
+Get the first child subpart of the given MIME \fIpart\fP, or NULL if none.
+
+.IP CURLMIME_LASTCHILD
+Get the last child subpart of the given MIME \fIpart\fP, or NULL if none.
+
+.IP CURLMIME_PREVSIBLING
+Get the subpart immediately preceding the given MIME \fIpart\fP in its
+parent's child list, or NULL if none.
+
+.IP CURLMIME_NEXTSIBLING
+Get the subpart immediately following the given MIME \fIpart\fP in its
+parent's child list, or NULL if none.
+
+.IP CURLMIME_FIRSTSIBLING
+Get the first subpart of MIME \fIpart\fP's parent, or NULL if \fIpart\fP
+is not a subpart.
+
+.IP CURLMIME_LASTSIBLING
+Get the last subpart of MIME \fIpart\fP's parent, or NULL if \fIpart\fP
+is not a subpart.
+
+.IP CURLMIME_PARENT
+Get \fIpart\fP's parent MIME part, or NULL if \fIpart\fP is not a subpart.
+
+.IP CURLMIME_TYPE
+Store the \fIpart\fP's type into \fICURLMIMEtype\fP storage at \fIvalue\fP.
+
+.IP CURLMIME_PTRBUFFER
+Get the current body buffer address of MIME \fIpart\fP. Storage at \fIvalue\fP
+must be of type \fIchar *\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_BUFFER.
+
+.IP CURLMIME_COPYBUFFER
+Copy the current data from MIME \fIpart\fP body buffer and store the copied
+data address into the \fIchar *\fP storage at \fIvalue\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_BUFFER.
+.sp
+Unlike CURLMIME_PTRBUFFER, the returned address must be freed after use.
+
+.IP CURLMIME_BUFFERLENGTH
+Get MIME \fIpart\fP's current buffer length into \fIsize_t\fP storage at
+\fIvalue\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_BUFFER.
+
+.IP CURLMIME_FILENAME
+Copy the current body data file name of the given MIME \fIpart\fP as a
+null-terminated string and store the copied data address into the \fIchar *\fP
+storage at \fIvalue\fP. The \fIpart\fP type must be CURLMIME_TYPE_FILE.
+.sp
+The copied data must be released by the caller after use.
+
+.IP CURLMIME_FD
+Store the file descriptor currently associated with the given MIME \fIpart\fP
+into the \fIFILE *\fP variable at \fIvalue\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_FILE.
+
+.IP CURLMIME_TMPFILE
+Returns non-zero into the \fIbool\fP storage pointed by \fIvalue\fP if
+the body data file associated with the given MIME \fIpart\fP is set to be
+deleted upon part destroy, else 0.
+The \fIpart\fP's type must be CURLMIME_TYPE_FILE.
+
+.IP CURLMIME_CALLBACK
+Get MIME \fIpart\fP's callback procedure address into \fIcurl_mime_callback\fP
+storage pointed by \fIvalue\fP. See curl_mime_setopt(3) for a description
+of callback procedures.
+The \fIpart\fP's type must be CURLMIME_TYPE_CALLBACK.
+
+.IP CURLMIME_CALLBACKARG
+Get MIME \fIpart\fP's current callback user data into the \fIvoid *\fP storage
+pointed by \fIvalue\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_CALLBACK.
+
+.IP CURLMIME_BOUNDARY
+Copy MIME \fIpart\fP's multipart boundary as a null-terminated string and store
+copied data address into the \fIchar *\fP storage pointed by \fIvalue\fP.
+The \fIpart\fP's type must be CURLMIME_TYPE_MULTIPART.
+
+.SH "OTHER OPTIONS"
+.IP CURLMIME_PTRNAME
+Return the MIME \fIpart\fP's current name address into the \fIchar *\fP storage
+pointed by \fIvalue\fP, or NULL if not defined. Name data is not copied.
+.sp
+Since the name may be null-terminated or not, CURLMIME_NAMELENGTH should be
+called to get the effective name length.
+
+.IP CURLMIME_COPYNAME
+Copy the MIME \fIpart\fP's current name and store the
+copied data address into the \fIchar *\fP storage pointed by \fIvalue\fP.
+.sp
+Unlike CURLMIME_PTRNAME, the returned address must be released by the caller
+after use.
+
+.IP CURLMIME_NAMELENGTH
+Return the MIME \fIpart\fP's current name length into the \fIsize_t\fP storage
+at \fIvalue\fP, or 0 if the name is null-terminated.
+
+.IP CURLMIME_HEADERLIST
+Return the MIME \fIpart\fP's current header list into the
+\fIstruct curl_slist *\fP storage at \fIvalue\fP. Header list is not copied and
+thus should not be released by the caller.
+
+.IP CURLMIME_ISFILE
+Returns non-zero into the \fIbool\fP storage pointed by \fIvalue\fP if
+the MIME \fIpart\fP is forced as an attachment, else 0.
+
+.IP CURLMIME_SHOWFILENAME
+Copy the MIME \fIpart\fP's forced attachment file name as a null-terminated
+string and store the copy address into the \fIchar *\fP storage at \fIvalue\fP.
+NULL may be returned if the attachment file name is not defined.
+
+.IP CURLMIME_ASIS
+Returns non-zero into the \fIbool\fP storage pointed by \fIvalue\fP if
+the \fIpart\fP is not MIME-structured, else 0.
+
+.IP CURLMIME_TRANSCODE
+Returns non-zero into the \fIbool\fP storage pointed by \fIvalue\fP if the
+MIME \fIpart\fP body has to be encoded/decoded according to the
+"Content-Transfer-Encoding" header.
+
+.SH "RETURN VALUE"
+CURL_MIME_OK (zero) if successful, else a CURL_MIME_\fIxxx\fP error code.
+
+.SH "SEE ALSO"
+.BR curl_mime_new "(3), " curl_mime_setopt "(3), " curl_mime_delete "(3)"

--- docs/libcurl/curl_mime_header.3
+++ docs/libcurl/curl_mime_header.3
@@ -0,0 +1,95 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_header 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_header - manipulate individual headers of a curl MIME part
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "CURLMIMEcode curl_mime_header(curl_mimepart * " part ","
+.BI " CURLMHoption " option ", const char * " name ", int " rank ","
+.BI " void * " arg ");"
+
+.SH DESCRIPTION
+curl_mime_header() allows to manipulate the MIME \fIpart\fP headers
+individually. The \fIoption\fP argument determines the exact function
+requested and the effective \fIarg\fP argument type.
+.PP
+Unless otherwise noted, the requested function is performed on, or relative to
+a target header determined as follows: if the \fIname\fP is not NULL, only
+the headers of this name are considered (the match is case-insensitive);
+\fIrank\fP specifies the occurrence number (starting at 1) of the (named)
+header; it may be given as a negative value: in this case, occurrence
+counting starts backwards from the end of of the header list. The
+interpretation of \fIrank\fP == 0 is request-dependent.
+
+Possible \fIoption\fP requests are:
+.IP CURLMH_NOP
+Do nothing. This can be used to bypass a call to curl_mime_header().
+
+.IP CURLMH_GET
+Store the address of the targeted header into the \fIstruct curl_slist *\fP
+storage pointed by \fIarg\fP. NULL can be stored if the target header does
+not exist.
+.sp
+If \fIrank\fP is zero, the first occurrence is used.
+
+.IP CURLMH_COUNT
+Count occurrences of the target header and stores the result into the
+\fIsize_t\fP storage pointed by \fIarg\fP. This request ignores the \fIrank\fP
+argument.
+
+.IP CURLMH_SET
+Set the target header. The new value is a \fIstruct curl_slist\fP pointed by
+\fIarg\fP. The header setting is performed as follows:
+.RS
+.IP _
+If \fIrank\fP is zero, the new header is inserted just before the first target
+header in the existing list.
+.IP _
+If \fIrank\fP is larger than the target header occurrence count, the new
+header is inserted just after the last occurrence of the target header.
+.IP _
+Else the target header is replaced by the new header.
+.RE
+.IP CURLMH_PREPEND
+Insert a new header immediately before the target header. The new header is
+a \fIstruct curl_slist\fP pointed by \fIarg\fP. If \fIrank\fP is zero, the
+first occurrence is targeted.
+
+.IP CURLMH_APPEND
+Append a new header immediately after the target header. The new header is
+a \fIstruct curl_slist\fP pointed by \fIarg\fP. If \fIrank\fP is zero, the
+last occurrence is targeted.
+
+.IP CURLMH_DELETE
+Delete all or a single occurrence of a header. If \fIrank\fP is zero, all
+occurrences of the (named) header are deleted. Else only the target header
+is deleted. Deletion consists in unlinking the header and releasing it.
+
+.SH "RETURN VALUE"
+CURL_MIME_OK (zero) if successful, else a CURL_MIME_\fIxxx\fP error code.
+
+.SH "SEE ALSO"
+.BR curl_mime_new "(3), " curl_mime_setopt "(3), " curl_slist_append "(3)"

--- docs/libcurl/curl_mime_new.3
+++ docs/libcurl/curl_mime_new.3
@@ -0,0 +1,71 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_new 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_new - create a MIME document part
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "curl_mimepart * curl_mime_new(CURLMIMEtype " part_type ");"
+
+.SH DESCRIPTION
+This function allocates and returns an empty MIME document part.
+The \fIpart_type\fP argument determines how the part's body data will be stored
+and must be specified as one of the following values:
+
+.IP CURLMIME_TYPE_NONE
+The part is created "untyped". The final part type setting will then have to
+be set later.
+
+.IP CURLMIME_TYPE_BUFFER
+MIME part body is stored in a memory buffer. Later calls to
+\fIcurl_mime_setopt\fP will set the buffer address, data length and whether
+the buffer is dynamically allocated or not.
+
+.IP CURLMIME_TYPE_FILE
+MIME part body is held in a file. The actual file name and/or descriptor is
+set via subsequent calls to \fIcurl_mime_setopt\fP.
+
+.IP CURLMIME_TYPE_CALLBACK
+Body data are handled "on the fly" by a user callback procedure.
+See \fIcurl_mime_setopt\fP for the prototype of such procedures.
+
+.IP CURLMIME_TYPE_MULTIPART
+This is a non-leaf MIME part. It supports the "Content-Type: multipart/xxxx"
+MIME header and is made of a list of subparts. Currently, there is no support
+for body data appearing before the first subpart or after the last one.
+
+.IP CURLMIME_TYPE_CONCAT
+The part does not hold body data by itself, but yields a list of parts: when
+converted to MIME textual form, the whole subtree will appear as a single part.
+This part type allows the logical concatenation of non-contiguous buffered
+body data and/or file and memory body data. Since the subparts of such a part
+only represent the current part's body, they may not be of type
+\fICURLMIME_TYPE_MULTIPART\fP and may not contain MIME headers.
+
+.SH "RETURN VALUE"
+A pointer to the newly allocated MIME part, or NULL if an error occurred.
+
+.SH "SEE ALSO"
+.BR curl_mime_setopt "(3), " curl_mime_delete "(3)"

--- docs/libcurl/curl_mime_put.3
+++ docs/libcurl/curl_mime_put.3
@@ -0,0 +1,50 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_put 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_put - feed a MIME decoding context with a text chunk
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "int curl_mime_put(curl_mimectl * " context ", const char * " buf ","
+.BI " size_t " buflen ");"
+
+.SH DESCRIPTION
+This function feeds then given MIME decoding \fIcontext\fP with the
+\fIbuflen\fP-byte long MIME textual data chunk at \fIbuf\fP: the data is
+then parsed and the equivalent part tree structure is built accordingly.
+Subsequent calls to curl_mime_put() can be issued to feed the context with
+additional data.
+.PP
+A handle to the top part of the resulting tree structure is returned
+by curl_mime_delete_context() when called after all MIME data has been fed
+into curl_mime_put().
+
+.SH "RETURN VALUE"
+If the returned value is positive or zero, it is the effective consumed
+character count. Else this is a
+negative error number (-CURL_MIME_\fIxxx\fP).
+
+.SH "SEE ALSO"
+.BR curl_mime_decode_context "(3), " curl_mime_delete_context "(3)"

--- docs/libcurl/curl_mime_setopt.3
+++ docs/libcurl/curl_mime_setopt.3
@@ -0,0 +1,249 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * $Id$
+.\" **************************************************************************
+.\"
+.TH curl_mime_setopt 3 "24 July 2008" "libcurl 7.18.2" "libcurl Manual"
+.SH NAME
+curl_mime_setopt - set options of a curl MIME part
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "CURLMIMEcode curl_mime_setopt(curl_mimepart * " part ","
+.BI "CURLMIMEoption " option ", ...);"
+.br
+.BI "CURLMIMEcode curl_mime_vsetopt(curl_mimepart * " part ","
+.BI "CURLMIMEoption " option ", va_list " params ");"
+
+.SH DESCRIPTION
+curl_mime_setopt() allows to set various MIME part parameters and options,
+to link subparts within parts, etc. The \fIoption\fP argument determines
+the exact function to perform and the type of possible additional arguments.
+.PP
+curl_mime_vsetopt() is the argument vector version of the same procedure.
+
+.SH "MIME STRUCTURE OPTIONS"
+Unless otherwise noted, the following options require the MIME type of the
+\fIpart\fP argument or its parent to be \fICURLMIME_TYPE_CONCAT\fP,
+\fICURLMIME_TYPE_MULTIPART\fP or \fICURLMIME_TYPE_NONE\fP.
+In the later case, the type is set to
+\fICURLMIME_TYPE_MULTIPART\fP with an empty child list and an undefined
+boundary. These options also require an additional \fIsubpart\fP argument of
+type \fIcurl_mimepart *\fP.
+
+.IP CURLMIME_FIRSTCHILD
+Prepend the MIME \fIsubpart\fP to the \fIpart\fP child list.
+
+.IP CURLMIME_LASTCHILD
+Append the MIME \fIsubpart\fP to the \fIpart\fP child list.
+
+.IP CURLMIME_PREVSIBLING
+Insert the MIME \fIsubpart\fP as a \fIpart\fP sibling, just before it in
+parent's child list. \fIpart\fP must already have been inserted as a subpart.
+
+.IP CURLMIME_NEXTSIBLING
+Insert the MIME \fIsubpart\fP as a \fIpart\fP sibling, just after it in
+parent's child list. \fIpart\fP must already have been inserted as a subpart.
+
+.IP CURLMIME_FIRSTSIBLING
+Append the MIME \fIsubpart\fP to the \fIpart\fP sibling list. \fIpart\fP must
+already have been inserted as a subpart.
+
+.IP CURLMIME_LASTSIBLING
+Prepend the MIME \fIsubpart\fP to the \fIpart\fP sibling list. \fIpart\fP must
+already have been inserted as a subpart.
+
+.IP CURLMIME_UNLINK
+Unlink \fIpart\fP from its parent's child list. This function requires no
+additional argument.
+
+.SH "CONTENTS SETTING OPTIONS"
+.IP CURLMIME_TYPE
+Sets the \fIpart\fP's content storage type according to and additional
+argument of type \fICURLMIMEtype\fP. If the previous \fIpart\fP's type was
+not compatible (i.e.: not the same), the previous contents are released.
+
+.IP CURLMIME_PTRBUFFER
+Takes two additional arguments: \fIbufaddr\fP of type \fIchar *\fP and
+\fIbufsize\fP of type \fIsize_t\fP. Use the \fIbufsize\fP-byte long static
+buffer at \fIbufaddr\fP as the MIME body data buffer of the given \fIpart\fP.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_BUFFER. If
+it is not CURLMIME_TYPE_BUFFER, an error is returned.
+.sp
+If a previous dynamically allocated buffer was associated with the part, it is
+released by this call.
+
+.IP CURLMIME_COPYBUFFER
+Takes two additional arguments: \fIdataptr\fP of type \fIchar *\fP and
+\fIdatasize\fP of type \fIsize_t\fP. Allocates a \fIdatasize\fP-byte long
+dynamic buffer, copy data from \fIdataptr\fP (if not NULL) to it and set it
+as the MIME body data buffer of the given \fIpart\fP.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_BUFFER. If
+it is not CURLMIME_TYPE_BUFFER, an error is returned.
+.sp
+If a previous dynamically allocated buffer was associated with the part, it is
+released by this call.
+.sp
+Dynamically allocated buffers offers the advantage to be automatically
+enlarged during MIME parsing to fit the actual data size.
+
+.IP CURLMIME_FILENAME
+Use an additional \fIchar *\fP argument as the null-terminated name of the
+file containing the \fIpart\fP's body. 
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_FILE. If
+it is not CURLMIME_TYPE_FILE, an error is returned.
+.sp
+Since the name is copied by this call, it is not needed to preserve the
+argument until effective use.
+
+.IP CURLMIME_TMPFILE
+If the additional \fIint\fP argument is non-zero, the file holding the
+\fIpart\fP's body is considered as temporary and will be deleted upon
+\fIpart\fP's destroy or type change. If \fIpart\fP type is not yet
+known, it is set to CURLMIME_TYPE_FILE. If it is not CURLMIME_TYPE_FILE,
+an error is returned.
+.sp
+The file deletion is effective only if the file name is known at
+\fIpart\fP destroy time.
+
+.IP CURLMIME_FD
+Takes an additional argument of type \fIFILE *\fP and use it as the body
+file descriptor for the \fIpart\fP.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_FILE. If
+it is not CURLMIME_TYPE_FILE, an error is returned.
+
+.IP CURLMIME_CALLBACK
+Use the additional argument of type \fIcurl_mime_callback\fP as the address
+of the callback procedure handling body data of the given MIME \fIpart\fP.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_CALLBACK. If
+it is not CURLMIME_TYPE_CALLBACK, an error is returned.
+.br
+Callback procedures must have the following prototype:
+.sp
+.BI "int callback_proc(curl_mimepart * " part ", CURLMIMEfunc " func ","
+.BI " void * " buf ", size_t " n ", void * " userdata ");"
+.sp
+where \fIuserdata\fP is the value set by CURLMIME_CALLBACKARG for the \fIpart\fP
+and \fIfunc\fP is one of the following value:
+.RS
+.IP CURLMIME_CALLBACK_READ
+Provide the caller up to \fIn\fP bytes into buffer at \fIbuf\fP. Prepare to
+provide next byte. Return the byte count provided, 0 if end of data or a
+negative CURL_MIME_\fIxxx\fP error code.
+
+.IP CURLMIME_CALLBACK_WRITE
+Consume \fIn\fP bytes from buffer at \fIbuf\fP. Return the effective
+consumed byte count, or a negative CURL_MIME_\fIxxx\fP error code.
+
+.IP CURLMIME_CALLBACK_WEOF
+Tells that there will be no more bytes to consume.
+
+.IP CURLMIME_CALLBACK_REWIND
+Prepare to provide the first data bytes upon next call with \fIfunc\fP ==
+CURLMIME_CALLBACK_READ.
+
+.IP CURLMIME_CALLBACK_CLOSE
+Tells the part is about to be destroyed.
+.RE
+
+.IP CURLMIME_CALLBACKARG
+The additional argument of type \fIvoid *\fP is set as the user data pointer for
+the callback procedure.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_CALLBACK. If
+it is not CURLMIME_TYPE_CALLBACK, an error is returned.
+
+.IP CURLMIME_BOUNDARY
+Take the additional \fIchar *\fP argument as the null-terminated boundary
+value for the \fIpart\fP.
+If \fIpart\fP type is not yet known, it is set to CURLMIME_TYPE_MULTIPART. If
+it is not CURLMIME_TYPE_MULTIPART, an error is returned.
+.sp
+Since the boundary is copied by this call, it is not needed to preserve the
+argument until effective use.
+
+.SH "OTHER OPTIONS"
+.IP CURLMIME_HEADERLIST
+Use the additional \fIstruct curl_slist *\fP argument as the MIME header list
+for the given \fIpart\fP. If a previous header list is associated to the part,
+it is destroyed.
+.sp
+The target MIME \fIpart\fP should not be a child of a part of type
+CURLTYPE_MIME_CONCAT: this would not lead in a error, but headers will simply
+be ignored.
+
+.IP CURLMIME_PTRNAME
+Takes two additional arguments: \fIname\fP of type \fIchar *\fP and
+\fInamelength\fP of type \fIsize_t\fP. Use the \fInamelength\fP-character
+string at \fIname\fP as the MIME \fIpart\fP name. This name will typically
+be used in the Content-Type part header and to determine content type
+from its suffix. It is also used as an attachment file name in the
+Content-Disposition header if none is provided.
+.sp
+The \fIname\fP data is not copied and therefore should be retained until
+part deletion.
+.sp
+When \fInamelength\fP is zero, \fIname\fP is assumed to be null-terminated.
+.sp
+If a previous copied name was associated with the part, it is
+released by this call.
+
+.IP CURLMIME_COPYNAME
+Takes two additional arguments: \fIname\fP of type \fIchar *\fP and
+\fInamelength\fP of type \fIsize_t\fP. Copy the \fInamelength\fP-character
+string at \fIname\fP as the MIME \fIpart\fP name.
+.sp
+Since copy is performed, the \fIname\fP data need not be retained after
+the call completes.
+.sp
+When \fInamelength\fP is zero, \fIname\fP is assumed to be null-terminated.
+.sp
+If a previous copied name was associated with the part, it is
+released by this call.
+
+.IP CURLMIME_SHOWFILENAME
+Use the \fIchar *\fP additional argument as the null-terminated attachment
+filename in the Content-Disposition MIME header. The name is copied, so
+there is no need to retain data after the call completes.
+
+.IP CURLMIME_ISFILE
+If the \fIint\fP additional argument is non-zero, the \fIpart\fP is
+an attachment. The attachment filename is specified by CURLMIME_SHOWFILENAME
+or if not set, by CURLMIME_PTRNAME/CURLMIME_COPYNAME.
+
+.IP CURLMIME_ASIS
+If the \fIint\fP additional argument is non-zero, \fIpart\fP MIME headers
+are not processed: the whole part data is then considered to belong to the body.
+This allows external MIME control over the so-called body part.
+
+.IP CURLMIME_TRANSCODE
+If the \fIint\fP additional argument is non-zero, the MIME \fIpart\fP body
+will be transcoded according to the "Content-Transfer-Encoding" MIME header
+upon parsing or encoding.
+
+.IP CURLMIME_NOP
+Do nothing and ignore all additional arguments: this is mainly of any use
+with curl_mime_vsetopt() to neutralise the call.
+
+.SH "RETURN VALUE"
+CURL_MIME_OK (zero) if successful, else a CURL_MIME_\fIxxx\fP error code.
+
+.SH "SEE ALSO"
+.BR curl_mime_new "(3), " curl_mime_getopt "(3), " curl_mime_delete "(3)"

--- lib/mime.c
+++ lib/mime.c
@@ -0,0 +1,2815 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+#include "setup.h"
+#include <curl/curl.h>
+
+#include "mime.h"
+#include "hash.h"
+#include "urldata.h"
+#include "formdata.h"
+#include "base64.h"
+
+
+#define BUFFER_GRANULE  4096    /* Allocation unit for dynamic buffer. */
+
+#define MAX_MIME_LEN    76      /* Recommended maximum MIME line length. */
+
+#define CID_HASH_SIZE   31      /* CID hash table size. */
+
+
+typedef enum {
+  CM_BEGINOUT,
+  CM_ENDBODY,
+  CM_OUTBODY,
+  CM_OUTBOUNDARY,
+  CM_OUTBOUNDTRAIL,
+  CM_OUTHDRLF,
+  CM_OUTHDRS,
+  CM_OUTPRELF,
+
+  CM_BEGININ,
+  CM_HDRIN,
+  CM_HDRCONT,
+  CM_HTRAILCR,
+  CM_HTRAILLF,
+  CM_HLEADCR,
+  CM_BODYIN,
+  CM_BODYCONT,
+  CM_SKIP,
+  CM_LAST
+} MIMEstate;
+
+
+static void release_part(curl_mimepart * part); /* Release variant part. */
+
+static void delete_part(curl_mimepart * part)
+{
+  release_part(part);           /* Free variant fields, delete subparts. */
+
+  /* Free associated storage. */
+
+  if((part->flags & MIME_FLAG_NAMEALLOC) && part->name)
+    free(part->name);
+
+  if(part->headers)
+    curl_slist_free_all(part->headers);
+
+  if(part->showfilename)
+    free(part->showfilename);
+
+  free(part);
+}
+
+static void
+release_part(curl_mimepart * part)
+{
+  curl_mimepart * subpart;
+  curl_mimepart * nextsubpart;
+
+  /* Free variant-associated storage according to part type. */
+
+  switch (part->parttype) {
+
+  case CURLMIME_TYPE_BUFFER:
+    if((part->flags & MIME_FLAG_BUFFERALLOC) && part->dep.buffer.buffer)
+      free(part->dep.buffer.buffer);
+
+    part->flags &= ~MIME_FLAG_BUFFERALLOC;
+    part->dep.buffer.buffer = NULL;
+    part->dep.buffer.bufferlength = 0;
+    break;
+
+  case CURLMIME_TYPE_FILE:
+    if(part->dep.file.fd) {
+      fclose(part->dep.file.fd);
+      part->dep.file.fd = NULL;
+    }
+
+    if(part->dep.file.filename) {
+      if(part->flags & MIME_FLAG_TMPFILE)
+        unlink(part->dep.file.filename);        /* Delete temporary file. */
+
+      free(part->dep.file.filename);
+      part->dep.file.filename = NULL;
+    }
+
+    part->flags &= ~MIME_FLAG_TMPFILE;
+    break;
+
+  case CURLMIME_TYPE_CALLBACK:
+    if(part->dep.callback.proc)
+      (*part->dep.callback.proc)(part, CURLMIME_CALLBACK_CLOSE,
+                                 NULL, 0,part->dep.callback.userdata);
+
+    part->dep.callback.proc = NULL;
+    part->dep.callback.userdata = NULL;
+    break;
+
+  case CURLMIME_TYPE_MULTIPART:
+  case CURLMIME_TYPE_CONCAT:
+    if(part->dep.node.boundary) {
+      free(part->dep.node.boundary);
+      part->dep.node.boundary = NULL;
+    }
+
+    /* Delete subparts. */
+
+    for (subpart = part->dep.node.first; subpart; subpart = nextsubpart) {
+      nextsubpart = subpart->next;
+      delete_part(subpart);
+    }
+
+    part->dep.node.first = NULL;
+    part->dep.node.last = NULL;
+    break;
+  }
+}
+
+
+static void insert_part_before(curl_mimepart * ref, curl_mimepart * newpart)
+{
+  /* Insert `newpart' in linked list as the previous sibling of `ref'.
+     Take care of parent if some. */
+
+  if((newpart->prev = ref->prev))
+    ref->prev->next = newpart;
+  else if(ref->parent)
+    ref->parent->dep.node.first = newpart;
+
+  ref->prev = newpart;
+  newpart->next = ref;
+  newpart->parent = ref->parent;
+}
+
+
+static void insert_part_after(curl_mimepart * ref, curl_mimepart * newpart)
+{
+  /* Insert `newpart' in linked list as the next sibling of `ref'.
+     Take care of parent if some. */
+
+  if((newpart->next = ref->next))
+    ref->next->prev = newpart;
+  else if(ref->parent)
+    ref->parent->dep.node.last = newpart;
+
+  ref->next = newpart;
+  newpart->prev = ref;
+  newpart->parent = ref->parent;
+}
+
+
+static CURLMIMEcode copybytes(char * * dst, char * src, size_t len)
+{
+  char * s;
+
+  /* If `len' is 0, copy string at `src' into dynamic storage, else copy `len'
+     bytes from `src' to dynamic storage.
+     Store the destination address at `dst'. */
+
+  *dst = NULL;
+
+  if(!src)
+    return CURL_MIME_OK;
+
+  if(!len)
+    s = strdup(src);
+  else {
+    s = malloc(len);
+
+    if(s)
+      memcpy(s, src, len);
+    }
+
+  if(!s)
+    return CURL_MIME_MEMORY;
+
+  *dst = s;
+  return CURL_MIME_OK;
+}
+
+
+CURLMIMEcode curl_mime_vsetopt(curl_mimepart * part,
+                               CURLMIMEoption option, va_list param)
+{
+  CURLMIMEtype parttype;
+  CURLMIMEcode result;
+  curl_mimepart * mp;
+  char * s;
+  size_t len;
+
+  /* Set MIME part option from a vararg vector. */
+
+  if(!part)
+    return CURL_MIME_INVALID;
+
+  switch (option) {
+
+  case CURLMIME_NOP:
+    break;
+
+  case CURLMIME_TYPE:                           /* Set part type. */
+    parttype = va_arg(param, CURLMIMEtype);
+
+    if(parttype == part->parttype)
+      break;                                    /* No change. */
+
+    switch (parttype) {                         /* Check for valid part type. */
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_BUFFER:
+    case CURLMIME_TYPE_FILE:
+    case CURLMIME_TYPE_CALLBACK:
+    case CURLMIME_TYPE_MULTIPART:
+    case CURLMIME_TYPE_CONCAT:
+      break;
+
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    release_part(part);                 /* Variant changes, release old. */
+    part->parttype = parttype;
+    break;
+
+  case CURLMIME_PTRNAME:                        /* Set static name. */
+    if(part->name && (part->flags & MIME_FLAG_NAMEALLOC))
+      free(part->name);
+
+    part->flags &= ~MIME_FLAG_NAMEALLOC;
+    part->name = va_arg(param, char *);         /* Ptr to name. */
+    part->namelength = va_arg(param, size_t);   /* Name length. */
+    break;
+
+  case CURLMIME_COPYNAME:                       /* Set allocated name. */
+    s = va_arg(param, char *);                  /* Ptr to name. */
+    len = va_arg(param, size_t);                /* Name length. */
+    result = copybytes(&s, s, len);
+
+    if(result != CURL_MIME_OK)
+      return result;
+
+    if((part->flags & MIME_FLAG_NAMEALLOC) && part->name)
+      free(part->name);
+
+    if(s)
+      part->flags |= MIME_FLAG_NAMEALLOC;
+    else
+      part->flags &= ~MIME_FLAG_NAMEALLOC;
+
+    part->name = s;
+    part->namelength = len;
+    break;
+
+  case CURLMIME_PTRBUFFER:                      /* Set static part data. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+      part->parttype = CURLMIME_TYPE_BUFFER;
+      break;
+
+    case CURLMIME_TYPE_BUFFER:
+      release_part(part);
+      break;
+
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    part->flags &= ~MIME_FLAG_BUFFERALLOC;
+    part->dep.buffer.buffer = va_arg(param, char *);
+    part->dep.buffer.bufferlength = va_arg(param, size_t);
+    break;
+
+  case CURLMIME_COPYBUFFER:                     /* Set allocated part data. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_BUFFER:
+      break;
+
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    s = va_arg(param, char *);                  /* Pointer to data. */
+    len = va_arg(param, size_t);                /* Data size. */
+
+    if(s) {
+      result = copybytes(&s, s, len);
+
+      if(result != CURL_MIME_OK)
+        return result;
+    }
+    else if(len > 0) {
+      s = malloc(len);
+
+      if(!s)
+        return CURL_MIME_MEMORY;
+    }
+    else
+      return CURL_MIME_INVALID;
+
+    release_part(part);
+    part->parttype = CURLMIME_TYPE_BUFFER;
+
+    if(s)
+      part->flags |= MIME_FLAG_BUFFERALLOC;
+
+    part->dep.buffer.buffer = s;
+    part->dep.buffer.bufferlength = len;
+    break;
+
+  case CURLMIME_HEADERLIST:
+    if(part->headers)
+      curl_slist_free_all(part->headers);
+
+    part->headers = va_arg(param, struct curl_slist *);
+    break;
+
+  case CURLMIME_ISFILE:                         /* Set transmitted file flag. */
+    part->flags &= ~MIME_FLAG_RMTFILE;
+
+    if(va_arg(param, int))
+      part->flags |= MIME_FLAG_RMTFILE;
+
+    break;
+
+  case CURLMIME_ASIS:                           /* Set "no-MIME" flag. */
+    part->flags &= ~MIME_FLAG_NOMIME;
+
+    if(va_arg(param, int))
+      part->flags |= MIME_FLAG_NOMIME;
+
+    break;
+
+  case CURLMIME_TRANSCODE:       /* Set transfer encoding process flag. */
+    part->flags &= ~MIME_FLAG_TRANSCODE;
+
+    if(va_arg(param, int))
+      part->flags |= MIME_FLAG_TRANSCODE;
+
+    break;
+
+  case CURLMIME_SHOWFILENAME:                   /* Set transmitted file name. */
+    s = va_arg(param, char *);
+
+    if(s)
+      if(!(s = strdup(s)))
+        return CURL_MIME_MEMORY;
+
+    if(part->showfilename)
+      free(part->showfilename);
+
+    part->showfilename = s;
+    break;
+
+  case CURLMIME_FILENAME:                       /* Set data file name. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_FILE:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    s = va_arg(param, char *);
+
+    if(s)
+      if(!(s = strdup(s)))
+        return CURL_MIME_MEMORY;
+
+    part->parttype = CURLMIME_TYPE_FILE;
+
+    if(part->dep.file.filename)
+      free(part->dep.file.filename);
+
+    part->dep.file.filename = s;
+    break;
+
+  case CURLMIME_FD:                             /* Set data file descriptor. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_FILE:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    part->parttype = CURLMIME_TYPE_FILE;
+
+    if(part->dep.file.fd)
+      fclose(part->dep.file.fd);
+
+    part->dep.file.fd = va_arg(param, FILE *);
+    break;
+
+  case CURLMIME_TMPFILE:                        /* Set temporary file flag. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_FILE:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    part->parttype = CURLMIME_TYPE_FILE;
+    part->flags &= ~MIME_FLAG_TMPFILE;
+
+    if(va_arg(param, int))
+      part->flags |= MIME_FLAG_TMPFILE;
+
+    break;
+
+  case CURLMIME_CALLBACK:               /* Set data read callback procedure. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_CALLBACK:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    part->parttype = CURLMIME_TYPE_CALLBACK;
+    part->dep.callback.proc = va_arg(param, curl_mime_callback);
+    break;
+
+  case CURLMIME_CALLBACKARG:    /* Set user data to callback procedure. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+    case CURLMIME_TYPE_CALLBACK:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    part->parttype = CURLMIME_TYPE_CALLBACK;
+    part->dep.callback.userdata = va_arg(param, void *);
+    break;
+
+  case CURLMIME_FIRSTCHILD:             /* Prepend part to subpart list. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp)
+      return CURL_MIME_INVALID;
+
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+      part->parttype = CURLMIME_TYPE_CONCAT;
+    case CURLMIME_TYPE_CONCAT:
+    case CURLMIME_TYPE_MULTIPART:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    if(part->dep.node.first)
+      insert_part_before(part->dep.node.first, mp);
+    else {
+      part->dep.node.first = mp;
+      part->dep.node.last = mp;
+      mp->parent = part;
+    }
+
+    break;
+
+  case CURLMIME_LASTCHILD:              /* Append part to subpart list. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp)
+      return CURL_MIME_INVALID;
+
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+      part->parttype = CURLMIME_TYPE_CONCAT;
+    case CURLMIME_TYPE_CONCAT:
+    case CURLMIME_TYPE_MULTIPART:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    if(part->dep.node.last)
+      insert_part_after(part->dep.node.last, mp);
+    else {
+      part->dep.node.first = mp;
+      part->dep.node.last = mp;
+      mp->parent = part;
+    }
+
+    break;
+
+  case CURLMIME_PREVSIBLING:    /* Insert part in list as previous sibling. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp || mp == part)
+      return CURL_MIME_INVALID;
+
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    insert_part_before(part, mp);
+    break;
+
+  case CURLMIME_NEXTSIBLING:    /* Insert part in list as next sibling. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp || mp == part)
+      return CURL_MIME_INVALID;
+
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    insert_part_after(part, mp);
+    break;
+
+  case CURLMIME_FIRSTSIBLING:   /* Prepend part to list. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp)
+      return CURL_MIME_INVALID;
+
+    if(part->parent)
+      part = part->parent->dep.node.first;
+    else
+      while(part->prev)
+        part = part->prev;
+
+    if(mp == part)
+      return CURL_MIME_INVALID;
+
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    insert_part_before(part, mp);
+    break;
+
+  case CURLMIME_LASTSIBLING:    /* Append part to list. */
+    mp = va_arg(param, curl_mimepart *);
+
+    if(!mp)
+      return CURL_MIME_INVALID;
+
+    if(part->parent)
+      part = part->parent->dep.node.last;
+    else
+      while(part->next)
+        part = part->next;
+
+    if(mp == part)
+      return CURL_MIME_INVALID;
+
+    if(mp->parent)
+      curl_mime_setopt(mp, CURLMIME_UNLINK);
+
+    insert_part_after(part, mp);
+    break;
+
+  case CURLMIME_UNLINK:         /* Unlink part from the list it belongs to. */
+    if(part->prev)
+      part->prev->next = part->next;
+    else if(part->parent)
+      part->parent->dep.node.first = part->next;
+
+    if(part->next)
+      part->next->prev = part->prev;
+    else if(part->parent)
+      part->parent->dep.node.last = part->prev;
+
+    part->parent = NULL;
+    part->next = NULL;
+    part->prev = NULL;
+    break;
+
+  case CURLMIME_BOUNDARY:               /* Set multipart boundary. */
+    switch (part->parttype) {
+    case CURLMIME_TYPE_NONE:
+      part->parttype = CURLMIME_TYPE_MULTIPART;
+    case CURLMIME_TYPE_MULTIPART:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    s = va_arg(param, char *);
+
+    if(s)
+      if(!(s = strdup(s)))
+        return CURL_MIME_MEMORY;
+
+    if(part->dep.node.boundary)
+      free(part->dep.node.boundary);
+
+    part->dep.node.boundary = s;
+    break;
+
+  default:
+    return CURL_MIME_INVALID;
+  }
+
+  return CURL_MIME_OK;
+}
+
+
+CURLMIMEcode curl_mime_setopt(curl_mimepart * part, CURLMIMEoption option, ...)
+{
+  va_list param;
+  CURLMIMEcode result;
+
+  /* Set MIME part option from the variable argument list. */
+
+  va_start(param, option);
+  result = curl_mime_vsetopt(part, option, param);
+  va_end(param);
+  return result;
+}
+
+
+CURLMIMEcode curl_mime_getopt(curl_mimepart * part,
+                              CURLMIMEoption option, void * value)
+{
+  size_t count;
+  CURLMIMEcode result;
+  char * s;
+  struct curl_slist * lp;
+
+  /* Get the value associated with the `option' specified and store it at
+     `value'. */
+
+  if(!part || !value)
+    return CURL_MIME_INVALID;
+
+  switch (option) {
+
+  case CURLMIME_NOP:
+    break;
+
+  case CURLMIME_TYPE:
+    *((CURLMIMEtype *) value) = part->parttype;
+    break;
+
+  case CURLMIME_PTRNAME:
+    *((char * *) value) = part->name;
+    break;
+
+  case CURLMIME_COPYNAME:
+    return copybytes((char * *) value, part->name, part->namelength);
+
+  case CURLMIME_PTRBUFFER:
+    *((char * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_BUFFER)
+      return CURL_MIME_INVALID;
+
+    *((char * *) value) = part->dep.buffer.buffer;
+    break;
+
+  case CURLMIME_COPYBUFFER:
+    *((char * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_BUFFER)
+      return CURL_MIME_INVALID;
+
+    return copybytes((char * *) value,
+                     part->dep.buffer.buffer, part->dep.buffer.bufferlength);
+
+  case CURLMIME_HEADERLIST:
+    *((struct curl_slist * *) value) = part->headers;
+    break;
+
+  case CURLMIME_ISFILE:
+    *((bool *) value) = part->flags & MIME_FLAG_RMTFILE? 1: 0;
+    break;
+
+  case CURLMIME_ASIS:
+    *((bool *) value) = part->flags & MIME_FLAG_NOMIME? 1: 0;
+    break;
+
+  case CURLMIME_TRANSCODE:
+    *((bool *) value) = part->flags & MIME_FLAG_TRANSCODE? 1: 0;
+    break;
+
+  case CURLMIME_SHOWFILENAME:
+    return copybytes((char * *) value, part->showfilename, 0);
+
+  case CURLMIME_FILENAME:
+    *((char * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_FILE)
+      return CURL_MIME_INVALID;
+
+    return copybytes((char * *) value, part->dep.file.filename, 0);
+
+  case CURLMIME_FD:
+    *((FILE * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_FILE)
+      return CURL_MIME_INVALID;
+
+    *((FILE * *) value) = part->dep.file.fd;
+    break;
+
+  case CURLMIME_TMPFILE:
+    *((bool *) value) = 0;
+
+    if(part->parttype != CURLMIME_TYPE_FILE)
+      return CURL_MIME_INVALID;
+
+    *((bool *) value) = part->flags & MIME_FLAG_TMPFILE? 1: 0;
+    break;
+
+  case CURLMIME_CALLBACK:
+    *((curl_read_callback *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_CALLBACK)
+      return CURL_MIME_INVALID;
+
+    *((curl_mime_callback *) value) = part->dep.callback.proc;
+    break;
+
+  case CURLMIME_CALLBACKARG:
+    *((void * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_CALLBACK)
+      return CURL_MIME_INVALID;
+
+    *((void * *) value) = part->dep.callback.userdata;
+    break;
+
+  case CURLMIME_FIRSTCHILD:
+    *((curl_mimepart * *) value) = NULL;
+
+    switch (part->parttype) {
+    case CURLMIME_TYPE_MULTIPART:
+    case CURLMIME_TYPE_CONCAT:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    *((curl_mimepart * *) value) = part->dep.node.first;
+    break;
+
+  case CURLMIME_LASTCHILD:
+    *((curl_mimepart * *) value) = NULL;
+
+    switch (part->parttype) {
+    case CURLMIME_TYPE_MULTIPART:
+    case CURLMIME_TYPE_CONCAT:
+      break;
+    default:
+      return CURL_MIME_INVALID;
+    }
+
+    *((curl_mimepart * *) value) = part->dep.node.last;
+    break;
+
+  case CURLMIME_PREVSIBLING:
+    *((curl_mimepart * *) value) = part->prev;
+    break;
+
+  case CURLMIME_NEXTSIBLING:
+    *((curl_mimepart * *) value) = part->next;
+    break;
+
+  case CURLMIME_FIRSTSIBLING:
+    if(part->parent)
+      part = part->parent->dep.node.first;
+    else
+      while(part->prev)
+        part = part->prev;
+
+    *((curl_mimepart * *) value) = part;
+    break;
+
+  case CURLMIME_LASTSIBLING:
+    if(part->parent)
+      part = part->parent->dep.node.last;
+    else
+      while(part->next)
+        part = part->next;
+
+    *((curl_mimepart * *) value) = part;
+    break;
+
+  case CURLMIME_BOUNDARY:
+    *((char * *) value) = NULL;
+
+    if(part->parttype != CURLMIME_TYPE_MULTIPART)
+      return CURL_MIME_INVALID;
+
+    return copybytes((char * *) value, part->dep.node.boundary, 0);
+
+  case CURLMIME_PARENT:
+    *((curl_mimepart * *) value) = part->parent;
+    break;
+
+  case CURLMIME_NAMELENGTH:
+    *((size_t *) value) = part->namelength;
+    break;
+
+  case CURLMIME_BUFFERLENGTH:
+    *((size_t *) value) = 0;
+
+    if(part->parttype != CURLMIME_TYPE_BUFFER)
+      return CURL_MIME_INVALID;
+
+    *((size_t *) value) = part->dep.buffer.bufferlength;
+    break;
+
+  default:
+    return CURL_MIME_INVALID;
+    }
+
+  return CURL_MIME_OK;
+}
+
+
+curl_mimepart * curl_mime_new(CURLMIMEtype parttype)
+{
+  curl_mimepart * part;
+
+  /* Create a new mime part. */
+
+  part = calloc(sizeof *part, 1);
+
+  if(part)
+    if(curl_mime_setopt(part, CURLMIME_TYPE, parttype) != CURL_MIME_OK) {
+      free(part);
+      part = (curl_mimepart *) NULL;
+    }
+
+  return part;
+}
+
+
+CURLMIMEcode curl_mime_delete(curl_mimepart * part)
+{
+  /* Delete a MIME part and its subparts. */
+
+  if(!part)
+    return CURL_MIME_INVALID;
+
+  curl_mime_setopt(part, CURLMIME_UNLINK);
+
+  delete_part(part);
+  return CURL_MIME_OK;
+}
+
+
+static struct curl_slist * * find_mime_header(struct curl_slist * * head,
+const char * name, int rank, size_t * count)
+{
+  struct curl_slist * * lpp;
+  struct curl_slist * lp;
+  size_t l;
+
+  /* In header list starting at `*head', find the `rank'th occurrence of
+     header `name'. Upon return, `*count' holds the number of named headers
+     found. Return a pointer to the targeted header address. */
+
+  *count = 0;
+  l = name? strlen(name): 0;
+
+  for (lpp = head; (lp = *lpp); lpp = &lp->next) {
+    /* Check name. */
+    if(name) {
+      if(!curl_strnequal(name, lp->data, l))
+        continue;
+
+      if(lp->data[l] != ':')
+        continue;
+      }
+
+    head = lpp;
+    (*count)++;
+
+    if(rank)
+      if(!--rank)
+        break;
+  }
+
+  return head;
+}
+
+
+CURLMIMEcode curl_mime_header(curl_mimepart * part, CURLMHoption option,
+                              const char * name, int rank, void * arg)
+{
+  struct curl_slist * * lpp;
+  struct curl_slist * lp;
+  size_t count;
+
+  if(!part)
+    return CURL_MIME_INVALID;
+
+  /* Process negative rank: -1 is the last, -2 is the 2nd before last, etc. */
+  if(rank < 0) {
+    find_mime_header(&part->headers, name, 0, &count);
+    rank = -rank >= count? 1: 1 + count + rank;
+  }
+
+  switch (option) {
+
+  case CURLMH_NOP:              /* Do nothing. */
+    break;
+
+  case CURLMH_GET:              /* Get a specific header. */
+    if(!rank)
+      rank = 1;
+
+    lpp = find_mime_header(&part->headers, name, rank, &count);
+    *((struct curl_slist * *) arg) = rank == count? *lpp: NULL;
+    break;
+
+  case CURLMH_COUNT:            /* Count headers. */
+    find_mime_header(&part->headers, name, 0, &count);
+    *((size_t *) arg) = count;
+    break;
+
+  case CURLMH_SET:              /* Set a specific header. */
+    lp = (struct curl_slist *) arg;
+
+    if(!lp)
+      return CURL_MIME_INVALID;
+
+    if(!rank)
+      return curl_mime_header(part, CURLMH_PREPEND, name, 1, lp);
+
+    lpp = find_mime_header(&part->headers, name, rank, &count);
+
+    if(rank != count)
+      return curl_mime_header(part, CURLMH_APPEND, name, count, lp);
+
+    lp->next = *lpp;
+    *lpp = lp;
+    lpp = &lp->next;
+    lp = *lpp;
+
+    if(lp) {
+      *lpp = lp->next;
+      lp->next = NULL;
+      curl_slist_free_all(lp);
+    }
+
+    break;
+
+  case CURLMH_PREPEND:          /* Prepend as previous sibling. */
+    lp = (struct curl_slist *) arg;
+
+    if(!lp)
+      return CURL_MIME_INVALID;
+
+    lpp = find_mime_header(&part->headers, name, rank? rank: 1, &count);
+    lp->next = *lpp;
+    *lpp = lp;
+    break;
+
+  case CURLMH_APPEND:           /* Append as next sibling. */
+    lp = (struct curl_slist *) arg;
+
+    if(!lp)
+      return CURL_MIME_INVALID;
+
+    lpp = find_mime_header(&part->headers, name, rank? rank: ~0, &count);
+
+    if(!count)
+      lpp = find_mime_header(&part->headers, NULL, ~0, &count);
+
+    if(*lpp)
+      lpp = &(*lpp)->next;
+
+    lp->next = *lpp;
+    *lpp = lp;
+    break;
+
+  case CURLMH_DELETE:           /* Delete a specific header. */
+    if(rank) {
+      lpp = find_mime_header(&part->headers, name, rank, &count);
+      lp = *lpp;
+
+      if(lp && rank == count) {
+        *lpp = lp->next;
+        lp->next = NULL;
+        curl_slist_free_all(lp);
+      }
+    }
+    else {
+      /* Delete all matching headers. */
+
+      lpp = find_mime_header(&part->headers, name, 1, &count);
+
+      while(count) {
+        lp = *lpp;
+        *lpp = lp->next;
+        lp->next = NULL;
+        curl_slist_free_all(lp);
+        lpp = find_mime_header(lpp, name, 1, &count);
+      }
+    }
+
+  default:
+    return CURL_MIME_INVALID;
+  }
+
+  return CURL_MIME_OK;
+}
+
+
+static void
+rewind_tree(curl_mimepart * part)
+{
+  switch (part->parttype) {
+
+  case CURLMIME_TYPE_BUFFER:
+    part->dep.buffer.bufferoffset = 0;
+    break;
+
+  case CURLMIME_TYPE_FILE:
+    if(part->dep.file.fd)
+      fseek(part->dep.file.fd, 0L, SEEK_SET);
+
+    break;
+
+  case CURLMIME_TYPE_CALLBACK:
+    if(part->dep.callback.proc)
+      (*part->dep.callback.proc)(part, CURLMIME_CALLBACK_REWIND, NULL,
+                                 0, part->dep.callback.userdata);
+
+    break;
+
+  case CURLMIME_TYPE_MULTIPART:
+  case CURLMIME_TYPE_CONCAT:
+    for (part = part->dep.node.first; part; part = part->next)
+      rewind_tree(part);
+  }
+}
+
+
+curl_mimectl * curl_mime_encode_context(curl_mimepart * partlist)
+{
+  curl_mimectl * ctl;
+
+  /* Create a new mime encoding context. */
+
+  ctl = calloc(sizeof *ctl, 1);
+
+  if(ctl) {
+    rewind_tree(partlist);
+    ctl->top = partlist;
+    ctl->current = partlist;
+    ctl->state = CM_BEGINOUT;
+  }
+
+  return ctl;
+}
+
+
+curl_mimepart * curl_mime_delete_context(curl_mimectl * ctl)
+{
+  curl_mimepart * part;
+
+  if(!ctl)
+    return NULL;
+
+  part = ctl->top;
+  free(ctl);
+  return part;
+}
+
+
+static size_t checkhdrlength(const char * data, size_t n, size_t llen)
+{
+  int i;
+
+  /* Cut header lines if too long. */
+
+  if(llen + n <= MAX_MIME_LEN)
+    return n;                           /* Fits in maximum line length. */
+
+  if(llen < MAX_MIME_LEN) {
+    /* Try to find a line break point before the length limit. */
+    for (i = MAX_MIME_LEN - llen; !ISBLANK(data[i]);)
+      if(!i--)                          /* No line break point. */
+        break;
+
+    switch (i) {
+
+    case -1:
+      break;
+
+    case 0:
+      if(!llen)
+        break;
+
+    default:
+      return i;
+    }
+
+  i = MAX_MIME_LEN + 1 - llen;
+  }
+  else
+    i = 0;
+
+  /* Line cannot be cut before length limit.
+     Find the next possible break point. */
+
+  for (; i < n; i++)
+      if(ISBLANK(data[i]))
+        break;
+
+  return i;
+}
+
+
+static int header_param_bounds(const char * hdr, const char * name,
+                               int * offset, int * data, int * after)
+{
+  int l;
+  const char * cp;
+
+  for (cp = hdr; *cp && *cp != ':'; cp++)
+    ;
+
+  if(*cp)
+    for(cp++; ISSPACE(*cp); cp++)
+      ;
+
+  *offset = cp - hdr;
+
+  if(name) {
+    l = strlen(name);
+
+    for (;;) {
+      while(*cp && *cp != ';')
+        cp++;
+
+      if(*cp)
+        for(cp++; ISSPACE(*cp); cp++)
+          ;
+
+      if(!*cp)
+        return 0;
+
+      if(curl_strnequal(name, cp, l)) {
+        *offset = cp - hdr;
+
+        for (cp += l; ISSPACE(*cp); cp++)
+          ;
+
+        if(*cp == '=')
+          break;
+      }
+    }
+
+    for (cp++; ISSPACE(*cp); cp++)
+      ;
+  }
+
+  for (l = 0; cp[l] && cp[l] != ';'; l++)
+    ;
+
+  while (l--)
+    if(!ISSPACE(cp[l]))
+      break;
+
+  *data = cp - hdr;
+  *after = *data + l + 1;
+  return 1;
+}
+
+
+static char * get_header_param(const char * hdr, const char * name)
+{
+  int low;
+  int high;
+  int l;
+  char * cp;
+
+  if(!header_param_bounds(hdr, name, &low, &low, &high))
+    return NULL;
+
+  hdr += low;
+  high -= low;
+
+  if(*hdr == '\'' || *hdr == '"')
+    if(high > 1 && *hdr == hdr[high - 1]) {
+      hdr++;
+      high -= 2;
+    }
+
+  cp = malloc(high + 1);
+
+  if(cp) {
+    memcpy(cp, hdr, high);
+    cp[high] = '\0';
+  }
+
+  return cp;
+}
+
+
+static int set_header_param(char * * hdrp,
+                            const char * name, const char * value, size_t vlen)
+{
+  int low;
+  int data;
+  int high;
+  int l;
+  int present;
+  int n;
+  char * cp;
+
+  n = 0;
+  cp = *hdrp;
+  l = strlen(cp);
+  present = header_param_bounds(cp, name, &low, &data, &high);
+
+  if(!present) {
+    n = 2;
+    low = l;
+    high = l;
+  }
+
+  if(!vlen)
+    vlen = strlen(value);
+
+  n += vlen - (high - low);
+
+  if(name)
+    n += 3 + strlen(name);
+
+  if(n) {
+    if(n < 0)
+      memmove(cp + high + n, cp + high, l - high + 1);
+
+    cp = realloc(cp, l + n + 1);
+
+    if(!cp)
+      return CURL_MIME_MEMORY;
+
+    if(n > 0)
+      memmove(cp + high + n, cp + high, l - high + 1);
+
+    *hdrp = cp;
+  }
+
+  cp += low;
+
+  if(!present) {
+    *cp++ = ';';
+    *cp++ = ' ';
+  }
+
+  if(name) {
+    while (*name)
+      *cp++ = *name++;
+
+    *cp++ = '=';
+    *cp++ = '"';
+  }
+
+  while (vlen--)
+    *cp++ = *value++;
+
+  if(name)
+    *cp++ = '"';
+
+  return 0;
+}
+
+
+static char *
+content_type_boundary(curl_mimepart * part)
+{
+  struct curl_slist * sl;
+  char * cp;
+  int result;
+
+  curl_mime_header(part, CURLMH_GET, "Content-Type", 1, (void *) &sl);
+
+  if(!sl)
+    return NULL;
+
+  cp = get_header_param(sl->data, NULL);
+
+  if(cp) {
+    result = curl_strnequal(cp, "multipart/", 10);
+    free(cp);
+    cp = result? get_header_param(sl->data, "boundary"): NULL;
+  }
+
+  return cp;
+}
+
+
+static int
+get_body_chunk(curl_mimepart * part, char * buf, size_t n)
+{
+  size_t i;
+  int result;
+
+  switch (part->parttype) {
+
+  case CURLMIME_TYPE_BUFFER:
+    if(!part->dep.buffer.buffer)
+      return 0;
+
+    if(part->dep.buffer.bufferlength)
+      i = part->dep.buffer.bufferlength - part->dep.buffer.bufferoffset;
+    else
+      i = strlen(part->dep.buffer.buffer + part->dep.buffer.bufferoffset);
+
+    if(!i)
+      return 0;
+
+    if(i > n)
+      i = n;
+
+    memcpy(buf, part->dep.buffer.buffer + part->dep.buffer.bufferoffset, i);
+    part->dep.buffer.bufferoffset += i;
+    result = i;
+    break;
+
+  case CURLMIME_TYPE_FILE:
+    if(!part->dep.file.fd) {
+      if(!part->dep.file.filename)
+        return -CURL_MIME_INVALID;
+
+      part->dep.file.fd = fopen(part->dep.file.filename, "rb");
+
+      if(!part->dep.file.fd)
+        return -CURL_MIME_IO;
+    }
+
+    result = fread(buf, 1, n, part->dep.file.fd);
+
+    if(result < 0)
+      result = -CURL_MIME_IO;
+
+    break;
+
+  case CURLMIME_TYPE_CALLBACK:
+    if(!part->dep.callback.proc)
+      result = 0;
+    else
+      result = (*part->dep.callback.proc)(part, CURLMIME_CALLBACK_READ, buf,
+                                          n, part->dep.callback.userdata);
+
+    break;
+
+  case CURLMIME_TYPE_CONCAT:
+    result = 0;
+
+    for (part = part->dep.node.first; part; part = part->next)
+      if((result = get_body_chunk(part, buf, n)))
+        break;
+
+    break;
+  }
+
+  return result;
+}
+
+
+static CURLMIMEcode
+require_bytes(curl_mimectl * ctl, curl_mimepart * part,
+              size_t needbyte, char * * bufptr, size_t * datalen)
+{
+  int result;
+
+  if(part->parttype == CURLMIME_TYPE_BUFFER) {
+    *datalen = 0;
+    *bufptr = NULL;
+
+    if(part->dep.buffer.buffer) {
+      *bufptr = part->dep.buffer.buffer + part->dep.buffer.bufferoffset;
+
+      if(part->dep.buffer.bufferlength)
+        *datalen = part->dep.buffer.bufferlength -
+                   part->dep.buffer.bufferoffset;
+      else
+        *datalen = strlen(*bufptr);
+    }
+
+    return CURL_MIME_OK;
+  }
+
+  *datalen = ctl->worklen - ctl->index2;
+  *bufptr = ctl->workbuf + ctl->index2;
+
+  while (*datalen < needbyte) {
+    if(ctl->index2) {
+      memcpy(ctl->workbuf, ctl->workbuf + ctl->index2, *datalen);
+      ctl->worklen = *datalen;
+      ctl->index2 = 0;
+    }
+
+    *bufptr = ctl->workbuf;
+    result = get_body_chunk(part, ctl->workbuf + ctl->worklen,
+                            sizeof ctl->workbuf - ctl->worklen);
+
+    if(result <= 0) {
+      result = -result;
+      break;
+    }
+
+    ctl->worklen += result;
+    *datalen = ctl->worklen;
+    result = CURL_MIME_OK;
+    }
+
+  return result;
+}
+
+
+static void
+consume_bytes(curl_mimectl * ctl, curl_mimepart * part, size_t numbytes)
+
+{
+  if(part->parttype == CURLMIME_TYPE_BUFFER)
+    part->dep.buffer.bufferoffset += numbytes;
+  else
+    ctl->index2 += numbytes;
+}
+
+
+int curl_mime_get(curl_mimectl * ctl, char * buf, size_t n)
+{
+  size_t i;
+  size_t j;
+  size_t l;
+  char * p;
+  int result;
+  long sr;
+  curl_mimepart * part;
+  struct curl_slist * lp;
+
+  if(!ctl || !buf || !n)
+    return -CURL_MIME_INVALID;
+
+  for (l = 0; n;) {
+    part = ctl->current;
+
+    switch (ctl->state) {
+
+    case CM_BEGINOUT:                   /* Begin output of current part. */
+      ctl->index1 = 0;
+      ctl->index2 = 0;
+      ctl->worklen = 0;
+
+      if((part->flags & MIME_FLAG_NOMIME) || (part != ctl->top &&
+         part->parent && part->parent->parttype == CURLMIME_TYPE_CONCAT))
+        ctl->state = CM_OUTBODY;
+      else {
+        /* Be sure there is a MIME-Version header in the top part. */
+        if(part == ctl->top) {
+          curl_mime_header(part, CURLMH_GET, "MIME-Version", 1, (void *) &lp);
+
+          if(!lp) {
+            lp = curl_slist_append(NULL, "MIME-Version: 1.0");
+
+            if(!lp)
+              return l? l: -CURL_MIME_MEMORY;
+
+            curl_mime_header(part, CURLMH_APPEND, NULL, 0, (void *) lp);
+          }
+        }
+
+        /* Build, complete or fix the content-type header. */
+        curl_mime_header(part, CURLMH_GET, "Content-Type", 1, (void *) &lp);
+
+        if(!lp) {
+          lp = curl_slist_append(NULL, "Content-Type: ");
+
+          if(!lp)
+            return l? l: -CURL_MIME_MEMORY;
+
+          curl_mime_header(part, CURLMH_APPEND, NULL, 0, (void *) lp);
+        }
+
+        p = get_header_param(lp->data, NULL);
+
+        if(!p)
+          return l? l: -CURL_MIME_MEMORY;
+
+        result = *p;
+        free(p);
+
+        if(!result) {
+          if(part->parttype != CURLMIME_TYPE_MULTIPART)
+            p = (char *) Curl_ContentTypeForFilename(part->showfilename, NULL);
+          else if (part == ctl->top)
+            p = "multipart/form-data";
+          else
+            p = "multipart/related";
+
+          result = set_header_param(&lp->data, NULL, p, 0);
+
+          if(result)
+            return l? l: -result;
+        }
+
+        p = get_header_param(lp->data, NULL);
+        result = curl_strnequal(p, "multipart/", 10);
+        free(p);
+        part->flags &= ~(MIME_FLAG_BASE64 | MIME_FLAG_QUOTEPRINT);
+
+        if(result) {
+          if(part->parttype == CURLMIME_TYPE_MULTIPART &&
+             part->dep.node.boundary) {
+            result = set_header_param(&lp->data,
+                                     "boundary", part->dep.node.boundary, 0);
+
+            if(result)
+              return l? l: -result;
+          }
+          else if ((p = get_header_param(lp->data, "boundary")))
+            free(p);
+          else {
+            p = Curl_FormBoundary();
+
+            if(!p)
+              return l? l: -CURL_MIME_MEMORY;
+
+            result = set_header_param(&lp->data, "boundary", p, 0);
+
+            if(part->parttype == CURLMIME_TYPE_MULTIPART)
+              part->dep.node.boundary = p;
+            else
+              free(p);
+
+            if(result)
+              return l? l: -result;
+          }
+        }
+        else {
+          /* Get the content transfer encoding from header. */
+          curl_mime_header(part, CURLMH_GET,
+            "Content-Transfer-Encoding", 1, (void *) &lp);
+
+          if(lp && (p = get_header_param(lp->data, NULL))) {
+            if(curl_strequal(p, "base64"))
+              part->flags |= MIME_FLAG_BASE64;
+            else if(curl_strequal(p, "quoted-printable"))
+              part->flags |= MIME_FLAG_QUOTEPRINT;
+          }
+        }
+
+        /* Process the Content-Disposition header. It is mandatory if:
+           _ This is a subpart of a multipart/form-data part.
+           _ There is an attachment filename.
+         */
+
+        result = 0;
+
+        if (part != ctl->top && part->parent) {
+          curl_mime_header(part->parent,
+                           CURLMH_GET, "Content-Type", 1, (void *) &lp);
+
+          if(lp) {
+            p = get_header_param(lp->data, NULL);
+
+            if(!p)
+              return l? l: -CURL_MIME_MEMORY;
+
+            result = curl_strnequal(p, "multipart/form-data", 19);
+            free(p);
+          }
+        }
+
+        if(part->showfilename || part->name || result) {
+          curl_mime_header(part,
+                           CURLMH_GET, "Content-Disposition", 1, (void *) &lp);
+
+          if(!lp) {
+            lp = curl_slist_append(NULL, "Content-Disposition: ");
+
+            if(!lp)
+              return l? l: -CURL_MIME_MEMORY;
+
+            curl_mime_header(part, CURLMH_APPEND, NULL, 0, (void *) lp);
+          }
+
+          if(result) {
+            result = set_header_param(&lp->data, NULL, "form-data", 0);
+
+            if(result)
+              return l? l: -result;
+
+            p = get_header_param(lp->data, "name");
+            free(p);
+
+            if(!p) {
+              if(part->name)
+                result = set_header_param(&lp->data,
+                                          "name", part->name, part->namelength);
+              else
+                result = set_header_param(&lp->data, "name", "???", 0);
+
+              if(result)
+                return l? l: -result;
+            }
+
+            p = NULL;
+          }
+          else {
+            p = get_header_param(lp->data, NULL);
+
+            if(!p)
+              return l? l: -CURL_MIME_MEMORY;
+
+            result = *p;
+            free(p);
+
+            if(!result) {
+              result = set_header_param(&lp->data, NULL, "attachment", 0);
+
+              if(result)
+                return l? l: -result;
+            }
+
+            p = part->name;
+          }
+
+          if (part->showfilename)
+            result = set_header_param(&lp->data,
+                                      "filename", part->showfilename, 0);
+          else if(p)
+            result = set_header_param(&lp->data,
+                                      "filename", p, part->namelength);
+          else
+            result = 0;
+
+          if(result)
+            return l? l: -result;
+        }
+
+        ctl->state = CM_OUTHDRS;
+      }
+
+      break;
+
+    case CM_OUTHDRS:                    /* Output headers. */
+      lp = part->headers;
+
+      for (i = 0; i < ctl->index1; i++)
+        lp = lp->next;
+
+      if(!lp) {
+        ctl->state = CM_OUTPRELF;
+        *buf++ = '\r';
+        l++;
+        n--;
+      }
+      else {
+        i = strlen(lp->data + ctl->index2);
+
+        if(!i) {
+          ctl->state = CM_OUTHDRLF;
+          *buf++ = '\r';
+          l++;
+          n--;
+          ctl->index1++;        /* Advance to next header. */
+          ctl->index2 = 0;
+        }
+        else {
+          /* Check if header must be continued. */
+          i = checkhdrlength(lp->data + ctl->index2, i, ctl->length);
+
+          if(!i) {
+            ctl->state = CM_OUTHDRLF;
+            *buf++ = '\r';
+            l++;
+            n--;
+          }
+          else {
+            if(i > n)
+              i = n;
+
+            memcpy(buf, lp->data + ctl->index2, i);
+            buf += i;
+            l += i;
+            ctl->index2 += i;
+            ctl->length += i;
+            n -= i;
+          }
+        }
+      }
+
+      break;
+
+    case CM_OUTHDRLF:                   /* Output LF at end of header line. */
+      ctl->length = 0;
+      ctl->state = CM_OUTHDRS;
+      *buf++ = '\n';
+      l++;
+      n--;
+      break;
+
+    case CM_OUTPRELF:                   /* Output LF after all headers. */
+      ctl->index1 = 0;
+      ctl->index2 = 0;
+      ctl->length = 0;
+      ctl->worklen = 0;
+      ctl->state = CM_OUTBODY;
+      *buf++ = '\n';
+      l++;
+      n--;
+      break;
+
+    case CM_OUTBODY:                    /* Output part body. */
+      /* Check for multipart: subparts are processed recursively. */
+      if(part->parttype == CURLMIME_TYPE_MULTIPART) {
+        /* No support for leading and trailing data. */
+        ctl->index1 = 0;
+        ctl->index2 = 0;
+
+        if(!part->dep.node.boundary)
+          part->dep.node.boundary = content_type_boundary(part);
+
+        ctl->state = CM_OUTBOUNDARY;
+        break;
+      }
+
+      /* Process data without transfer encoding. */
+
+      if(!(part->flags & MIME_FLAG_TRANSCODE) ||
+         !(part->flags & (MIME_FLAG_BASE64 | MIME_FLAG_QUOTEPRINT))) {
+        /* No transcoding needed: direct read into target buffer. */
+        result = get_body_chunk(part, buf, n);
+
+        if(result < 0)
+          return l? l: result;
+
+        if(!result)
+          ctl->state = CM_ENDBODY;
+        else {
+          buf += result;
+          l += result;
+          n -= result;
+        }
+
+        break;
+      }
+
+      /* If some encoded data has been latched, output it. */
+
+      if(ctl->encdata[0]) {
+        *buf++ = ctl->encdata[0];
+        ctl->encdata[0] = ctl->encdata[1];
+        ctl->encdata[1] = ctl->encdata[2];
+        ctl->encdata[2] = ctl->encdata[3];
+        ctl->encdata[3] = '\0';
+        l++;
+        n--;
+        break;
+      }
+
+      /* Perform the encoding. */
+
+      if(part->flags & MIME_FLAG_BASE64) {
+        if(ctl->length >= MAX_MIME_LEN) {
+          *buf++ = '\r';
+          ctl->encdata[0] = '\n';
+          ctl->length = 0;
+          l++;
+          n--;
+          break;
+        }
+
+        result = require_bytes(ctl, part, 3, &p, &i);
+        sr = 0;
+
+        switch (i) {
+
+        default:
+          sr |= (long) (p[2] & 0xFF);
+
+        case 2:
+          sr |= (long) (p[1] & 0xFF) << 8;
+
+        case 1:
+          sr |= (long) (p[0] & 0xFF) << 16;
+
+        case 0:
+          break;
+        }
+
+        if(i < 3 && result)
+          return l? l: -result;
+
+        if(!i) {
+          if(ctl->length) {
+            *buf++ = '\r';
+            ctl->encdata[0] = '\n';
+            ctl->length = 0;
+            n--;
+            l++;
+            break;
+          }
+
+          ctl->state = CM_ENDBODY;
+          break;
+        }
+
+        ctl->encdata[2] = i > 2? Curl_table64[sr & 0x3F]: '=';
+        sr >>= 6;
+        ctl->encdata[1] = i > 1? Curl_table64[sr & 0x3F]: '=';
+        sr >>= 6;
+        ctl->encdata[0] = Curl_table64[sr & 0x3F];
+        sr >>= 6;
+        *buf++ = Curl_table64[sr & 0x3F];
+        ctl->length += 4;
+        n--;
+        l++;
+        consume_bytes(ctl, part, 3);
+        break;
+      }
+      else {                                            /* Quoted printable. */
+        result = require_bytes(ctl, part, 1, &p, &i);
+
+        if(i && *p == '\r') {
+          if(i < 2)
+            result = require_bytes(ctl, part, 2, &p, &i);
+
+          if(i >= 2 && p[1] == '\n') {
+            consume_bytes(ctl, part, 2);
+            *buf++ = '\r';
+            ctl->encdata[0] = '\n';
+            ctl->length = 0;
+            l++;
+            n--;
+            break;
+          }
+        }
+
+        if(!i) {
+          if(result)
+            return l? l: -result;
+
+          if(!ctl->length) {
+            ctl->state = CM_ENDBODY;
+            break;
+          }
+
+        ctl->length = MAX_MIME_LEN;             /* Force concealed newline. */
+        }
+
+        j = 0;
+
+        if(i) {
+          switch (*p) {
+
+          case '.':
+          case '-':
+            if(ctl->length)
+              break;
+
+          case '=':
+            j = 2;
+            break;
+
+          default:
+            if(!ISPRINT(*p))
+              j = 2;
+
+            break;
+          }
+        }
+
+        if(ctl->length + j >= MAX_MIME_LEN - 2) {
+          /* Concealed newline. */
+          *buf++ = '=';
+          ctl->encdata[0] = '\r';
+          ctl->encdata[1] = '\n';
+          ctl->length = 0;
+          l++;
+          n--;
+          break;
+        }
+
+        if(j) {
+          *buf++ = '=';
+          snprintf(ctl->encdata, sizeof ctl->encdata, "%02X", *p & 0xFF);
+        }
+        else
+          *buf++ = *p++;
+
+        ctl->length += 1 + j;
+        l++;
+        n--;
+        consume_bytes(ctl, part, 1);
+      }
+
+      break;
+
+    case CM_ENDBODY:                    /* Terminate part body. */
+      if(part == ctl->top)
+        return l;                       /* End of output data. */
+
+      if(part->next) {
+        ctl->index1 = 1;                /* Generate inter-part boundary. */
+        ctl->current = part->next;
+      }
+      else {
+        ctl->index1 = 2;                /* Generate trailing boundary. */
+        ctl->current = part->parent;
+      }
+
+      ctl->index2 = 0;
+      ctl->state = CM_OUTBOUNDARY;
+      break;
+
+    case CM_OUTBOUNDARY:
+      if(ctl->index2 < 4) {
+        *buf++ = "\r\n--"[ctl->index2];
+        l++;
+        ctl->index2++;
+        n--;
+      }
+      else {
+        i = ctl->index2 - 4;
+        p = (ctl->index1 == 1? part->parent: part)->dep.node.boundary;
+
+        if(!p)
+          j = 0;
+        else
+          j = strlen(p + i);
+
+        if(!j) {
+          ctl->state = CM_OUTBOUNDTRAIL;
+          ctl->index2 = ctl->index1 == 2? 0: 2;
+        }
+        else {
+          if(j > n)
+            j = n;
+
+          memcpy(buf, p + i, j);
+          buf += j;
+          l += j;
+          ctl->index2 += j;
+          n -= j;
+        }
+      }
+
+      break;
+
+    case CM_OUTBOUNDTRAIL:              /* Boundary trailer. */
+      if(ctl->index2 < 4) {
+        *buf++ = "--\r\n"[ctl->index2++];
+        l++;
+        n--;
+      }
+      else {
+        ctl->length = 0;
+
+        switch (ctl->index1) {
+
+        case 0:                         /* Initial boundary. */
+          if(!part->dep.node.first) {
+            ctl->index1 = 2;
+            ctl->index2 = 0;
+            ctl->state = CM_OUTBOUNDARY;
+          }
+          else {
+            ctl->current = part->dep.node.first;
+            ctl->state = CM_BEGINOUT;
+          }
+
+          break;
+
+        case 1:                         /* Inter-part boundary. */
+          ctl->state = CM_BEGINOUT;
+          break;
+
+        default:                        /* Leading boundary. */
+          ctl->state = CM_ENDBODY;
+          break;
+        }
+      }
+
+      break;
+
+    default:
+      return l? l: -CURL_MIME_INTERNAL;
+    }
+  }
+
+  return l;
+}
+
+
+curl_mimectl * curl_mime_decode_context(curl_mime_parse_callback cb, void * arg)
+{
+  curl_mimectl * ctl;
+  curl_mimepart * part;
+  CURLMIMEcode result;
+
+  /* Create a new mime decoding context. */
+
+  ctl = calloc(sizeof *ctl, 1);
+
+  if(ctl) {
+    ctl->state = CM_BEGININ;
+    ctl->partproc = cb;
+    ctl->partarg = arg;
+  }
+
+  return ctl;
+}
+
+
+static int append_header_chars(curl_mimepart * part, const char * buf, size_t n)
+{
+  struct curl_slist * sl;
+  char * cp;
+  size_t i;
+
+  if(n) {
+    for (sl = part->headers; sl->next; sl = sl->next)
+      ;
+
+    i = strlen(sl->data);
+    cp = realloc(sl->data, i + n + 1);
+
+    if(!cp)
+      return CURL_MIME_MEMORY;
+
+    sl->data = cp;
+    memcpy(cp + i, buf, n);
+    cp[i + n] = '\0';
+  }
+
+  return 0;
+}
+
+
+static int mime_flush_in(curl_mimectl * ctl, size_t n)
+{
+  int result;
+  curl_mimepart * part;
+  size_t l;
+  size_t i;
+  struct curl_slist * sl;
+  char * cp;
+
+  /* Flush `n' bytes from the lookahead buffer to the current part. Return the
+     flushed byte count, or negative error. */
+
+  part = ctl->current;
+
+  for (l = 0; n || ctl->state == CM_BODYIN;) {
+    i = 0;
+
+    switch (ctl->state) {
+
+    case CM_BEGININ:
+      /* Perform user initialization of part. */
+      if(ctl->partproc) {
+        result = (*ctl->partproc)(ctl, CURLMIME_PART_NEW, part, ctl->partarg);
+
+        if(result)
+          return l? l: -result;
+      }
+
+      /* If user requested a headerless input, enter body. */
+
+      ctl->state = CM_HDRIN;
+
+      if(part->flags & MIME_FLAG_NOMIME)
+        ctl->state = CM_BODYIN;
+
+      ctl->index1 = 0;
+      ctl->index2 = 0;
+      break;
+
+    case CM_SKIP:
+      i = n;                                            /* Drop input. */
+      break;
+
+    case CM_HDRIN:
+      if(ctl->workbuf[0] == '\r') {
+        ctl->state = CM_HLEADCR;
+        i = 1;
+      }
+      else {
+        /* Allocate a new header. */
+        sl = curl_slist_append(part->headers, "");
+
+        if(!sl)
+          return l?l: -CURL_MIME_MEMORY;
+
+        part->headers = sl;
+        ctl->state = CM_HDRCONT;
+      }
+
+      break;
+
+    case CM_HDRCONT:
+      for (i = 0; i < n; i++)
+        if(ctl->workbuf[i] == '\r')
+          break;
+
+      if(!i) {
+        i++;
+        ctl->state = CM_HTRAILCR;
+        }
+      else {
+        result = append_header_chars(part, ctl->workbuf, i);
+
+        if(result)
+          return l? l: -result;
+      }
+
+      break;
+
+    case CM_HTRAILCR:
+      if(ctl->workbuf[0] == '\n') {
+        ctl->state = CM_HTRAILLF;
+        i = 1;
+      }
+      else {
+        result = append_header_chars(part, "\r", 1);
+
+        if(result)
+          return l? l: -result;
+
+        ctl->state = CM_HDRCONT;
+      }
+
+      break;
+
+    case CM_HTRAILLF:
+      if(ctl->workbuf[0] == ' ' || ctl->workbuf[0] == '\t')
+        ctl->state = CM_HDRCONT;
+      else
+        ctl->state = CM_HDRIN;
+
+      break;
+
+    case CM_HLEADCR:
+      if(ctl->workbuf[0] == '\n') {
+        i = 1;
+        ctl->state = CM_BODYIN;
+      }
+      else
+        ctl->state = CM_HDRIN;  /* CR alone cannot start a header: drop it. */
+
+      break;
+
+    case CM_BODYIN:
+      /* Give a chance to caller to set-up part type, buffer, etc. */
+
+      if(ctl->partproc) {
+        result = (*ctl->partproc)(ctl,
+                                    CURLMIME_PART_BODY, part, ctl->partarg);
+
+        if(result)
+          return l? l: -result;
+      }
+
+      /* Complete part configuration. */
+
+      if(part->parttype == CURLMIME_TYPE_NONE) {
+        /* If content-type is multipart, set as CURLTYPE_MIME_MULTIPART and
+           extract the boundary string. */
+
+        cp = content_type_boundary(part);
+
+        if(cp) {
+          result = curl_mime_setopt(part, CURLMIME_BOUNDARY, cp);
+          free(cp);
+
+          if(result)
+            return l? l: -result;
+        }
+      }
+
+      /* If type is not yet defined, use memory buffer. */
+
+      if(part->parttype == CURLMIME_TYPE_NONE)
+        curl_mime_setopt(part, CURLMIME_TYPE, CURLMIME_TYPE_BUFFER);
+
+      /* Initialize body input according to the part type. */
+
+      switch (part->parttype) {
+
+      case CURLMIME_TYPE_BUFFER:
+        if(!part->dep.buffer.buffer) {
+          result = curl_mime_setopt(part, CURLMIME_COPYBUFFER,
+                                    NULL, BUFFER_GRANULE + 1);
+
+          if(result)
+            return l? l: -result;
+
+          part->dep.buffer.bufferlength--;      /* For null terminator. */
+        }
+
+        break;
+
+      case CURLMIME_TYPE_FILE:
+        if(!part->dep.file.fd) {
+          if(!part->dep.file.filename)
+            part->dep.file.fd = tmpfile();
+          else
+            part->dep.file.fd = fopen(part->dep.file.filename, "w+b");
+
+          if(!part->dep.file.fd)
+            return l? l: -CURL_MIME_IO;
+        }
+
+        break;
+
+      case CURLMIME_TYPE_CALLBACK:
+        if(!part->dep.callback.proc)
+          return l? l: -CURL_MIME_INVALID;
+
+        break;
+
+      case CURLMIME_TYPE_MULTIPART:
+        if(!part->dep.node.boundary)
+          return l? l: -CURL_MIME_INVALID;
+
+        break;
+
+      default:
+        return l? l: -CURL_MIME_INVALID;
+      }
+
+      ctl->state = CM_BODYCONT;
+      break;
+
+    case CM_BODYCONT:
+      switch (part->parttype) {
+
+      case CURLMIME_TYPE_BUFFER:
+        i = part->dep.buffer.bufferlength - ctl->index1;
+
+        if(i > n)
+          i = n;
+
+        if(i) {
+          memcpy(part->dep.buffer.buffer + ctl->index1, ctl->workbuf, i);
+          ctl->index1 += i;
+        }
+        else if(part->flags & MIME_FLAG_BUFFERALLOC) {
+          cp = realloc(part->dep.buffer.buffer,
+                       part->dep.buffer.bufferlength + BUFFER_GRANULE + 1);
+
+          if(!cp)
+            return l? l: -CURL_MIME_MEMORY;
+
+          part->dep.buffer.buffer = cp;
+          part->dep.buffer.bufferlength += BUFFER_GRANULE;
+        }
+        else
+          return l? l: -CURL_MIME_MEMORY;
+
+        break;
+
+      case CURLMIME_TYPE_FILE:
+        result = fwrite(ctl->workbuf, 1, n, part->dep.file.fd);
+
+        if (result <= 0)
+          return l? l: -CURL_MIME_IO;
+
+        i = result;
+        break;
+
+      case CURLMIME_TYPE_CALLBACK:
+        result = (*part->dep.callback.proc)(part, CURLMIME_CALLBACK_WRITE,
+                                            ctl->workbuf, n,
+                                            part->dep.callback.userdata);
+
+        if (result <= 0)
+          return l? l: result? result: -CURL_MIME_IO;
+
+        i = result;
+        break;
+
+      default:                          /* Multipart before 1st boundary. */
+        i = l;                          /* Drop data bytes. */
+        break;
+      }
+
+      break;
+    }
+
+    /* Consume bytes in lookahead buffer. */
+
+    if(i) {
+      if((ctl->worklen -= i))
+        memcpy(ctl->workbuf, ctl->workbuf + i, ctl->worklen);
+
+      n -= i;
+      l += i;
+    }
+  }
+
+  return l;
+}
+
+
+static int mime_drain_in(curl_mimectl * ctl)
+{
+  curl_mimepart * part;
+  int result;
+
+  if(ctl->state == CM_BODYCONT) {
+    part = ctl->current;
+
+    switch (part->parttype) {
+
+    case CURLMIME_TYPE_BUFFER:
+      if(part->flags & MIME_FLAG_BUFFERALLOC) {
+        part->dep.buffer.buffer = realloc(part->dep.buffer.buffer,
+                                          ctl->index1 + 1);
+        part->dep.buffer.buffer[ctl->index1] = '\0';
+      }
+      else if(ctl->index1 < part->dep.buffer.bufferlength)
+        part->dep.buffer.buffer[ctl->index1] = '\0';
+
+      part->dep.buffer.bufferlength = ctl->index1;
+      break;
+
+    case CURLMIME_TYPE_FILE:
+      if(fflush(part->dep.file.fd))
+        return CURL_MIME_IO;
+
+      if(!part->dep.file.filename)
+        fseek(part->dep.file.fd, 0L, SEEK_SET);
+      else {
+        fclose(part->dep.file.fd);
+        part->dep.file.fd = (FILE *) NULL;
+      }
+
+      break;
+
+    case CURLMIME_TYPE_CALLBACK:
+      result = (*part->dep.callback.proc)(part, CURLMIME_CALLBACK_WEOF,
+                                          NULL, 0, part->dep.callback.userdata);
+
+      if (result)
+        return result;
+
+      break;
+    }
+  }
+
+  ctl->state = CM_SKIP;
+  return 0;
+}
+
+
+int
+curl_mime_put(curl_mimectl * ctl, const char * buf, size_t n)
+{
+  CURLMIMEcode result;
+  curl_mimepart * part;
+  curl_mimepart * matchpart;    /* Part matching the current boundary. */
+  int matchlast;                /* Part terminated by a trailing boundary. */
+  int maybelast;                /* Input line may be a trailing boundary. */
+  size_t l;
+  int i;
+  int j;
+
+  if(!ctl)
+    return -CURL_MIME_INVALID;
+
+  /* Allocate top part if needed. */
+  if(!ctl->top) {
+    part = curl_mime_new(CURLMIME_TYPE_NONE);
+
+    if(!part)
+      return -CURL_MIME_MEMORY;
+
+    ctl->top = part;
+    ctl->current = part;
+  }
+  else if(!ctl->current)
+    return -CURL_MIME_INVALID;                  /* Already terminated. */
+
+  /* Be sure we do not have more than a CRLFdataCRLF in lookahead buffer.
+     This situation may occur if lookahead flushing failed in the previous
+     call. */
+
+  if(ctl->worklen > 2) {
+    for (l = ctl->worklen - 2; l--;)
+      if(ctl->workbuf[l] == '\r' && ctl->workbuf[l + 1] == '\n')
+        break;
+
+    if((l + 1) > 1) {                   /* Unsigned may have undeflowed. */
+      do {
+        result = mime_flush_in(ctl, l);
+
+        if(result < 0)
+          return result;
+
+        l -= result;
+      } while (l);
+    }
+  }
+
+  if(!n) {
+    /* This is an end-of-write request. */
+
+    while (ctl->worklen) {
+      result = mime_flush_in(ctl, ctl->worklen);
+
+      if(result <= 0)
+        return result? result: -CURL_MIME_IO;
+    }
+
+    /* Terminate all parts. */
+
+    while ((part = ctl->current)) {
+      result = mime_drain_in(ctl);
+
+      if(!result && ctl->partproc)
+        result = (*ctl->partproc)(ctl, CURLMIME_PART_END, part, ctl->partarg);
+
+      ctl->current = part->parent;
+
+      if(result)
+        return -result;
+    }
+
+    return 0;
+  }
+
+  if(!buf)
+    return -CURL_MIME_INVALID;
+
+
+  /* Accumulate characters in buffer, checking for a multipart boundary. */
+  for (l = 0;;) {
+    /* Make sure we have a full line. */
+    if(ctl->worklen <= 2 || ctl->workbuf[ctl->worklen - 2] != '\r' ||
+           ctl->workbuf[ctl->worklen - 1] != '\n') {
+      if(ctl->worklen >= sizeof ctl->workbuf) {
+        /* Long "line": flush it as data. */
+        result = mime_flush_in(ctl, ctl->worklen - 1);  /* Keep trailing CR. */
+
+        if(result <= 0)
+          return l? l: result? result: -CURL_MIME_IO;
+      }
+      else if(!n)
+        break;
+      else {
+        /* Accumulate data. */
+        ctl->workbuf[ctl->worklen++] = *buf++;
+        n--;
+        l++;
+      }
+
+      continue;
+    }
+
+    /* End of line reached. Check for boundary. */
+    if(ctl->worklen < 6 || memcmp(ctl->workbuf, "\r\n--", 4)) {
+      /* Not a boundary. Parse as data. */
+      result = mime_flush_in(ctl, ctl->worklen - 2);    /* Keep CRLF. */
+
+      if(result <= 0)
+        return l? l: result? result: -CURL_MIME_IO;
+
+      continue;
+    }
+
+    /* Line is a boundary candidate. Strip trailing spaces. */
+    for (j = ctl->worklen - 2; ISSPACE(ctl->workbuf[j]); j--)
+      ;
+
+    maybelast = j >= 5 && ctl->workbuf[j] == '-' && ctl->workbuf[j - 1] == '-';
+
+    j += 1 - 4;                         /* Length without the leading CRLF--. */
+
+    matchpart = NULL;
+
+    for (part = ctl->current; part; part = part->parent)
+      if(part->parttype == CURLMIME_TYPE_MULTIPART &&
+         part->dep.node.boundary) {
+        i = strlen(part->dep.node.boundary);
+
+        if(!memcmp(part->dep.node.boundary, ctl->workbuf + 4, i)) {
+          /* This boundary matches line prefix. Check if full match. */
+          if(i == j) {
+            matchpart = part;
+            matchlast = 0;
+            break;
+          }
+          else if(maybelast && j == i + 2) {
+            matchpart = part;
+            matchlast = 1;
+            break;
+          }
+        }
+      }
+
+    if(!matchpart) {
+      /* Not a boundary. Parse as data. */
+      result = mime_flush_in(ctl, ctl->worklen - 2);
+
+      if(result <= 0)
+        return l? l: result? result: -CURL_MIME_IO;
+
+      continue;
+    }
+
+    /* Line matches a boundary. Terminate subparts. */
+
+    while ((part = ctl->current) != matchpart) {
+      result = mime_drain_in(ctl);
+
+      if(!result && ctl->partproc)
+        result = (*ctl->partproc)(ctl, CURLMIME_PART_END, part, ctl->partarg);
+
+      ctl->current = part->parent;
+
+      if(result)
+        return l? l: -result;
+    }
+
+    /* The current part is the multipart with the boundary found.
+       If this is a terminating boundary, set state to ignore all input until
+         another boundary is found.
+       Else create a new subpart.
+     */
+
+    ctl->worklen = 0;                           /* Throw boundary away. */
+
+    if(matchlast) {
+      result = mime_drain_in(ctl);
+
+      if(result)
+        return l? l: -result;
+    }
+    else {
+      /* Create a new part. */
+      part = curl_mime_new(CURLMIME_TYPE_NONE);
+
+      if(!part)
+        return l? l: -CURL_MIME_MEMORY;
+
+      curl_mime_setopt(ctl->current, CURLMIME_LASTCHILD, part);
+      ctl->current = part;
+      ctl->state = CM_BEGININ;
+    }
+  }
+
+  return l;
+}
+
+
+void
+curl_cid_delete(curl_cids * cids)
+{
+  if(cids) {
+    if(cids->mid)
+      free(cids->mid);
+
+    if(cids->hash)
+      Curl_hash_destroy(cids->hash);
+
+    free((char *) cids);
+  }
+}
+
+
+static int
+id_string(const char * header, curl_mimepart * part, char * * dst)
+{
+  struct curl_slist * sl;
+  char * p;
+  size_t i;
+
+  *dst = NULL;
+  curl_mime_header(part, CURLMH_GET, header, 1, (void *) &sl);
+
+  if(!sl)
+    return 0;           /* Not found: OK. */
+
+  p = get_header_param(sl->data, NULL);
+
+  if(!p)
+    return -1;          /* Allocation error. */
+
+  /* Suppress enclosing "<" and ">". */
+  i = strlen(p);
+
+  if(*p == '<' && p[i - 1] == '>') {
+    memcpy(p, p + 1, i - 2);
+    p[i - 2] = '\0';
+  }
+
+  *dst = p;
+  return 0;
+}
+
+
+static int
+get_cids(curl_cids * cids, curl_mimepart * part)
+{
+  char * p;
+
+  if(!part)
+    return 0;
+
+  if(id_string("Content-ID", part, &p))
+    return -1;
+
+  if(p) {
+    if(!Curl_hash_add(cids->hash, p, strlen(p), part)) {
+      free(p);
+      return -1;
+    }
+
+    free(p);
+  }
+
+  if(part->parttype == CURLMIME_TYPE_MULTIPART)
+    for (part = part->dep.node.first; part; part = part->next)
+      if(get_cids(cids, part))
+        return -1;
+
+  return 0;
+}
+
+
+static void
+cid_destructor(void * p)
+{
+  /* Nothing to do. */
+}
+
+
+curl_cids *
+curl_cid_index(curl_mimepart * toppart)
+{
+  curl_cids * cids;
+
+  if(!toppart)
+    return NULL;
+
+  cids = (curl_cids *) calloc(sizeof *cids, 1);
+
+  if(!cids)
+    return NULL;
+
+  cids->mimetree = toppart;
+  cids->hash = Curl_hash_alloc(CID_HASH_SIZE, Curl_hash_str,
+                               Curl_str_key_compare, cid_destructor);
+
+  if(!cids->hash) {
+    curl_cid_delete(cids);
+    return NULL;
+  }
+
+  if(id_string("Message-ID", toppart, &cids->mid) || get_cids(cids, toppart)) {
+    curl_cid_delete(cids);
+    return NULL;
+  }
+
+  return cids;
+}
+
+
+static char *
+unescape(CURL * * curl, const char * s, size_t n)
+
+{
+  if(!*curl)
+    if(!(*curl = curl_easy_init()))
+      return NULL;
+
+  return curl_easy_unescape(*curl, s, n, NULL);
+}
+
+
+curl_mimepart *
+curl_cid_lookup(CURL * curl, curl_cids * cids, char * id, size_t idlen)
+{
+  char * p;
+  char * cp;
+  CURL * lcurl;
+  curl_mimepart * part;
+
+  if(!cids || !id)
+    return NULL;
+
+  lcurl = curl;
+
+  if(!idlen)
+    idlen = strlen(id);
+
+  /* ID can be an cid or mid URI or a (possibly "<...>" enclosed) CID or MID. */
+
+  if(idlen > 4 &&
+     (curl_strnequal(id, "mid:", 4) || curl_strnequal(id, "cid:", 4))) {
+    /* ID is an URI. */
+    idlen -= 4;
+
+    if(*id == 'm' || *id == 'M') {
+      /* MID URI. */
+      id += 4;
+
+      /* Check if the MID contains a CID. */
+      cp = id + idlen;
+      for (p = id; p != cp; p++)
+        if(*p == '/')
+          break;
+
+      cp = p != id? unescape(&lcurl, id, p - id): strdup("");
+
+      if(!cp) {
+        if(lcurl != curl)
+          curl_easy_cleanup(lcurl);
+
+        return NULL;
+      }
+
+      if(!cids->mid || strcmp(cids->mid, cp)) {
+        if(lcurl != curl)
+          curl_easy_cleanup(lcurl);
+
+        free(cp);
+        return NULL;                            /* MID does not match. */
+      }
+
+      free(cp);
+      idlen -= p - id;
+
+      if(!idlen) {
+        if(lcurl != curl)
+          curl_easy_cleanup(lcurl);
+
+        return cids->mimetree;          /* MID URI does not contain a CID. */
+      }
+
+      id = p + 1;
+      idlen--;
+    }
+    else {
+      /* CID URI. */
+      id += 4;
+    }
+
+    /* Process the CID. */
+    cp = idlen? unescape(&lcurl, id, idlen): strdup("");
+
+    if(!cp) {
+      if(lcurl != curl)
+        curl_easy_cleanup(lcurl);
+
+      return NULL;
+    }
+
+    part = (curl_mimepart *) Curl_hash_pick(cids->hash, cp, strlen(cp));
+    free(cp);
+
+    if(lcurl != curl)
+      curl_easy_cleanup(lcurl);
+
+    return part;
+  }
+
+  /* This is not an URI. Drop "<" ... ">" delimiters if some. */
+
+  if(*id == '<' && id[idlen - 1] == '>') {
+    id++;
+    idlen -= 2;
+  }
+
+  part = (curl_mimepart *) Curl_hash_pick(cids->hash, id, idlen);
+
+  if(part)
+    return part;
+
+  if(cids->mid && !strncmp(cids->mid, id, idlen) && !cids->mid[idlen - 1])
+    return cids->mimetree;
+
+  return NULL;
+}

--- lib/mime.h
+++ lib/mime.h
@@ -0,0 +1,222 @@
+#ifndef __MIME_H
+#define __MIME_H
+
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * $Id$
+ ***************************************************************************/
+
+/* MIME part types. */
+typedef enum {
+  CURLMIME_TYPE_NONE,                   /* Untyped. */
+  CURLMIME_TYPE_BUFFER,                 /* Data in memory. */
+  CURLMIME_TYPE_FILE,                   /* Data in file. */
+  CURLMIME_TYPE_CALLBACK,               /* Data from/to callback procedure. */
+  CURLMIME_TYPE_MULTIPART,              /* Structured part. */
+  CURLMIME_TYPE_CONCAT,                 /* Subparts are concatenated data. */
+  CURLMIME_TYPE_LAST
+} CURLMIMEtype;
+
+/* MIME error codes. */
+typedef enum {
+  CURL_MIME_OK,                 /* OK. */
+  CURL_MIME_INVALID,            /* Invalid argument. */
+  CURL_MIME_MEMORY,             /* Memory allocation error. */
+  CURL_MIME_IO,                 /* I/O error. */
+  CURL_MIME_INTERNAL,           /* Internal error. */
+  CURL_MIME_LAST
+} CURLMIMEcode;
+
+/* MIME parse callback functions. */
+typedef enum {
+  CURLMIME_PART_NEW,
+  CURLMIME_PART_BODY,
+  CURLMIME_PART_END,
+  CURLMIME_PART_LAST,
+} CURLMIMEparse;
+
+/* MIME callback functions. */
+typedef enum {
+  CURLMIME_CALLBACK_READ,
+  CURLMIME_CALLBACK_WRITE,
+  CURLMIME_CALLBACK_WEOF,
+  CURLMIME_CALLBACK_REWIND,
+  CURLMIME_CALLBACK_CLOSE,
+  CURLMIME_CALLBACK_LAST
+} CURLMIMEfunc;
+
+typedef struct curl_mimepart    curl_mimepart;          /* Forward. */
+typedef struct curl_mimectl     curl_mimectl;           /* Forward. */
+
+typedef int     (*curl_mime_callback)(curl_mimepart * part, CURLMIMEfunc func,
+                                      void * buf, size_t n, void * userdata);
+
+/* MIME part structure. */
+struct curl_mimepart {
+  curl_mimepart *       parent;         /* Parent mime part. */
+  curl_mimepart *       prev;           /* Previous part. */
+  curl_mimepart *       next;           /* Next part. */
+  char *                name;           /* Part name. */
+  char *                showfilename;   /* Transmitted file name. */
+  struct curl_slist *   headers;        /* Part MIME headers. */
+  size_t                namelength;     /* Name length: 0 if null-terminated. */
+  unsigned int          flags;          /* Various flags: see below. */
+  CURLMIMEtype          parttype;       /* This part type. */
+  union {                               /* Varies depending on part type. */
+    struct {                            /* parttype: CURLMIME_TYPE_BUFFER. */
+      char *            buffer;         /* Pointer to data. */
+      size_t            bufferlength;   /* Data length. */
+      size_t            bufferoffset;   /* Current offset in buffer. */
+    }           buffer;
+    struct {                            /* parttype :CURLMIME_TYPE_FILE. */
+      char *            filename;       /* Local file name. */
+      FILE *            fd;             /* File descriptor. */
+    }           file;
+    struct {                            /* parttype: CURLMIME_TYPE_CALLBACK. */
+      curl_mime_callback        proc;   /* Callback procedure. */
+      void *            userdata;       /* User parameter to callback proc. */
+    }           callback;
+    struct {                            /* parttype: CURLMIME_TYPE_MULTIPART */
+                                        /*   or CURLMIME_TYPE_CONCAT. */
+      curl_mimepart *   first;          /* First subpart. */
+      curl_mimepart *   last;           /* Last subpart. */
+      char *            boundary;       /* Multipart boundary. */
+    }           node;
+  }     dep;
+};
+
+
+#define MIME_WORKBUF_SIZE       4096    /* Work buffer size. */
+
+typedef CURLMIMEcode (* curl_mime_parse_callback)(curl_mimectl * ctl,
+                                                  CURLMIMEparse func,
+                                                  curl_mimepart * part,
+                                                  void * userdata);
+
+/* MIME control structure. */
+struct curl_mimectl {
+  curl_mimepart *       top;            /* Top part. */
+  curl_mimepart *       current;        /* Current I/O part. */
+  curl_mime_parse_callback partproc;    /* Called for a new part. */
+  void *                partarg;        /* User argument to partproc(). */
+  unsigned int          state;          /* Current I/O state. */
+  unsigned int          index1;         /* Parse engine index 1. */
+  unsigned int          index2;         /* Parse engine index 2. */
+  unsigned int          length;         /* Current line length. */
+  char                  encdata[4];     /* Latched encoded data. */
+  unsigned int          worklen;        /* Lookahead character count. */
+  char                  workbuf[MIME_WORKBUF_SIZE];     /* Work buffer. */
+};
+
+/* MIME part flags. */
+#define MIME_FLAG_NAMEALLOC     (1 << 0)        /* Allocated name. */
+#define MIME_FLAG_BUFFERALLOC   (1 << 1)        /* Allocated buffer. */
+#define MIME_FLAG_TMPFILE       (1 << 2)        /* Delete file after use. */
+#define MIME_FLAG_RMTFILE       (1 << 3)        /* Transmitted as a file. */
+#define MIME_FLAG_NOMIME        (1 << 4)        /* Do not prepend headers. */
+#define MIME_FLAG_TRANSCODE     (1 << 5)        /* Process transfer encoding. */
+#define MIME_FLAG_BASE64        (1 << 6)        /* Base64 transfer encoding. */
+#define MIME_FLAG_QUOTEPRINT    (1 << 7)        /* Quoted printable encoding. */
+
+
+/* MIME options. */
+typedef enum {
+  CURLMIME_NOP,                 /* Do nothing. */
+  CURLMIME_TYPE,                /* Get/set part type. */
+  CURLMIME_PTRNAME,             /* Get/set part name. */
+  CURLMIME_COPYNAME,            /* Get/set part name after copy. */
+  CURLMIME_PTRBUFFER,           /* Get/set buffer. */
+  CURLMIME_COPYBUFFER,          /* Get/set buffer after copy. */
+  CURLMIME_HEADERLIST,          /* Get/set header list. */
+  CURLMIME_ISFILE,              /* Get/set file transmitted flag. */
+  CURLMIME_SHOWFILENAME,        /* Get/set transmitted file name. */
+  CURLMIME_FILENAME,            /* Get/set local file name. */
+  CURLMIME_FD,                  /* Get/set file descriptor. */
+  CURLMIME_TMPFILE,             /* Get/set temporary file flag. */
+  CURLMIME_CALLBACK,            /* Get/set callback procedure. */
+  CURLMIME_CALLBACKARG,         /* Get/set user callback argument. */
+  CURLMIME_ASIS,                /* Get/set the "no MIME" flag. */
+  CURLMIME_TRANSCODE,           /* Get/set the transfer encoding flag. */
+  CURLMIME_FIRSTCHILD,          /* Get first child/prepend child. */
+  CURLMIME_LASTCHILD,           /* Get last child/append child. */
+  CURLMIME_PREVSIBLING,         /* Get previous part/insert part before. */
+  CURLMIME_NEXTSIBLING,         /* Get next part/insert part after. */
+  CURLMIME_FIRSTSIBLING,        /* Get first list part/prepend part to list. */
+  CURLMIME_LASTSIBLING,         /* Get last list part/append part to list. */
+  CURLMIME_BOUNDARY,            /* Get/set multipart boundary. */
+  CURLMIME_UNLINK,              /* Unlink part from its parent (setopt only). */
+  CURLMIME_PARENT,              /* Get part's parent (getopt only). */
+  CURLMIME_NAMELENGTH,          /* Get name length (getopt only). */
+  CURLMIME_BUFFERLENGTH,        /* Get buffer length (getopt only). */
+  CURLMIME_LAST
+} CURLMIMEoption;
+
+
+/* MIME header options. */
+typedef enum {
+  CURLMH_NOP,                   /* Do nothing. */
+  CURLMH_GET,                   /* Get a specific header. */
+  CURLMH_COUNT,                 /* Count headers. */
+  CURLMH_SET,                   /* Set a specific header. */
+  CURLMH_APPEND,                /* Append as next sibling. */
+  CURLMH_PREPEND,               /* Prepend as previous sibling. */
+  CURLMH_DELETE,                /* Delete a specific header. */
+  CURLMH_LAST
+} CURLMHoption;
+
+
+/* CID index table. */
+typedef struct {
+  char *                mid;            /* Message ID of the structure. */
+  curl_mimepart *       mimetree;       /* Target MIME structure. */
+  struct curl_hash *    hash;           /* CID table. */
+} curl_cids;
+
+
+/* Prototypes. */
+
+CURL_EXTERN curl_mimepart * curl_mime_new(CURLMIMEtype parttype);
+CURL_EXTERN CURLMIMEcode curl_mime_vsetopt(curl_mimepart * part,
+                                          CURLMIMEoption option, va_list param);
+CURL_EXTERN CURLMIMEcode curl_mime_setopt(curl_mimepart * part,
+                                          CURLMIMEoption option, ...);
+CURL_EXTERN CURLMIMEcode curl_mime_getopt(curl_mimepart * part,
+                                          CURLMIMEoption option, void * value);
+CURL_EXTERN CURLMIMEcode curl_mime_delete(curl_mimepart * part);
+CURL_EXTERN CURLMIMEcode curl_mime_header(curl_mimepart * part,
+                                          CURLMHoption option,
+                                          const char * name, int rank,
+                                          void * arg);
+
+CURL_EXTERN curl_mimectl * curl_mime_encode_context(curl_mimepart * partlist);
+CURL_EXTERN curl_mimepart * curl_mime_delete_context(curl_mimectl * ctl);
+CURL_EXTERN int curl_mime_get(curl_mimectl * ctl, char * buf, size_t n);
+CURL_EXTERN curl_mimectl *
+                        curl_mime_decode_context(curl_mime_parse_callback cb,
+                                                 void * arg);
+CURL_EXTERN int curl_mime_put(curl_mimectl * ctl, const char * buf, size_t n);
+
+CURL_EXTERN curl_cids * curl_cid_index(curl_mimepart * toppart);
+CURL_EXTERN curl_mimepart * curl_cid_lookup(CURL * curl, curl_cids * cids,
+                                            char * id, size_t idlen);
+CURL_EXTERN void curl_cid_delete(curl_cids * cids);
+
+#endif

--- lib/test.c
+++ lib/test.c
@@ -0,0 +1,257 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#include "mime.h"
+
+
+typedef struct {
+	char *		data;
+	size_t		index;
+}		cbdata;
+
+
+void
+show_part(curl_cids * cids, char * id)
+
+{
+	curl_mimepart * tree;
+	curl_mimectl * ctl;
+	int i;
+	int j;
+	int seeneol;
+	char buf[256];
+
+	printf("\nContent of %s:", id);
+	tree = curl_cid_lookup(NULL, cids, id, 0);
+
+	if(!tree) {
+		printf("(NULL)\n");
+		return;
+		}
+
+	printf("\n");
+	ctl = curl_mime_encode_context(tree);
+	seeneol = 1;
+
+	while ((i = curl_mime_get(ctl, buf, sizeof buf)) > 0) {
+		for (j = 0; j < i; j++) {
+			switch (buf[j]) {
+
+			case '\r':
+				continue;
+
+			case '\n':
+				seeneol = 1;
+				break;
+
+			default:
+				if (seeneol)
+					putchar('\t');
+
+				seeneol = 0;
+				break;
+				}
+
+			putchar(buf[j]);
+			}
+		}
+
+	if (!seeneol)
+		putchar('\n');
+
+	curl_mime_delete_context(ctl);
+
+	/* Delete the MIME-Version header (inserted by encoding). */
+  	curl_mime_header(tree, CURLMH_DELETE, "MIME-Version", 1, (void *) NULL);
+}
+
+
+CURLMIMEcode parse_callback(curl_mimectl * ctl, CURLMIMEparse func,
+					curl_mimepart * part, void * userdata)
+
+{
+	struct curl_slist * sl;
+
+	switch (func) {
+
+	case CURLMIME_PART_BODY:
+		/* If XML subtype, use a temporary file. */
+
+		if (curl_mime_header(part, CURLMH_GET,
+		    "content-type", 0, &sl) != CURL_MIME_OK || !sl || !sl->data)
+			break;
+
+		if (strstr(sl->data, "/xml"))
+			curl_mime_setopt(part, CURLMIME_TYPE,
+			    CURLMIME_TYPE_FILE);
+
+		break;
+		}
+
+	return CURL_MIME_OK;
+}
+
+
+int
+mime_callback(curl_mimepart * part, CURLMIMEfunc func, void * buf, size_t n,
+								void * userdata)
+
+{
+	int i;
+	cbdata * cbarg;
+
+	cbarg = (cbdata *) userdata;
+
+	switch (func) {
+
+	case CURLMIME_CALLBACK_REWIND:
+		cbarg->index = 0;
+		return CURL_MIME_OK;
+
+	case CURLMIME_CALLBACK_READ:
+		i = strlen(cbarg->data + cbarg->index);
+
+		if (i > n)
+			i = n;
+
+		if (i)
+			memcpy(buf, cbarg->data + cbarg->index, i);
+
+		cbarg->index += i;
+		return i;
+		}
+
+	return -CURL_MIME_INVALID;		/* Unsupported argument. */
+}
+
+
+main(int argc, char * * argv)
+
+{
+	curl_mimepart * bp1;
+	curl_mimepart * bp2;
+	curl_mimectl * hp;
+	curl_cids * cids;
+	int i;
+	int j;
+	int n;
+	cbdata cbarg;
+	char buf[128];
+	char mimebuf[40000];
+
+	curl_global_init(CURL_GLOBAL_ALL);
+
+	/* Build a MIME document with tree calls. */
+
+	bp2 = curl_mime_new(CURLMIME_TYPE_CONCAT);
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-Encoding: 8bit"));
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-ID: <attached-text@cid.org>"));
+
+	bp1 = curl_mime_new(CURLMIME_TYPE_BUFFER);
+	curl_mime_setopt(bp1, CURLMIME_COPYBUFFER,
+	    "this is\r\na test sen", 0);
+	curl_mime_setopt(bp2, CURLMIME_LASTCHILD, bp1);
+
+	cbarg.data = "tence !\r\n";
+	cbarg.index = 0;
+	bp1 = curl_mime_new(CURLMIME_TYPE_CALLBACK);
+	curl_mime_setopt(bp1, CURLMIME_CALLBACK, mime_callback);
+	curl_mime_setopt(bp1, CURLMIME_CALLBACKARG, &cbarg);
+	curl_mime_setopt(bp2, CURLMIME_LASTCHILD, bp1);
+
+	bp1 = curl_mime_new(CURLMIME_TYPE_MULTIPART);
+	curl_mime_setopt(bp1, CURLMIME_PTRNAME, "formpart1");
+	curl_mime_setopt(bp1, CURLMIME_LASTCHILD, bp2);
+
+	bp2 = curl_mime_new(CURLMIME_TYPE_BUFFER);
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-Type: application/XML"));
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-Encoding: 8bit"));
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-ID: <xml@cid.org>"));
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-Transfer-Encoding: quoted-printable"));
+	curl_mime_setopt(bp2, CURLMIME_TRANSCODE, 1);
+	curl_mime_setopt(bp2, CURLMIME_PTRBUFFER, "<?XML version=\"1.0\"?>\r\n\
+<body ref=\"cid:attached_text@cid.org\">\r\n\
+</body>\r\n", 0);
+	curl_mime_setopt(bp1, CURLMIME_LASTCHILD, bp2);
+
+	bp2 = curl_mime_new(CURLMIME_TYPE_MULTIPART);
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Content-Type: ; start=\"cid:xml@cid.org\"; type=application/XML"));
+	curl_mime_header(bp2, CURLMH_APPEND, NULL, 0, curl_slist_append(NULL,
+	    "Message-ID: <mytest@cid.org>"));
+	curl_mime_setopt(bp2, CURLMIME_LASTCHILD, bp1);
+
+	bp1 = curl_mime_new(CURLMIME_TYPE_BUFFER);
+	curl_mime_setopt(bp1, CURLMIME_PTRBUFFER, "First level part", 0);
+	curl_mime_setopt(bp1, CURLMIME_PTRNAME, "formpart2");
+	curl_mime_setopt(bp2, CURLMIME_LASTCHILD, bp1);
+
+	/* Encode it. */
+
+	hp = curl_mime_encode_context(bp2);
+	n = 0;
+
+	while ((i = curl_mime_get(hp, buf, sizeof buf)) > 0) {
+		memcpy(mimebuf + n, buf, i);
+		n += i;
+		}
+
+/*	fwrite(mimebuf, 1, n, stdout);		/* Show it in MIME form. */
+
+	curl_mime_delete_context(hp);
+	curl_mime_delete(bp1);
+
+	/* Decode (i.e.: parse) the MIME document encoded above. */
+
+	hp = curl_mime_decode_context(parse_callback, NULL);
+
+	for (i = 0; i < n;) {
+		j = n - i;
+
+		if (j > 100)
+			j = 100;
+
+		curl_mime_put(hp, mimebuf + i, j);
+		i += j;
+		}
+
+	bp1 = curl_mime_delete_context(hp);
+
+	/* Show some parts from their ID. */
+
+	cids = curl_cid_index(bp1);
+	show_part(cids, "<attached-text@cid.org>");
+	show_part(cids, "xml@cid.org");
+	show_part(cids, "does-not-exist@cid.org");
+	show_part(cids, "cid:xml%40cid.org");
+	show_part(cids, "mid:mytest@cid.org/attached-text@cid.org");
+	show_part(cids, "mid:mybadtest@cid.org/attached-text@cid.org");
+	show_part(cids, "mid:mytest@cid.org");
+	curl_cid_delete(cids);
+
+	/* Re-encode it. In get loop, check if it matches the original one. */
+
+	hp = curl_mime_encode_context(bp1);
+	j = 0;
+
+	while ((i = curl_mime_get(hp, buf, sizeof buf)) > 0) {
+		if (memcmp(mimebuf + j, buf, i))
+			fprintf(stderr, "Mismatch %d\n", j);
+
+		j += i;
+		}
+
+	curl_mime_delete_context(hp);
+	curl_mime_delete(bp1);
+	curl_global_cleanup();
+	exit(0);
+}


