libsoup r1176 - in trunk: . docs/reference libsoup tests
- From: danw svn gnome org
- To: svn-commits-list gnome org
- Subject: libsoup r1176 - in trunk: . docs/reference libsoup tests
- Date: Wed, 1 Oct 2008 21:53:26 +0000 (UTC)
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, ¶ms))
+ 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, ¶ms);
+ 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]