libsoup r1176 - in trunk: . docs/reference libsoup tests



Author: danw
Date: Wed Oct  1 21:53:26 2008
New Revision: 1176
URL: http://svn.gnome.org/viewvc/libsoup?rev=1176&view=rev

Log:
	* libsoup/soup-multipart.c: New type and methods for working with
	multipart HTTP bodies (eg, multipart/form-data and
	multipart/byte-ranges)

	* libsoup/soup-message-headers.c (soup_message_headers_get_ranges)
	(soup_message_headers_set_ranges)
	(soup_message_headers_set_range)
	(soup_message_headers_get_content_range)
	(soup_message_headers_set_content_range): New methods for dealing
	with the Range and Content-Range headers.

	* libsoup/soup-form.h (SOUP_FORM_MIME_TYPE_URLENCODED)
	(SOUP_FORM_MIME_TYPE_MULTIPART): #define these MIME types here

	* libsoup/soup-form.c (soup_form_decode_multipart): new utility
	for parsing multipart/form-data forms.
	(soup_form_request_new_from_multipart): new utility for
	constructing multipart/form-data forms

	* libsoup/soup-headers.c (soup_headers_parse): this is now
	non-static, for use by soup-multipart

	* libsoup/soup-message-server-io.c (get_response_headers)
	(handle_partial_get): if the client requested a partial GET, and
	the SoupServer is returning the full body, rebuild the response to
	include only the requested range instead

	* tests/forms-test.c: renamed from query-test and updated to do
	both application/x-www-form-urlencoded and multipart/form-data
	tests

	* tests/range-test.c: test of Range/Content-Range functionality


Added:
   trunk/libsoup/soup-multipart.c
   trunk/libsoup/soup-multipart.h
   trunk/tests/forms-test.c
      - copied, changed from r1173, /trunk/tests/query-test.c
   trunk/tests/range-test.c
Removed:
   trunk/tests/query-test.c
Modified:
   trunk/ChangeLog
   trunk/docs/reference/libsoup-2.4-docs.sgml
   trunk/docs/reference/libsoup-2.4-sections.txt
   trunk/libsoup/Makefile.am
   trunk/libsoup/soup-form.c
   trunk/libsoup/soup-form.h
   trunk/libsoup/soup-headers.c
   trunk/libsoup/soup-headers.h
   trunk/libsoup/soup-message-headers.c
   trunk/libsoup/soup-message-headers.h
   trunk/libsoup/soup-message-server-io.c
   trunk/libsoup/soup.h
   trunk/tests/   (props changed)
   trunk/tests/Makefile.am
   trunk/tests/header-parsing.c

Modified: trunk/docs/reference/libsoup-2.4-docs.sgml
==============================================================================
--- trunk/docs/reference/libsoup-2.4-docs.sgml	(original)
+++ trunk/docs/reference/libsoup-2.4-docs.sgml	Wed Oct  1 21:53:26 2008
@@ -14,8 +14,7 @@
   </chapter>
 
   <chapter>
-    <title>API Reference</title>
-    <xi:include href="xml/soup-address.xml"/>
+    <title>Core API</title>
     <xi:include href="xml/soup-auth.xml"/>
     <xi:include href="xml/soup-auth-domain.xml"/>
     <xi:include href="xml/soup-auth-domain-basic.xml"/>
@@ -27,18 +26,29 @@
     <xi:include href="xml/soup-message-headers.xml"/>
     <xi:include href="xml/soup-message-body.xml"/>
     <xi:include href="xml/soup-method.xml"/>
+    <xi:include href="xml/soup-multipart.xml"/>
     <xi:include href="xml/soup-server.xml"/>
     <xi:include href="xml/soup-session.xml"/>
     <xi:include href="xml/soup-session-async.xml"/>
     <xi:include href="xml/soup-session-sync.xml"/>
-    <xi:include href="xml/soup-socket.xml"/>
     <xi:include href="xml/soup-status.xml"/>
     <xi:include href="xml/soup-uri.xml"/>
-    <xi:include href="xml/soup-value-utils.xml"/>
-    <xi:include href="xml/soup-xmlrpc.xml"/>
     <xi:include href="xml/soup-misc.xml"/>
   </chapter>
 
+  <chapter>
+    <title>Web Services APIs</title>
+    <xi:include href="xml/soup-forms.xml"/>
+    <xi:include href="xml/soup-xmlrpc.xml"/>
+    <xi:include href="xml/soup-value-utils.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Low-level Networking API</title>
+    <xi:include href="xml/soup-address.xml"/>
+    <xi:include href="xml/soup-socket.xml"/>
+  </chapter>
+
   <index>
     <title>Index</title>
   </index>

Modified: trunk/docs/reference/libsoup-2.4-sections.txt
==============================================================================
--- trunk/docs/reference/libsoup-2.4-sections.txt	(original)
+++ trunk/docs/reference/libsoup-2.4-sections.txt	Wed Oct  1 21:53:26 2008
@@ -113,6 +113,14 @@
 <SUBSECTION>
 soup_message_headers_get_content_disposition
 soup_message_headers_set_content_disposition
+<SUBSECTION>
+SoupRange
+soup_message_headers_get_ranges
+soup_message_headers_set_ranges
+soup_message_headers_set_range
+soup_message_headers_free_ranges
+soup_message_headers_get_content_range
+soup_message_headers_set_content_range
 <SUBSECTION Standard>
 SOUP_TYPE_MESSAGE_HEADERS
 soup_message_headers_get_type
@@ -569,18 +577,10 @@
 soup_date_is_past
 soup_date_free
 <SUBSECTION>
-soup_form_decode
-soup_form_encode
-soup_form_encode_datalist
-soup_form_encode_hash
-soup_form_encode_valist
-soup_form_request_new
-soup_form_request_new_from_datalist
-soup_form_request_new_from_hash
-<SUBSECTION>
 soup_headers_parse_request
 soup_headers_parse_response
 soup_headers_parse_status_line
+soup_headers_parse
 <SUBSECTION>
 soup_header_parse_list
 soup_header_parse_quality_list
@@ -610,6 +610,24 @@
 </SECTION>
 
 <SECTION>
+<FILE>soup-forms</FILE>
+<TITLE>HTML Form Support</TITLE>
+<SUBSECTION>
+SOUP_FORM_MIME_TYPE_MULTIPART
+SOUP_FORM_MIME_TYPE_URLENCODED
+soup_form_decode
+soup_form_decode_multipart
+soup_form_encode
+soup_form_encode_datalist
+soup_form_encode_hash
+soup_form_encode_valist
+soup_form_request_new
+soup_form_request_new_from_datalist
+soup_form_request_new_from_hash
+soup_form_request_new_from_multipart
+</SECTION>
+
+<SECTION>
 <FILE>soup-xmlrpc</FILE>
 <TITLE>XMLRPC Support</TITLE>
 <SUBSECTION>
@@ -740,3 +758,22 @@
 SOUP_TYPE_COOKIE_JAR
 soup_cookie_jar_get_type
 </SECTION>
+
+<SECTION>
+<FILE>soup-multipart</FILE>
+<TITLE>SoupMultipart</TITLE>
+SoupMultipart
+soup_multipart_new
+soup_multipart_new_from_message
+soup_multipart_free
+<SUBSECTION>
+soup_multipart_get_length
+soup_multipart_get_part
+soup_multipart_append_part
+soup_multipart_append_form_string
+soup_multipart_append_form_file
+soup_multipart_to_message
+<SUBSECTION Standard>
+SOUP_TYPE_MULTIPART
+soup_multipart_get_type
+</SECTION>

Modified: trunk/libsoup/Makefile.am
==============================================================================
--- trunk/libsoup/Makefile.am	(original)
+++ trunk/libsoup/Makefile.am	Wed Oct  1 21:53:26 2008
@@ -62,6 +62,7 @@
 	soup-message-headers.h	\
 	soup-method.h		\
 	soup-misc.h     	\
+	soup-multipart.h     	\
 	soup-portability.h	\
 	soup-server.h		\
 	soup-session.h		\
@@ -131,6 +132,7 @@
 	soup-message-server-io.c	\
 	soup-method.c     		\
 	soup-misc.c     		\
+	soup-multipart.c	     	\
 	soup-nossl.c     		\
 	soup-path-map.h     		\
 	soup-path-map.c     		\

Modified: trunk/libsoup/soup-form.c
==============================================================================
--- trunk/libsoup/soup-form.c	(original)
+++ trunk/libsoup/soup-form.c	Wed Oct  1 21:53:26 2008
@@ -15,6 +15,33 @@
 #include "soup-message.h"
 #include "soup-uri.h"
 
+/**
+ * SECTION:soup-form
+ * @short_description: HTML form handling
+ * @see_also: #SoupMultipart
+ *
+ * libsoup contains several help methods for processing HTML forms as
+ * defined by <ulink
+ * url="http://www.w3.org/TR/html401/interact/forms.html#h-17.13";>the
+ * HTML 4.01 specification</ulink>.
+ **/
+
+/**
+ * SOUP_FORM_MIME_TYPE_URLENCODED:
+ *
+ * A macro containing the value
+ * <literal>"application/x-www-form-urlencoded"</literal>; the default
+ * MIME type for POSTing HTML form data.
+ **/
+
+/**
+ * SOUP_FORM_MIME_TYPE_MULTIPART:
+ *
+ * A macro containing the value
+ * <literal>"multipart/form-data"</literal>; the MIME type used for
+ * posting form data that contains files to be uploaded.
+ **/
+
 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
 
@@ -80,6 +107,98 @@
 	return form_data_set;
 }
 
+/**
+ * soup_form_decode_multipart:
+ * @msg: a #SoupMessage containing a "multipart/form-data" request body
+ * @file_control_name: the name of the HTML file upload control, or %NULL
+ * @filename: return location for the name of the uploaded file
+ * @content_type: return location for the MIME type of the uploaded file
+ * @file: return location for the uploaded file data
+ *
+ * Decodes the "multipart/form-data" request in @msg; this is a
+ * convenience method for the case when you have a single file upload
+ * control in a form. (Or when you don't have any file upload
+ * controls, but are still using "multipart/form-data" anyway.) Pass
+ * the name of the file upload control in @file_control_name, and
+ * soup_form_decode_multipart() will extract the uploaded file data
+ * into @filename, @content_type, and @file. All of the other form
+ * control data will be returned (as strings, as with
+ * soup_form_decode()) in the returned #GHashTable.
+ *
+ * You may pass %NULL for @filename and/or @content_type if you do not
+ * care about those fields. soup_form_decode_multipart() may also
+ * return %NULL in those fields if the client did not provide that
+ * information. You must free the returned filename and content-type
+ * with g_free(), and the returned file data with soup_buffer_free().
+ *
+ * If you have a form with more than one file upload control, you will
+ * need to decode it manually, using soup_multipart_new_from_message()
+ * and soup_multipart_get_part().
+ *
+ * Return value: a hash table containing the name/value pairs (other
+ * than @file_control_name) from @msg, which you can free with
+ * g_hash_table_destroy(). On error, it will return %NULL.
+ **/
+GHashTable *
+soup_form_decode_multipart (SoupMessage *msg, const char *file_control_name,
+			    char **filename, char **content_type,
+			    SoupBuffer **file)
+{
+	SoupMultipart *multipart;
+	GHashTable *form_data_set, *params;
+	SoupMessageHeaders *part_headers;
+	SoupBuffer *part_body;
+	char *disposition, *name;
+	int i;
+
+	multipart = soup_multipart_new_from_message (msg->request_headers,
+						     msg->request_body);
+	if (!multipart)
+		return NULL;
+
+	if (filename)
+		*filename = NULL;
+	if (content_type)
+		*content_type = NULL;
+	*file = NULL;
+
+	form_data_set = g_hash_table_new_full (g_str_hash, g_str_equal,
+					       g_free, g_free);
+	for (i = 0; i < soup_multipart_get_length (multipart); i++) {
+		soup_multipart_get_part (multipart, i, &part_headers, &part_body);
+		if (!soup_message_headers_get_content_disposition (
+			    part_headers, &disposition, &params))
+			continue;
+		name = g_hash_table_lookup (params, "name");
+		if (g_ascii_strcasecmp (disposition, "form-data") != 0 ||
+		    !name) {
+			g_free (disposition);
+			g_hash_table_destroy (params);
+			continue;
+		}
+
+		if (!strcmp (name, file_control_name)) {
+			if (filename)
+				*filename = g_strdup (g_hash_table_lookup (params, "filename"));
+			if (content_type)
+				*content_type = g_strdup (soup_message_headers_get_content_type (part_headers, NULL));
+			if (file)
+				*file = soup_buffer_copy (part_body);
+		} else {
+			g_hash_table_insert (form_data_set,
+					     g_strdup (name),
+					     g_strndup (part_body->data,
+							part_body->length));
+		}
+
+		g_free (disposition);
+		g_hash_table_destroy (params);
+	}
+
+	soup_multipart_free (multipart);
+	return form_data_set;
+}
+
 static void
 append_form_encoded (GString *str, const char *in)
 {
@@ -243,7 +362,7 @@
 
 	if (!strcmp (method, "POST")) {
 		soup_message_set_request (
-			msg, "application/x-www-form-urlencoded",
+			msg, SOUP_FORM_MIME_TYPE_URLENCODED,
 			SOUP_MEMORY_TAKE,
 			form_data, strlen (form_data));
 		form_data = NULL;
@@ -323,3 +442,35 @@
 	return soup_form_request_for_data (
 		method, uri, soup_form_encode_datalist (form_data_set));
 }
+
+/**
+ * soup_form_request_new_from_multipart:
+ * @uri: the URI to send the form data to
+ * @multipart: a "multipart/form-data" #SoupMultipart
+ *
+ * Creates a new %SoupMessage and sets it up to send @multipart to
+ * @uri via POST.
+ *
+ * To send a <literal>"multipart/form-data"</literal> POST, first
+ * create a #SoupMultipart, using %SOUP_FORM_MIME_TYPE_MULTIPART as
+ * the MIME type. Then use soup_multipart_append_form_string() and
+ * soup_multipart_append_form_file() to add the value of each form
+ * control to the multipart. (These are just convenience methods, and
+ * you can use soup_multipart_append_part() if you need greater
+ * control over the part headers.) Finally, call
+ * soup_form_request_new_from_multipart() to serialize the multipart
+ * structure and create a #SoupMessage.
+ *
+ * Return value: the new %SoupMessage
+ **/
+SoupMessage *
+soup_form_request_new_from_multipart (const char *uri,
+				      SoupMultipart *multipart)
+{
+	SoupMessage *msg;
+
+	msg = soup_message_new ("POST", uri);
+	soup_multipart_to_message (multipart, msg->request_headers,
+				   msg->request_body);
+	return msg;
+}

Modified: trunk/libsoup/soup-form.h
==============================================================================
--- trunk/libsoup/soup-form.h	(original)
+++ trunk/libsoup/soup-form.h	Wed Oct  1 21:53:26 2008
@@ -7,17 +7,26 @@
 #define  SOUP_FORM_H 1
 
 #include <libsoup/soup-types.h>
+#include <libsoup/soup-multipart.h>
 
 G_BEGIN_DECLS
 
-GHashTable  *soup_form_decode          (const char  *encoded_form);
+#define SOUP_FORM_MIME_TYPE_URLENCODED "application/x-www-form-urlencoded"
+#define SOUP_FORM_MIME_TYPE_MULTIPART  "multipart/form-data"
 
-char        *soup_form_encode          (const char  *first_field,
-					...) G_GNUC_NULL_TERMINATED;
-char        *soup_form_encode_hash     (GHashTable  *form_data_set);
-char        *soup_form_encode_datalist (GData      **form_data_set);
-char        *soup_form_encode_valist   (const char  *first_field,
-					va_list      args);
+GHashTable  *soup_form_decode           (const char   *encoded_form);
+GHashTable  *soup_form_decode_multipart (SoupMessage  *msg,
+					 const char   *file_control_name,
+					 char        **filename,
+					 char        **content_type,
+					 SoupBuffer  **file);
+
+char        *soup_form_encode           (const char   *first_field,
+					 ...) G_GNUC_NULL_TERMINATED;
+char        *soup_form_encode_hash      (GHashTable   *form_data_set);
+char        *soup_form_encode_datalist  (GData       **form_data_set);
+char        *soup_form_encode_valist    (const char   *first_field,
+					 va_list       args);
 
 #ifndef LIBSOUP_DISABLE_DEPRECATED
 /* Compatibility with libsoup 2.3.0 */
@@ -26,16 +35,18 @@
 #define soup_form_encode_urlencoded_list soup_form_encode_datalist
 #endif
 
-SoupMessage *soup_form_request_new               (const char  *method,
-						  const char  *uri,
-						  const char  *first_field,
-						  ...) G_GNUC_NULL_TERMINATED;
-SoupMessage *soup_form_request_new_from_hash     (const char  *method,
-						  const char  *uri,
-						  GHashTable  *form_data_set);
-SoupMessage *soup_form_request_new_from_datalist (const char  *method,
-						  const char  *uri,
-						  GData      **form_data_set);
+SoupMessage *soup_form_request_new                (const char     *method,
+						   const char     *uri,
+						   const char     *first_field,
+						   ...) G_GNUC_NULL_TERMINATED;
+SoupMessage *soup_form_request_new_from_hash      (const char     *method,
+						   const char     *uri,
+						   GHashTable     *form_data_set);
+SoupMessage *soup_form_request_new_from_datalist  (const char     *method,
+						   const char     *uri,
+						   GData         **form_data_set);
+SoupMessage *soup_form_request_new_from_multipart (const char     *uri,
+						   SoupMultipart  *multipart);
 
 G_END_DECLS
 

Modified: trunk/libsoup/soup-headers.c
==============================================================================
--- trunk/libsoup/soup-headers.c	(original)
+++ trunk/libsoup/soup-headers.c	Wed Oct  1 21:53:26 2008
@@ -14,7 +14,23 @@
 #include "soup-misc.h"
 #include "soup-uri.h"
 
-static gboolean
+/**
+ * soup_headers_parse:
+ * @str: the header string (including the Request-Line or Status-Line,
+ * and the trailing blank line)
+ * @len: length of @str up to (but not including) the terminating blank line.
+ * @dest: #SoupMessageHeaders to store the header values in
+ *
+ * Parses the headers of an HTTP request or response in @str and
+ * stores the results in @dest. Beware that @dest may be modified even
+ * on failure.
+ *
+ * This is a low-level method; normally you would use
+ * soup_headers_parse_request() or soup_headers_parse_response().
+ *
+ * Return value: success or failure
+ **/
+gboolean
 soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest)
 {
 	const char *headers_start;

Modified: trunk/libsoup/soup-headers.h
==============================================================================
--- trunk/libsoup/soup-headers.h	(original)
+++ trunk/libsoup/soup-headers.h	Wed Oct  1 21:53:26 2008
@@ -13,6 +13,10 @@
 
 /* HTTP Header Parsing */
 
+gboolean    soup_headers_parse              (const char          *str,
+					     int                  len,
+					     SoupMessageHeaders  *dest);
+
 guint       soup_headers_parse_request      (const char          *str,
 					     int                  len,
 					     SoupMessageHeaders  *req_headers,

Modified: trunk/libsoup/soup-message-headers.c
==============================================================================
--- trunk/libsoup/soup-message-headers.c	(original)
+++ trunk/libsoup/soup-message-headers.c	Wed Oct  1 21:53:26 2008
@@ -507,8 +507,8 @@
 			return hdrs->encoding;
 	}
 
-	hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_REQUEST) ?
-		SOUP_ENCODING_NONE : SOUP_ENCODING_EOF;
+	hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_RESPONSE) ?
+		SOUP_ENCODING_EOF : SOUP_ENCODING_NONE;
 	return hdrs->encoding;
 }
 
@@ -661,6 +661,264 @@
 		soup_message_headers_remove (hdrs, "Expect");
 }
 
+/**
+ * SoupRange:
+ * @start: the start of the range
+ * @end: the end of the range
+ *
+ * Represents a byte range as used in the Range header.
+ *
+ * If @end is non-negative, then @start and @end represent the bounds
+ * of of the range, counting from %0. (Eg, the first 500 bytes would be
+ * represented as @start = %0 and @end = %499.)
+ *
+ * If @end is %-1 and @start is non-negative, then this represents a
+ * range starting at @start and ending with the last byte of the
+ * requested resource body. (Eg, all but the first 500 bytes would be
+ * @start = %500, and @end = %-1.)
+ *
+ * If @end is %-1 and @start is negative, then it represents a "suffix
+ * range", referring to the last - start bytes of the resource body.
+ * (Eg, the last 500 bytes would be @start = %-500 and @end = %-1.)
+ **/
+
+/**
+ * soup_message_headers_get_ranges:
+ * @hdrs: a #SoupMessageHeaders
+ * @total_length: the total_length of the response body
+ * @ranges: return location for an array of #SoupRange
+ * @length: the length of the returned array
+ *
+ * Parses @hdrs's Range header and returns an array of the requested
+ * byte ranges. The returned array must be freed with
+ * soup_message_headers_free_ranges().
+ *
+ * If @total_length is non-0, its value will be used to adjust the
+ * returned ranges to have explicit start and end values. If
+ * @total_length is 0, then some ranges may have an end value of -1,
+ * as described under #SoupRange.
+ *
+ * Return value: %TRUE if @hdrs contained a "Range" header containing
+ * byte ranges which could be parsed, %FALSE otherwise (in which case
+ * @range and @length will not be set).
+ **/
+gboolean
+soup_message_headers_get_ranges (SoupMessageHeaders  *hdrs,
+				 goffset              total_length,
+				 SoupRange          **ranges,
+				 int                 *length)
+{
+	const char *range = soup_message_headers_get (hdrs, "Range");
+	GSList *range_list, *r;
+	GArray *array;
+	SoupRange cur;
+	char *spec, *end;
+
+	if (!range || strncmp (range, "bytes", 5) != 0)
+		return FALSE;
+
+	range += 5;
+	while (g_ascii_isspace (*range))
+		range++;
+	if (*range++ != '=')
+		return FALSE;
+	while (g_ascii_isspace (*range))
+		range++;
+
+	range_list = soup_header_parse_list (range);
+	if (!range_list)
+		return FALSE;
+
+	array = g_array_new (FALSE, FALSE, sizeof (SoupRange));
+	for (r = range_list; r; r = r->next) {
+		spec = r->data;
+		if (*spec == '-') {
+			cur.start = g_ascii_strtoll (spec, &end, 10) + total_length;
+			cur.end = total_length - 1;
+		} else {
+			cur.start = g_ascii_strtoull (spec, &end, 10);
+			if (*end == '-')
+				end++;
+			if (*end)
+				cur.end = g_ascii_strtoull (end, &end, 10);
+			else
+				cur.end = total_length - 1;
+		}
+		if (*end) {
+			g_array_free (array, TRUE);
+			soup_header_free_list (range_list);
+			return FALSE;
+		}
+
+		g_array_append_val (array, cur);
+	}
+
+	soup_header_free_list (range_list);
+	*ranges = (SoupRange *)array->data;
+	*length = array->len;
+	g_array_free (array, FALSE);
+	return TRUE;
+}
+
+/**
+ * soup_message_headers_free_ranges:
+ * @hdrs: a #SoupMessageHeaders
+ * @ranges: an array of #SoupRange
+ *
+ * Frees the array of ranges returned from soup_message_headers_get_ranges().
+ **/
+void
+soup_message_headers_free_ranges (SoupMessageHeaders  *hdrs,
+				  SoupRange           *ranges)
+{
+	g_free (ranges);
+}
+
+/**
+ * soup_message_headers_set_ranges:
+ * @hdrs: a #SoupMessageHeaders
+ * @ranges: an array of #SoupRange
+ * @length: the length of @range
+ *
+ * Sets @hdrs's Range header to request the indicated ranges. (If you
+ * only want to request a single range, you can use
+ * soup_message_headers_set_range().)
+ **/
+void
+soup_message_headers_set_ranges (SoupMessageHeaders  *hdrs,
+				 SoupRange           *ranges,
+				 int                  length)
+{
+	GString *header;
+	int i;
+
+	header = g_string_new ("bytes=");
+	for (i = 0; i < length; i++) {
+		if (i > 0)
+			g_string_append_c (header, ',');
+		if (ranges[i].end >= 0) {
+			g_string_append_printf (header, "%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT,
+						ranges[i].start, ranges[i].end);
+		} else if (ranges[i].start >= 0) {
+			g_string_append_printf (header,"%" G_GINT64_FORMAT "-",
+						ranges[i].start);
+		} else {
+			g_string_append_printf (header, "%" G_GINT64_FORMAT,
+						ranges[i].start);
+		}
+	}
+
+	soup_message_headers_replace (hdrs, "Range", header->str);
+	g_string_free (header, TRUE);
+}
+
+/**
+ * soup_message_headers_set_range:
+ * @hdrs: a #SoupMessageHeaders
+ * @start: the start of the range to request
+ * @end: the end of the range to request
+ *
+ * Sets @hdrs's Range header to request the indicated range.
+ * @start and @end are interpreted as in a #SoupRange.
+ *
+ * If you need to request multiple ranges, use
+ * soup_message_headers_set_ranges().
+ **/
+void
+soup_message_headers_set_range (SoupMessageHeaders  *hdrs,
+				goffset              start,
+				goffset              end)
+{
+	SoupRange range;
+
+	range.start = start;
+	range.end = end;
+	soup_message_headers_set_ranges (hdrs, &range, 1);
+}
+
+/**
+ * soup_message_headers_get_content_range:
+ * @hdrs: a #SoupMessageHeaders
+ * @start: return value for the start of the range
+ * @end: return value for the end of the range
+ * @total_length: return value for the total length of the resource,
+ * or %NULL if you don't care.
+ *
+ * Parses @hdrs's Content-Range header and returns it in @start,
+ * @end, and @total_length. If the total length field in the header
+ * was specified as "*", then @total_length will be set to -1.
+ *
+ * Return value: %TRUE if @hdrs contained a "Content-Range" header
+ * containing a byte range which could be parsed, %FALSE otherwise.
+ **/
+gboolean
+soup_message_headers_get_content_range (SoupMessageHeaders  *hdrs,
+					goffset             *start,
+					goffset             *end,
+					goffset             *total_length)
+{
+	const char *header = soup_message_headers_get (hdrs, "Content-Range");
+	goffset length;
+	char *p;
+
+	if (!header || strncmp (header, "bytes ", 6) != 0)
+		return FALSE;
+
+	header += 6;
+	while (g_ascii_isspace (*header))
+		header++;
+	if (!g_ascii_isdigit (*header))
+		return FALSE;
+
+	*start = g_ascii_strtoull (header, &p, 10);
+	if (*p != '-')
+		return FALSE;
+	*end = g_ascii_strtoull (p + 1, &p, 10);
+	if (*p != '/')
+		return FALSE;
+	p++;
+	if (*p == '*') {
+		length = -1;
+		p++;
+	} else
+		length = g_ascii_strtoull (p, &p, 10);
+
+	if (total_length)
+		*total_length = length;
+	return *p == '\0';
+}
+
+/**
+ * soup_message_headers_set_content_range:
+ * @hdrs: a #SoupMessageHeaders
+ * @start: the start of the range
+ * @end: the end of the range
+ * @total_length: the total length of the resource, or -1 if unknown
+ *
+ * Sets @hdrs's Content-Range header according to the given values.
+ * (Note that @total_length is the total length of the entire resource
+ * that this is a range of, not simply @end - @start + 1.)
+ **/
+void
+soup_message_headers_set_content_range (SoupMessageHeaders  *hdrs,
+					goffset              start,
+					goffset              end,
+					goffset              total_length)
+{
+	char *header;
+
+	if (total_length >= 0) {
+		header = g_strdup_printf ("bytes %" G_GINT64_FORMAT "-%"
+					  G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
+					  start, end, total_length);
+	} else {
+		header = g_strdup_printf ("bytes %" G_GINT64_FORMAT "-%"
+					  G_GINT64_FORMAT "/*", start, end);
+	}
+	soup_message_headers_replace (hdrs, "Content-Range", header);
+	g_free (header);
+}
+
 static gboolean
 parse_content_foo (SoupMessageHeaders *hdrs, const char *header_name,
 		   char **foo, GHashTable **params)
@@ -795,6 +1053,10 @@
  * it down to just the final path component, so you do not need to
  * test this yourself.)
  *
+ * Content-Disposition is also used in "multipart/form-data", however
+ * this is handled automatically by #SoupMultipart and the associated
+ * form methods.
+ *
  * Return value: %TRUE if @hdrs contains a "Content-Disposition"
  * header, %FALSE if not (in which case * disposition and * params
  * will be unchanged).

Modified: trunk/libsoup/soup-message-headers.h
==============================================================================
--- trunk/libsoup/soup-message-headers.h	(original)
+++ trunk/libsoup/soup-message-headers.h	Wed Oct  1 21:53:26 2008
@@ -14,7 +14,8 @@
 
 typedef enum {
 	SOUP_MESSAGE_HEADERS_REQUEST,
-	SOUP_MESSAGE_HEADERS_RESPONSE
+	SOUP_MESSAGE_HEADERS_RESPONSE,
+	SOUP_MESSAGE_HEADERS_MULTIPART
 } SoupMessageHeadersType;
 
 SoupMessageHeaders *soup_message_headers_new      (SoupMessageHeadersType type);
@@ -82,6 +83,34 @@
 void            soup_message_headers_set_expectations    (SoupMessageHeaders *hdrs,
 							  SoupExpectation     expectations);
 
+typedef struct {
+	goffset start;
+	goffset end;
+} SoupRange;
+
+gboolean        soup_message_headers_get_ranges          (SoupMessageHeaders  *hdrs,
+							  goffset              total_length,
+							  SoupRange          **ranges,
+							  int                 *length);
+void            soup_message_headers_free_ranges         (SoupMessageHeaders  *hdrs,
+							  SoupRange           *ranges);
+void            soup_message_headers_set_ranges          (SoupMessageHeaders  *hdrs,
+							  SoupRange           *ranges,
+							  int                  length);
+void            soup_message_headers_set_range           (SoupMessageHeaders  *hdrs,
+							  goffset              start,
+							  goffset              end);
+
+gboolean        soup_message_headers_get_content_range   (SoupMessageHeaders  *hdrs,
+							  goffset             *start,
+							  goffset             *end,
+							  goffset             *total_length);
+void            soup_message_headers_set_content_range   (SoupMessageHeaders  *hdrs,
+							  goffset              start,
+							  goffset              end,
+							  goffset              total_length);
+
+
 const char *soup_message_headers_get_content_type     (SoupMessageHeaders  *hdrs,
 						       GHashTable         **params);
 void        soup_message_headers_set_content_type     (SoupMessageHeaders  *hdrs,

Modified: trunk/libsoup/soup-message-server-io.c
==============================================================================
--- trunk/libsoup/soup-message-server-io.c	(original)
+++ trunk/libsoup/soup-message-server-io.c	Wed Oct  1 21:53:26 2008
@@ -16,6 +16,7 @@
 #include "soup-address.h"
 #include "soup-auth.h"
 #include "soup-headers.h"
+#include "soup-multipart.h"
 #include "soup-server.h"
 #include "soup-socket.h"
 
@@ -93,6 +94,98 @@
 }
 
 static void
+handle_partial_get (SoupMessage *msg)
+{
+	SoupRange *ranges;
+	int nranges;
+	SoupBuffer *full_response;
+
+	/* Make sure the message is set up right for us to return a
+	 * partial response; it has to be a GET, the status must be
+	 * 200 OK (and in particular, NOT already 206 Partial
+	 * Content), and the SoupServer must have already filled in
+	 * the response body
+	 */
+	if (msg->method != SOUP_METHOD_GET ||
+	    msg->status_code != SOUP_STATUS_OK ||
+	    soup_message_headers_get_encoding (msg->response_headers) !=
+	    SOUP_ENCODING_CONTENT_LENGTH ||
+	    msg->response_body->length == 0)
+		return;
+
+	/* Oh, and there has to have been a valid Range header on the
+	 * request, of course.
+	 */
+	if (!soup_message_headers_get_ranges (msg->request_headers,
+					      msg->response_body->length,
+					      &ranges, &nranges))
+		return;
+
+	full_response = soup_message_body_flatten (msg->response_body);
+	if (!full_response)
+		return;
+
+	soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+	soup_message_body_truncate (msg->response_body);
+
+	if (nranges == 1) {
+		SoupBuffer *range_buf;
+
+		/* Single range, so just set Content-Range and fix the body. */
+
+		soup_message_headers_set_content_range (msg->response_headers,
+							ranges[0].start,
+							ranges[0].end,
+							full_response->length);
+		range_buf = soup_buffer_new_subbuffer (full_response,
+						       ranges[0].start,
+						       ranges[0].end - ranges[0].start + 1);
+		soup_message_body_append_buffer (msg->response_body, range_buf);
+		soup_buffer_free (range_buf);
+	} else {
+		SoupMultipart *multipart;
+		SoupMessageHeaders *part_headers;
+		SoupBuffer *part_body;
+		const char *content_type;
+		int i;
+
+		/* Multiple ranges, so build a multipart/byteranges response
+		 * to replace msg->response_body with.
+		 */
+
+		multipart = soup_multipart_new ("multipart/byteranges");
+		content_type = soup_message_headers_get (msg->response_headers,
+							 "Content-Type");
+		for (i = 0; i < nranges; i++) {
+			part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+			if (content_type) {
+				soup_message_headers_append (part_headers,
+							     "Content-Type",
+							     content_type);
+			}
+			soup_message_headers_set_content_range (part_headers,
+								ranges[i].start,
+								ranges[i].end,
+								full_response->length);
+			part_body = soup_buffer_new_subbuffer (full_response,
+							       ranges[i].start,
+							       ranges[i].end - ranges[i].start + 1);
+			soup_multipart_append_part (multipart, part_headers,
+						    part_body);
+			soup_message_headers_free (part_headers);
+			soup_buffer_free (part_body);
+		}
+
+		soup_multipart_to_message (multipart, msg->response_headers,
+					   msg->response_body);
+		soup_multipart_free (multipart);
+	}
+
+	soup_buffer_free (full_response);
+	soup_message_headers_free_ranges (msg->request_headers, ranges);
+}
+
+static void
 get_response_headers (SoupMessage *msg, GString *headers,
 		      SoupEncoding *encoding, gpointer user_data)
 {
@@ -100,6 +193,8 @@
 	SoupMessageHeadersIter iter;
 	const char *name, *value;
 
+	handle_partial_get (msg);
+
 	g_string_append_printf (headers, "HTTP/1.%c %d %s\r\n",
 				soup_message_get_http_version (msg) == SOUP_HTTP_1_0 ? '0' : '1',
 				msg->status_code, msg->reason_phrase);

Added: trunk/libsoup/soup-multipart.c
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-multipart.c	Wed Oct  1 21:53:26 2008
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-multipart.c: multipart HTTP message bodies
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#include <string.h>
+
+#include "soup-multipart.h"
+#include "soup-headers.h"
+
+/**
+ * SECTION:soup-multipart
+ * @short_description: multipart HTTP message bodies
+ * @see_also: #SoupMessageBody, #SoupMessageHeaders
+ *
+ **/
+
+/**
+ * SoupMultipart:
+ *
+ * Represents a multipart HTTP message body, parsed according to the
+ * syntax of RFC 2046. Of particular interest to HTTP are
+ * <literal>multipart/byte-ranges</literal> and
+ * <literal>multipart/form-data</literal>.
+ *
+ * Although the headers of a #SoupMultipart body part will contain the
+ * full headers from that body part, libsoup does not interpret them
+ * according to MIME rules. For example, each body part is assumed to
+ * have "binary" Content-Transfer-Encoding, even if its headers
+ * explicitly state otherwise. In other words, don't try to use
+ * #SoupMultipart for handling real MIME multiparts.
+ **/
+
+struct SoupMultipart {
+	char *mime_type, *boundary;
+	GPtrArray *headers, *bodies;
+};
+
+static SoupMultipart *
+soup_multipart_new_internal (char *mime_type, char *boundary)
+{
+	SoupMultipart *multipart;
+
+	multipart = g_slice_new (SoupMultipart);
+	multipart->mime_type = mime_type;
+	multipart->boundary = boundary;
+	multipart->headers = g_ptr_array_new ();
+	multipart->bodies = g_ptr_array_new ();
+
+	return multipart;
+}
+
+static char *
+generate_boundary (void)
+{
+	static int counter;
+	struct {
+		GTimeVal timeval;
+		int counter;
+	} data;
+
+	g_get_current_time (&data.timeval);
+	data.counter = counter++;
+
+	/* The maximum boundary string length is 69 characters, and a
+	 * stringified SHA256 checksum is 64 bytes long.
+	 */
+	return g_compute_checksum_for_data (G_CHECKSUM_SHA256,
+					    (const guchar *)&data,
+					    sizeof (data));
+}
+
+/**
+ * soup_multipart_new:
+ * @mime_type: the MIME type of the multipart to create.
+ *
+ * Creates a new empty #SoupMultipart with a randomly-generated
+ * boundary string. Note that @mime_type must be the full MIME type,
+ * including "multipart/".
+ *
+ * Return value: a new empty #SoupMultipart of the given @mime_type
+ **/
+SoupMultipart *
+soup_multipart_new (const char *mime_type)
+{
+	return soup_multipart_new_internal (g_strdup (mime_type),
+					    generate_boundary ());
+}
+
+static const char *
+find_boundary (const char *start, const char *boundary, int boundary_len)
+{
+	const char *b, *end;
+
+	end = start + 2;
+	while ((b = strstr (end, boundary))) {
+		end = b + boundary_len;
+		if (b[-1] == '-' && b[-2] == '-' &&
+		    (b == start + 2 || (b[-3] == '\n' && b[-4] == '\r'))) {
+			if ((end[0] == '-' && end[1] == '-') ||
+			    (end[0] == '\r' && end[1] == '\n'))
+				return b - 2;
+		}
+	}
+	return NULL;
+}
+
+/**
+ * soup_multipart_new_from_message:
+ * @headers: the headers of the HTTP message to parse
+ * @body: the body of the HTTP message to parse
+ *
+ * Parses @headers and @body to form a new #SoupMultipart
+ *
+ * Return value: a new #SoupMultipart (or %NULL if the message couldn't
+ * be parsed or wasn't multipart).
+ **/
+SoupMultipart *
+soup_multipart_new_from_message (SoupMessageHeaders *headers,
+				 SoupMessageBody *body)
+{
+	SoupMultipart *multipart;
+	const char *content_type, *boundary;
+	GHashTable *params;
+	int boundary_len;
+	SoupBuffer *flattened;
+	const char *start, *split, *end;
+	SoupMessageHeaders *part_headers;
+	SoupBuffer *part_body;
+
+	content_type = soup_message_headers_get_content_type (headers, &params);
+	if (!content_type)
+		return NULL;
+
+	boundary = g_hash_table_lookup (params, "boundary");
+	if (strncmp (content_type, "multipart/", 10) != 0 || !boundary) {
+		g_hash_table_destroy (params);
+		return NULL;
+	}
+
+	multipart = soup_multipart_new_internal (
+		g_strdup (content_type), g_strdup (boundary));
+	g_hash_table_destroy (params);
+
+	flattened = soup_message_body_flatten (body);
+	boundary = multipart->boundary;
+	boundary_len = strlen (boundary);
+
+	/* skip preamble */
+	start = find_boundary (flattened->data, boundary, boundary_len);
+	if (!start) {
+		soup_multipart_free (multipart);
+		return NULL;
+	}
+
+	while (start[2 + boundary_len] != '-') {
+		end = find_boundary (start + 2 + boundary_len, boundary, boundary_len);
+		if (!end) {
+			soup_multipart_free (multipart);
+			return NULL;
+		}
+
+		split = strstr (start, "\r\n\r\n");
+		if (!split || split > end) {
+			soup_multipart_free (multipart);
+			return NULL;
+		}
+		split += 4;
+
+		/* @start points to the start of the boundary line
+		 * preceding this part, and @split points to the end
+		 * of the headers / start of the body.
+		 *
+		 * We tell soup_headers_parse() to start parsing at
+		 * @start, because it skips the first line of the
+		 * input anyway (expecting it to be either a
+		 * Request-Line or Status-Line).
+		 */
+		part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+		g_ptr_array_add (multipart->headers, part_headers);
+		if (!soup_headers_parse (start, split - 2 - start,
+					 part_headers)) {
+			soup_multipart_free (multipart);
+			return NULL;
+		}
+
+		/* @split, as previously mentioned, points to the
+		 * start of the body, and @end points to the start of
+		 * the following boundary line, which is to say 2 bytes
+		 * after the end of the body.
+		 */
+		part_body = soup_buffer_new_subbuffer (flattened,
+						       split - flattened->data,
+						       end - 2 - split);
+		g_ptr_array_add (multipart->bodies, part_body);
+
+		start = end;
+	}
+
+	soup_buffer_free (flattened);
+	return multipart;
+}
+
+/**
+ * soup_multipart_get_length:
+ * @multipart: a #SoupMultipart
+ *
+ * Gets the number of body parts in @multipart
+ *
+ * Return value: the number of body parts in @multipart
+ **/
+int
+soup_multipart_get_length (SoupMultipart *multipart)
+{
+	return multipart->bodies->len;
+}
+
+/**
+ * soup_multipart_get_part:
+ * @multipart: a #SoupMultipart
+ * @part: the part number to get (counting from 0)
+ * @headers: return location for the MIME part headers
+ * @body: return location for the MIME part body
+ *
+ * Gets the indicated body part from @multipart.
+ *
+ * Return value: %TRUE on success, %FALSE if @part is out of range (in
+ * which case @headers and @body won't be set)
+ **/
+gboolean
+soup_multipart_get_part (SoupMultipart *multipart, int part,
+			 SoupMessageHeaders **headers, SoupBuffer **body)
+{
+	if (part < 0 || part >= multipart->bodies->len)
+		return FALSE;
+	*headers = multipart->headers->pdata[part];
+	*body = multipart->bodies->pdata[part];
+	return TRUE;
+}
+
+/**
+ * soup_multipart_append_part:
+ * @multipart: a #SoupMultipart
+ * @headers: the MIME part headers
+ * @body: the MIME part body
+ *
+ * Adds a new MIME part to @multipart with the given headers and body.
+ * (The multipart will make its own copies of @headers and @body, so
+ * you should free your copies if you are not using them for anything
+ * else.)
+ **/
+void
+soup_multipart_append_part (SoupMultipart      *multipart,
+			    SoupMessageHeaders *headers,
+			    SoupBuffer         *body)
+{
+	SoupMessageHeaders *headers_copy;
+	SoupMessageHeadersIter iter;
+	const char *name, *value;
+
+	/* Copying @headers is annoying, but the alternatives seem
+	 * worse:
+	 *
+	 * 1) We don't want to use g_boxed_copy, because
+	 *    SoupMessageHeaders actually implements that as just a
+	 *    ref, which would be confusing since SoupMessageHeaders
+	 *    is mutable and the caller might modify @headers after
+	 *    appending it.
+	 *
+	 * 2) We can't change SoupMessageHeaders to not just do a ref
+	 *    from g_boxed_copy, because that would break language
+	 *    bindings (which need to be able to hold a ref on
+	 *    msg->request_headers, but don't want to duplicate it).
+	 *
+	 * 3) We don't want to steal the reference to @headers,
+	 *    because then we'd have to either also steal the
+	 *    reference to @body (which would be inconsistent with
+	 *    other SoupBuffer methods), or NOT steal the reference to
+	 *    @body, in which case there'd be inconsistency just
+	 *    between the two arguments of this method!
+	 */
+	headers_copy = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	soup_message_headers_iter_init (&iter, headers);
+	while (soup_message_headers_iter_next (&iter, &name, &value))
+		soup_message_headers_append (headers_copy, name, value);
+
+	g_ptr_array_add (multipart->headers, headers_copy);
+	g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
+}
+
+/**
+ * soup_multipart_append_form_string:
+ * @multipart: a multipart (presumably of type "multipart/form-data")
+ * @control_name: the name of the control associated with @data
+ * @data: the body data
+ *
+ * Adds a new MIME part containing @data to @multipart, using
+ * "Content-Disposition: form-data", as per the HTML forms
+ * specification. See soup_form_request_new_from_multipart() for more
+ * details.
+ **/ 
+void
+soup_multipart_append_form_string (SoupMultipart *multipart,
+				   const char *control_name, const char *data)
+{
+	SoupBuffer *body;
+
+	body = soup_buffer_new (SOUP_MEMORY_COPY, data, strlen (data));
+	soup_multipart_append_form_file (multipart, control_name,
+					 NULL, NULL, body);
+	soup_buffer_free (body);
+}
+
+/**
+ * soup_multipart_append_form_file:
+ * @multipart: a multipart (presumably of type "multipart/form-data")
+ * @control_name: the name of the control associated with this file
+ * @filename: the name of the file, or %NULL if not known
+ * @content_type: the MIME type of the file, or %NULL if not known
+ * @body: the file data
+ *
+ * Adds a new MIME part containing @body to @multipart, using
+ * "Content-Disposition: form-data", as per the HTML forms
+ * specification. See soup_form_request_new_from_multipart() for more
+ * details.
+ **/ 
+void
+soup_multipart_append_form_file (SoupMultipart *multipart,
+				 const char *control_name, const char *filename,
+				 const char *content_type, SoupBuffer *body)
+{
+	SoupMessageHeaders *headers;
+	GString *disposition;
+
+	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+	disposition = g_string_new ("form-data; ");
+	soup_header_g_string_append_param (disposition, "name", control_name);
+	if (filename) {
+		g_string_append (disposition, "; ");
+		soup_header_g_string_append_param (disposition, "filename", filename);
+	}
+	soup_message_headers_append (headers, "Content-Disposition",
+				     disposition->str);
+	g_string_free (disposition, TRUE);
+
+	if (content_type) {
+		soup_message_headers_append (headers, "Content-Type",
+					     content_type);
+	}
+
+	/* The HTML spec says we need to set Content-Transfer-Encoding
+	 * if the data is not 7bit. It probably doesn't actually
+	 * matter...
+	 */
+	if (content_type && strncmp (content_type, "text/", 5) != 0) {
+		soup_message_headers_append (headers,
+					     "Content-Transfer-Encoding",
+					     "binary");
+	} else {
+		soup_message_headers_append (headers,
+					     "Content-Transfer-Encoding",
+					     "8bit");
+	}
+
+	g_ptr_array_add (multipart->headers, headers);
+	g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
+}
+
+/**
+ * soup_multipart_to_message:
+ * @multipart: a #SoupMultipart
+ * @dest_headers: the headers of the HTTP message to serialize @multipart to
+ * @dest_body: the body of the HTTP message to serialize @multipart to
+ *
+ * Serializes @multipart to @dest_headers and @dest_body.
+ **/
+void
+soup_multipart_to_message (SoupMultipart *multipart,
+			   SoupMessageHeaders *dest_headers,
+			   SoupMessageBody *dest_body)
+{
+	SoupMessageHeaders *part_headers;
+	SoupBuffer *part_body;
+	SoupMessageHeadersIter iter;
+	const char *name, *value;
+	GString *str;
+	char *content_type;
+	int i;
+
+	content_type = g_strdup_printf ("%s; boundary=\"%s\"",
+					multipart->mime_type,
+					multipart->boundary);
+	soup_message_headers_replace (dest_headers, "Content-Type",
+				      content_type);
+	g_free (content_type);
+
+	for (i = 0; i < multipart->bodies->len; i++) {
+		part_headers = multipart->headers->pdata[i];
+		part_body = multipart->bodies->pdata[i];
+
+		str = g_string_new ("\r\n--");
+		g_string_append (str, multipart->boundary);
+		g_string_append (str, "\r\n");
+		soup_message_headers_iter_init (&iter, part_headers);
+		while (soup_message_headers_iter_next (&iter, &name, &value))
+			g_string_append_printf (str, "%s: %s\r\n", name, value);
+		g_string_append (str, "\r\n");
+		soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
+					  str->str, str->len);
+		g_string_free (str, FALSE);
+
+		soup_message_body_append_buffer (dest_body, part_body);
+	}
+
+	str = g_string_new ("\r\n--");
+	g_string_append (str, multipart->boundary);
+	g_string_append (str, "--\r\n");
+	soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
+				  str->str, str->len);
+	g_string_free (str, FALSE);
+
+	/* (The "\r\n" after the close-delimiter seems wrong according
+	 * to my reading of RFCs 2046 and 2616, but that's what
+	 * everyone else does.)
+	 */
+}
+
+/**
+ * soup_multipart_free:
+ * @multipart: a #SoupMultipart
+ *
+ * Frees @multipart
+ **/
+void
+soup_multipart_free (SoupMultipart *multipart)
+{
+	int i;
+
+	g_free (multipart->mime_type);
+	g_free (multipart->boundary);
+	for (i = 0; i < multipart->headers->len; i++)
+		soup_message_headers_free (multipart->headers->pdata[i]);
+	g_ptr_array_free (multipart->headers, TRUE);
+	for (i = 0; i < multipart->bodies->len; i++)
+		soup_buffer_free (multipart->bodies->pdata[i]);
+	g_ptr_array_free (multipart->bodies, TRUE);
+
+	g_slice_free (SoupMultipart, multipart);
+}
+
+static SoupMultipart *
+soup_multipart_copy (SoupMultipart *multipart)
+{
+	SoupMultipart *copy;
+	int i;
+
+	copy = soup_multipart_new_internal (g_strdup (multipart->mime_type),
+					    g_strdup (multipart->boundary));
+	for (i = 0; i < multipart->bodies->len; i++) {
+		soup_multipart_append_part (copy,
+					    multipart->headers->pdata[i],
+					    multipart->bodies->pdata[i]);
+	}
+	return copy;
+}
+
+GType
+soup_multipart_get_type (void)
+{
+	static volatile gsize type_volatile = 0;
+
+	if (g_once_init_enter (&type_volatile)) {
+		GType type = g_boxed_type_register_static (
+			g_intern_static_string ("SoupMultipart"),
+			(GBoxedCopyFunc) soup_multipart_copy,
+			(GBoxedFreeFunc) soup_multipart_free);
+		g_once_init_leave (&type_volatile, type);
+	}
+	return type_volatile;
+}

Added: trunk/libsoup/soup-multipart.h
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-multipart.h	Wed Oct  1 21:53:26 2008
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_MULTIPART_H
+#define SOUP_MULTIPART_H 1
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-message-body.h>
+#include <libsoup/soup-message-headers.h>
+
+typedef struct SoupMultipart SoupMultipart;
+
+GType soup_multipart_get_type (void);
+#define SOUP_TYPE_MULTIPART (soup_multipart_get_type ())
+
+SoupMultipart *soup_multipart_new              (const char          *mime_type);
+SoupMultipart *soup_multipart_new_from_message (SoupMessageHeaders  *headers,
+						SoupMessageBody     *body);
+
+int      soup_multipart_get_length         (SoupMultipart       *multipart);
+gboolean soup_multipart_get_part           (SoupMultipart       *multipart,
+					    int                  part,
+					    SoupMessageHeaders **headers,
+					    SoupBuffer         **body);
+
+void     soup_multipart_append_part        (SoupMultipart       *multipart,
+					    SoupMessageHeaders  *headers,
+					    SoupBuffer          *body);
+
+void     soup_multipart_append_form_string (SoupMultipart       *multipart,
+					    const char          *control_name,
+					    const char          *data);
+void     soup_multipart_append_form_file   (SoupMultipart       *multipart,
+					    const char          *control_name,
+					    const char          *filename,
+					    const char          *content_type,
+					    SoupBuffer          *body);
+
+void     soup_multipart_to_message         (SoupMultipart       *multipart,
+					    SoupMessageHeaders  *dest_headers,
+					    SoupMessageBody     *dest_body);
+
+void     soup_multipart_free               (SoupMultipart       *multipart);
+
+#endif /* SOUP_MULTIPART_H */

Modified: trunk/libsoup/soup.h
==============================================================================
--- trunk/libsoup/soup.h	(original)
+++ trunk/libsoup/soup.h	Wed Oct  1 21:53:26 2008
@@ -25,6 +25,7 @@
 #include <libsoup/soup-message.h>
 #include <libsoup/soup-method.h>
 #include <libsoup/soup-misc.h>
+#include <libsoup/soup-multipart.h>
 #include <libsoup/soup-server.h>
 #include <libsoup/soup-session-async.h>
 #include <libsoup/soup-session-feature.h>

Modified: trunk/tests/Makefile.am
==============================================================================
--- trunk/tests/Makefile.am	(original)
+++ trunk/tests/Makefile.am	Wed Oct  1 21:53:26 2008
@@ -16,6 +16,7 @@
 	continue-test	\
 	date		\
 	dns		\
+	forms-test	\
 	get		\
 	getbug		\
 	header-parsing  \
@@ -38,6 +39,7 @@
 continue_test_SOURCES = continue-test.c $(TEST_SRCS)
 date_SOURCES = date.c $(TEST_SRCS)
 dns_SOURCES = dns.c
+forms_test_SOURCES = forms-test.c $(TEST_SRCS)
 get_SOURCES = get.c
 getbug_SOURCES = getbug.c
 header_parsing_SOURCES = header-parsing.c $(TEST_SRCS)
@@ -45,8 +47,8 @@
 ntlm_test_SOURCES = ntlm-test.c $(TEST_SRCS)
 proxy_test_SOURCES = proxy-test.c $(TEST_SRCS)
 pull_api_SOURCES = pull-api.c $(TEST_SRCS)
+range_test_SOURCES = range-test.c $(TEST_SRCS)
 redirect_test_SOURCES = redirect-test.c $(TEST_SRCS)
-query_test_SOURCES = query-test.c $(TEST_SRCS)
 server_auth_test_SOURCES = server-auth-test.c $(TEST_SRCS)
 simple_httpd_SOURCES = simple-httpd.c
 simple_proxy_SOURCES = simple-proxy.c
@@ -56,10 +58,10 @@
 xmlrpc_server_test_SOURCES = xmlrpc-server-test.c $(TEST_SRCS)
 
 if HAVE_APACHE
-APACHE_TESTS = auth-test proxy-test pull-api
+APACHE_TESTS = auth-test proxy-test pull-api range-test
 endif
 if HAVE_CURL
-CURL_TESTS = query-test server-auth-test
+CURL_TESTS = forms-test server-auth-test
 endif
 if HAVE_SSL
 SSL_TESTS = ssl-test

Copied: trunk/tests/forms-test.c (from r1173, /trunk/tests/query-test.c)
==============================================================================
--- /trunk/tests/query-test.c	(original)
+++ trunk/tests/forms-test.c	Wed Oct  1 21:53:26 2008
@@ -17,16 +17,13 @@
 #include <unistd.h>
 
 #include <glib.h>
-#include <libsoup/soup-form.h>
-#include <libsoup/soup-message.h>
-#include <libsoup/soup-server.h>
-#include <libsoup/soup-session-sync.h>
+#include <libsoup/soup.h>
 
 #include "test-utils.h"
 
 static struct {
-	const char *title, *name;
-	const char *result;
+	char *title, *name;
+	char *result;
 } tests[] = {
 	/* Both fields must be filled in */
 	{ NULL, "Name", "" },
@@ -55,7 +52,7 @@
 };
 
 static void
-do_test (int n, gboolean extra, const char *uri)
+do_hello_test (int n, gboolean extra, const char *uri)
 {
 	GPtrArray *args;
 	char *title_arg = NULL, *name_arg = NULL;
@@ -110,20 +107,125 @@
 }
 
 static void
-do_query_tests (const char *uri)
+do_hello_tests (const char *uri)
 {
 	int n;
 
+	debug_printf (1, "Hello tests (GET, application/x-www-form-urlencoded)\n");
 	for (n = 0; n < G_N_ELEMENTS (tests); n++) {
-		do_test (n, FALSE, uri);
-		do_test (n, TRUE, uri);
+		do_hello_test (n, FALSE, uri);
+		do_hello_test (n, TRUE, uri);
 	}
 }
 
 static void
-server_callback (SoupServer *server, SoupMessage *msg,
-		 const char *path, GHashTable *query,
-		 SoupClientContext *context, gpointer data)
+do_md5_test_curl (const char *uri, const char *file, const char *md5)
+{
+	GPtrArray *args;
+	char *file_arg, *str_stdout;
+
+	debug_printf (1, "  via curl: ");
+
+	args = g_ptr_array_new ();
+	g_ptr_array_add (args, "curl");
+	g_ptr_array_add (args, "-L");
+	g_ptr_array_add (args, "-F");
+	file_arg = g_strdup_printf ("file= %s", file);
+	g_ptr_array_add (args, file_arg);
+	g_ptr_array_add (args, "-F");
+	g_ptr_array_add (args, "fmt=txt");
+	g_ptr_array_add (args, (char *)uri);
+	g_ptr_array_add (args, NULL);
+
+	if (g_spawn_sync (NULL, (char **)args->pdata, NULL,
+			  G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
+			  NULL, NULL,
+			  &str_stdout, NULL, NULL, NULL)) {
+		if (str_stdout && !strcmp (str_stdout, md5))
+			debug_printf (1, "OK!\n");
+		else {
+			debug_printf (1, "WRONG!\n");
+			debug_printf (1, "  expected '%s', got '%s'\n",
+				      md5, str_stdout ? str_stdout : "(error)");
+			errors++;
+		}
+		g_free (str_stdout);
+	} else {
+		debug_printf (1, "ERROR!\n");
+		errors++;
+	}
+	g_ptr_array_free (args, TRUE);
+	g_free (file_arg);
+}
+
+static void
+do_md5_test_libsoup (const char *uri, const char *contents, const char *md5)
+{
+	SoupMultipart *multipart;
+	SoupBuffer *buffer;
+	SoupMessage *msg;
+	SoupSession *session;
+
+	debug_printf (1, "  via libsoup: ");
+
+	multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART);
+	buffer = soup_buffer_new (SOUP_MEMORY_COPY, contents, strlen (contents));
+	soup_multipart_append_form_file (multipart, "file",
+					 "index.txt", "text/plain",
+					 buffer);
+	soup_buffer_free (buffer);
+	soup_multipart_append_form_string (multipart, "fmt", "text");
+
+	msg = soup_form_request_new_from_multipart (uri, multipart);
+	soup_multipart_free (multipart);
+
+	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+	soup_session_send_message (session, msg);
+
+	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+		debug_printf (1, "ERROR: Unexpected status %d %s\n",
+			      msg->status_code, msg->reason_phrase);
+		errors++;
+	} else if (strcmp (msg->response_body->data, md5) != 0) {
+		debug_printf (1, "ERROR: Incorrect response: expected '%s' got '%s'\n",
+			      md5, msg->response_body->data);
+		errors++;
+	} else
+		debug_printf (1, "OK!\n");
+
+	g_object_unref (msg);
+	soup_test_session_abort_unref (session);
+}
+
+static void
+do_md5_tests (const char *uri)
+{
+	char *contents, *md5;
+	gsize length;
+	GError *error = NULL;
+
+	debug_printf (1, "\nMD5 tests (POST, multipart/form-data)\n");
+
+	if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) {
+		debug_printf (1, "  ERROR: Could not read " SRCDIR "/index.txt: %s\n", error->message);
+		g_error_free (error);
+		errors++;
+		return;
+	}
+
+	md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, contents, length);
+
+	do_md5_test_curl (uri, SRCDIR "/index.txt", md5);
+	do_md5_test_libsoup (uri, contents, md5);
+
+	g_free (contents);
+	g_free (md5);
+}
+
+static void
+hello_callback (SoupServer *server, SoupMessage *msg,
+		const char *path, GHashTable *query,
+		SoupClientContext *context, gpointer data)
 {
 	char *title, *name, *fmt;
 	const char *content_type;
@@ -144,13 +246,13 @@
 	buf = g_string_new (NULL);
 	if (!query || (fmt && !strcmp (fmt, "html"))) {
 		content_type = "text/html";
-		g_string_append (buf, "<html><head><title>query-test</title></head><body>\r\n");
+		g_string_append (buf, "<html><head><title>forms-test: hello</title></head><body>\r\n");
 		if (title && name) {
 			/* mumble mumble html-escape... */
 			g_string_append_printf (buf, "<p>Hello, <b><em>%s</em> %s</b></p>\r\n",
 						title, name);
 		}
-		g_string_append (buf, "<form action='/' method='get'>"
+		g_string_append (buf, "<form action='/hello' method='get'>"
 				 "<p>Title: <input name='title'></p>"
 				 "<p>Name: <input name='name'></p>"
 				 "<p><input type=hidden name='fmt' value='html'></p>"
@@ -174,6 +276,113 @@
 	soup_message_set_status (msg, SOUP_STATUS_OK);
 }
 
+static void
+md5_get_callback (SoupServer *server, SoupMessage *msg,
+		  const char *path, GHashTable *query,
+		  SoupClientContext *context, gpointer data)
+{
+	const char *file = NULL, *md5sum = NULL, *fmt;
+	const char *content_type;
+	GString *buf;
+
+	if (query) {
+		file = g_hash_table_lookup (query, "file");
+		md5sum = g_hash_table_lookup (query, "md5sum");
+		fmt = g_hash_table_lookup (query, "fmt");
+	} else
+		fmt = "html";
+
+	buf = g_string_new (NULL);
+	if (!strcmp (fmt, "html")) {
+		content_type = "text/html";
+		g_string_append (buf, "<html><head><title>forms-test: md5</title></head><body>\r\n");
+		if (file && md5sum) {
+			/* mumble mumble html-escape... */
+			g_string_append_printf (buf, "<p>File: %s<br>MD5: <b>%s</b></p>\r\n",
+						file, md5sum);
+		}
+		g_string_append (buf, "<form action='/md5' method='post' enctype='multipart/form-data'>"
+				 "<p>File: <input type='file' name='file'></p>"
+				 "<p><input type=hidden name='fmt' value='html'></p>"
+				 "<p><input type=submit></p>"
+				 "</form>\r\n");
+		g_string_append (buf, "</body></html>\r\n");
+	} else {
+		content_type = "text/plain";
+		if (md5sum)
+			g_string_append_printf (buf, "%s", md5sum);
+	}
+
+	soup_message_set_response (msg, content_type,
+				   SOUP_MEMORY_TAKE,
+				   buf->str, buf->len);
+	g_string_free (buf, FALSE);
+	soup_message_set_status (msg, SOUP_STATUS_OK);
+}
+
+static void
+md5_post_callback (SoupServer *server, SoupMessage *msg,
+		   const char *path, GHashTable *query,
+		   SoupClientContext *context, gpointer data)
+{
+	const char *content_type;
+	GHashTable *params;
+	const char *fmt;
+	char *filename, *md5sum, *redirect_uri;
+	SoupBuffer *file;
+	SoupURI *uri;
+
+	content_type = soup_message_headers_get_content_type (msg->request_headers, NULL);
+	if (!content_type || strcmp (content_type, "multipart/form-data") != 0) {
+		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+		return;
+	}
+
+	params = soup_form_decode_multipart (msg, "file",
+					     &filename, NULL, &file);
+	if (!params) {
+		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+		return;
+	}
+	fmt = g_hash_table_lookup (params, "fmt");
+
+	md5sum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+					      (gpointer)file->data,
+					      file->length);
+	soup_buffer_free (file);
+
+	uri = soup_uri_copy (soup_message_get_uri (msg));
+	soup_uri_set_query_from_fields (uri,
+					"file", filename ? filename : "",
+					"md5sum", md5sum,
+					"fmt", fmt ? fmt : "html",
+					NULL);
+	redirect_uri = soup_uri_to_string (uri, FALSE);
+
+	soup_message_set_status (msg, SOUP_STATUS_SEE_OTHER);
+	soup_message_headers_replace (msg->response_headers, "Location",
+				      redirect_uri);
+
+	g_free (redirect_uri);
+	soup_uri_free (uri);
+	g_free (md5sum);
+	g_free (filename);
+	g_hash_table_destroy (params);
+}
+
+static void
+md5_callback (SoupServer *server, SoupMessage *msg,
+	      const char *path, GHashTable *query,
+	      SoupClientContext *context, gpointer data)
+{
+	if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
+		md5_get_callback (server, msg, path, query, context, data);
+	else if (msg->method == SOUP_METHOD_POST)
+		md5_post_callback (server, msg, path, query, context, data);
+	else
+		soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
+}
+
 static gboolean run_tests = TRUE;
 
 static GOptionEntry no_test_entry[] = {
@@ -194,15 +403,21 @@
 	test_init (argc, argv, no_test_entry);
 
 	server = soup_test_server_new (TRUE);
-	soup_server_add_handler (server, NULL,
-				 server_callback, NULL, NULL);
+	soup_server_add_handler (server, "/hello",
+				 hello_callback, NULL, NULL);
+	soup_server_add_handler (server, "/md5",
+				 md5_callback, NULL, NULL);
 	port = 	soup_server_get_port (server);
 
 	loop = g_main_loop_new (NULL, TRUE);
 
 	if (run_tests) {
-		uri_str = g_strdup_printf ("http://localhost:%u";, port);
-		do_query_tests (uri_str);
+		uri_str = g_strdup_printf ("http://localhost:%u/hello";, port);
+		do_hello_tests (uri_str);
+		g_free (uri_str);
+
+		uri_str = g_strdup_printf ("http://localhost:%u/md5";, port);
+		do_md5_tests (uri_str);
 		g_free (uri_str);
 	} else {
 		printf ("Listening on port %d\n", port);

Modified: trunk/tests/header-parsing.c
==============================================================================
--- trunk/tests/header-parsing.c	(original)
+++ trunk/tests/header-parsing.c	Wed Oct  1 21:53:26 2008
@@ -833,7 +833,7 @@
 
 	debug_printf (1, "rfc2231 tests\n");
 
-	hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+	hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
 	params = g_hash_table_new (g_str_hash, g_str_equal);
 	g_hash_table_insert (params, "filename", RFC2231_TEST_FILENAME);
 	soup_message_headers_set_content_disposition (hdrs, "attachment", params);

Added: trunk/tests/range-test.c
==============================================================================
--- (empty file)
+++ trunk/tests/range-test.c	Wed Oct  1 21:53:26 2008
@@ -0,0 +1,294 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libsoup/soup.h"
+
+#include "test-utils.h"
+
+SoupBuffer *full_response;
+int total_length;
+char *test_response;
+
+static void
+get_full_response (void)
+{
+	char *contents;
+	gsize length;
+	GError *error = NULL;
+
+	if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) {
+		fprintf (stderr, "Could not read index.txt: %s\n",
+			 error->message);
+		exit (1);
+	}
+
+	full_response = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length);
+	debug_printf (1, "Total response length is %d\n\n", (int)length);
+}
+
+static void
+check_part (SoupMessageHeaders *headers, const char *body, gsize body_len,
+	    gboolean check_start_end, int expected_start, int expected_end)
+{
+	goffset start, end, total_length;
+
+	debug_printf (1, "    Content-Range: %s\n",
+		      soup_message_headers_get (headers, "Content-Range"));
+
+	if (!soup_message_headers_get_content_range (headers, &start, &end, &total_length)) {
+		debug_printf (1, "    Could not find/parse Content-Range\n");
+		errors++;
+		return;
+	}
+
+	if (total_length != full_response->length && total_length != -1) {
+		debug_printf (1, "    Unexpected total length %" G_GINT64_FORMAT " in response\n",
+			      total_length);
+		errors++;
+		return;
+	}
+
+	if (check_start_end) {
+		if ((expected_start >= 0 && start != expected_start) ||
+		    (expected_start < 0 && start != full_response->length + expected_start)) {
+			debug_printf (1, "    Unexpected range start %" G_GINT64_FORMAT " in response\n",
+				      start);
+			errors++;
+			return;
+		}
+
+		if ((expected_end >= 0 && end != expected_end) ||
+		    (expected_end < 0 && end != full_response->length - 1)) {
+			debug_printf (1, "    Unexpected range end %" G_GINT64_FORMAT " in response\n",
+				      end);
+			errors++;
+			return;
+		}
+	}
+
+	if (end - start + 1 != body_len) {
+		debug_printf (1, "    Range length (%d) does not match body length (%d)\n",
+			      (int)(end - start) + 1,
+			      (int)body_len);
+		errors++;
+		return;
+	}
+
+	memcpy (test_response + start, body, body_len);
+}
+
+static void
+request_single_range (SoupSession *session, char *uri,
+		      int start, int end)
+{
+	SoupMessage *msg;
+
+	msg = soup_message_new ("GET", uri);
+	soup_message_headers_set_range (msg->request_headers, start, end);
+
+	debug_printf (1, "    Range: %s\n",
+		      soup_message_headers_get (msg->request_headers, "Range"));
+
+	soup_session_send_message (session, msg);
+
+	if (msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
+		debug_printf (1, "    Unexpected status %d %s\n",
+			      msg->status_code, msg->reason_phrase);
+		g_object_unref (msg);
+		errors++;
+		return;
+	}
+
+	check_part (msg->response_headers, msg->response_body->data,
+		    msg->response_body->length, TRUE, start, end);
+	g_object_unref (msg);
+}
+
+static void
+request_multi_range (SoupSession *session, SoupMessage *msg)
+{
+	SoupMultipart *multipart;
+	const char *content_type;
+	int i, length;
+
+	debug_printf (1, "    Range: %s\n",
+		      soup_message_headers_get (msg->request_headers, "Range"));
+
+	soup_session_send_message (session, msg);
+
+	if (msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
+		debug_printf (1, "    Unexpected status %d %s\n",
+			      msg->status_code, msg->reason_phrase);
+		g_object_unref (msg);
+		errors++;
+		return;
+	}
+
+	content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
+	if (!content_type || strcmp (content_type, "multipart/byteranges") != 0) {
+		debug_printf (1, "    Response Content-Type (%s) was not multipart/byteranges\n",
+			      content_type);
+		g_object_unref (msg);
+		errors++;
+		return;
+	}
+
+	multipart = soup_multipart_new_from_message (msg->response_headers,
+						     msg->response_body);
+	if (!multipart) {
+		debug_printf (1, "    Could not parse multipart\n");
+		g_object_unref (msg);
+		errors++;
+		return;
+	}
+
+	length = soup_multipart_get_length (multipart);
+	for (i = 0; i < length; i++) {
+		SoupMessageHeaders *headers;
+		SoupBuffer *body;
+
+		debug_printf (1, "  Part %d\n", i + 1);
+		soup_multipart_get_part (multipart, i, &headers, &body);
+		check_part (headers, body->data, body->length, FALSE, 0, 0);
+	}
+
+	soup_multipart_free (multipart);
+	g_object_unref (msg);
+}
+
+static void
+request_double_range (SoupSession *session, char *uri,
+		      int first_start, int first_end,
+		      int second_start, int second_end)
+{
+	SoupMessage *msg;
+	SoupRange ranges[2];
+
+	msg = soup_message_new ("GET", uri);
+	ranges[0].start = first_start;
+	ranges[0].end = first_end;
+	ranges[1].start = second_start;
+	ranges[1].end = second_end;
+	soup_message_headers_set_ranges (msg->request_headers, ranges, 2);
+
+	request_multi_range (session, msg);
+}
+
+static void
+request_triple_range (SoupSession *session, char *uri,
+		      int first_start, int first_end,
+		      int second_start, int second_end,
+		      int third_start, int third_end)
+{
+	SoupMessage *msg;
+	SoupRange ranges[3];
+
+	msg = soup_message_new ("GET", uri);
+	ranges[0].start = first_start;
+	ranges[0].end = first_end;
+	ranges[1].start = second_start;
+	ranges[1].end = second_end;
+	ranges[2].start = third_start;
+	ranges[2].end = third_end;
+	soup_message_headers_set_ranges (msg->request_headers, ranges, 3);
+
+	request_multi_range (session, msg);
+}
+
+static void
+do_range_test (SoupSession *session, char *uri)
+{
+	int sevenths = full_response->length / 7;
+
+	memset (test_response, 0, full_response->length);
+
+	debug_printf (1, "Requesting %d-%d\n", 0 * sevenths, 1 * sevenths);
+	request_single_range (session, uri,
+			      0 * sevenths, 1 * sevenths);
+
+	/* These two are redundant in terms of data coverage (except
+	 * maybe for a single byte because of rounding), but they may
+	 * still catch Range-header-generating bugs.
+	 */
+	debug_printf (1, "Requesting %d-\n", 6 * sevenths);
+	request_single_range (session, uri,
+			      6 * sevenths, -1);
+	debug_printf (1, "Requesting -%d\n", 1 * sevenths);
+	request_single_range (session, uri,
+			      -1 * sevenths, -1);
+
+	debug_printf (1, "Requesting %d-%d,%d-%d\n",
+		      2 * sevenths, 3 * sevenths,
+		      5 * sevenths, 6 * sevenths);
+	request_double_range (session, uri,
+			      2 * sevenths, 3 * sevenths,
+			      5 * sevenths, 6 * sevenths);
+
+	debug_printf (1, "Requesting %d-%d,%d-%d,%d-%d\n",
+		      3 * sevenths, 4 * sevenths,
+		      1 * sevenths, 2 * sevenths,
+		      4 * sevenths, 5 * sevenths);
+	request_triple_range (session, uri,
+			      3 * sevenths, 4 * sevenths,
+			      1 * sevenths, 2 * sevenths,
+			      4 * sevenths, 5 * sevenths);
+
+	if (memcmp (full_response->data, test_response, full_response->length) != 0) {
+		debug_printf (1, "\nfull_response and test_response don't match\n");
+		errors++;
+	}
+}
+
+static void
+server_handler (SoupServer        *server,
+		SoupMessage       *msg, 
+		const char        *path,
+		GHashTable        *query,
+		SoupClientContext *client,
+		gpointer           user_data)
+{
+	soup_message_set_status (msg, SOUP_STATUS_OK);
+	soup_message_body_append_buffer (msg->response_body,
+					 full_response);
+}
+
+int
+main (int argc, char **argv)
+{
+	SoupSession *session;
+	SoupServer *server;
+	char *base_uri;
+
+	test_init (argc, argv, NULL);
+	apache_init ();
+
+	get_full_response ();
+	test_response = g_malloc0 (full_response->length);
+
+	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+
+	debug_printf (1, "1. Testing against apache\n");
+	do_range_test (session, "http://localhost:47524/";);
+
+	debug_printf (1, "\n2. Testing against SoupServer\n");
+	server = soup_test_server_new (FALSE);
+	soup_server_add_handler (server, NULL, server_handler, NULL, NULL);
+	base_uri = g_strdup_printf ("http://localhost:%u/";,
+				    soup_server_get_port (server));
+	do_range_test (session, base_uri);
+
+	soup_test_session_abort_unref (session);
+
+	soup_buffer_free (full_response);
+	g_free (test_response);
+
+	test_cleanup ();
+	return errors != 0;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]