1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-multipart.c: multipart HTTP message bodies
5 * Copyright (C) 2008 Red Hat, Inc.
10 #include "soup-multipart.h"
11 #include "soup-headers.h"
14 * SECTION:soup-multipart
15 * @short_description: multipart HTTP message bodies
16 * @see_also: #SoupMessageBody, #SoupMessageHeaders
23 * Represents a multipart HTTP message body, parsed according to the
24 * syntax of RFC 2046. Of particular interest to HTTP are
25 * <literal>multipart/byte-ranges</literal> and
26 * <literal>multipart/form-data</literal>.
28 * Although the headers of a #SoupMultipart body part will contain the
29 * full headers from that body part, libsoup does not interpret them
30 * according to MIME rules. For example, each body part is assumed to
31 * have "binary" Content-Transfer-Encoding, even if its headers
32 * explicitly state otherwise. In other words, don't try to use
33 * #SoupMultipart for handling real MIME multiparts.
38 struct SoupMultipart {
39 char *mime_type, *boundary;
40 GPtrArray *headers, *bodies;
43 static SoupMultipart *
44 soup_multipart_new_internal (char *mime_type, char *boundary)
46 SoupMultipart *multipart;
48 multipart = g_slice_new (SoupMultipart);
49 multipart->mime_type = mime_type;
50 multipart->boundary = boundary;
51 multipart->headers = g_ptr_array_new ();
52 multipart->bodies = g_ptr_array_new ();
58 generate_boundary (void)
66 /* avoid valgrind warning */
67 if (sizeof (data) != sizeof (data.timeval) + sizeof (data.counter))
68 memset (&data, 0, sizeof (data));
70 g_get_current_time (&data.timeval);
71 data.counter = counter++;
73 /* The maximum boundary string length is 69 characters, and a
74 * stringified SHA256 checksum is 64 bytes long.
76 return g_compute_checksum_for_data (G_CHECKSUM_SHA256,
77 (const guchar *)&data,
83 * @mime_type: the MIME type of the multipart to create.
85 * Creates a new empty #SoupMultipart with a randomly-generated
86 * boundary string. Note that @mime_type must be the full MIME type,
87 * including "multipart/".
89 * Return value: a new empty #SoupMultipart of the given @mime_type
94 soup_multipart_new (const char *mime_type)
96 return soup_multipart_new_internal (g_strdup (mime_type),
97 generate_boundary ());
101 find_boundary (const char *start, const char *end,
102 const char *boundary, int boundary_len)
106 for (b = memchr (start, '-', end - start);
107 b && b + boundary_len + 4 < end;
108 b = memchr (b + 2, '-', end - (b + 2))) {
109 /* Check for "--boundary" */
111 memcmp (b + 2, boundary, boundary_len) != 0)
114 /* Check that it's at start of line */
115 if (!(b == start || (b[-1] == '\n' && b[-2] == '\r')))
118 /* Check for "--" or "\r\n" after boundary */
119 if ((b[boundary_len + 2] == '-' && b[boundary_len + 3] == '-') ||
120 (b[boundary_len + 2] == '\r' && b[boundary_len + 3] == '\n'))
127 * soup_multipart_new_from_message:
128 * @headers: the headers of the HTTP message to parse
129 * @body: the body of the HTTP message to parse
131 * Parses @headers and @body to form a new #SoupMultipart
133 * Return value: a new #SoupMultipart (or %NULL if the message couldn't
134 * be parsed or wasn't multipart).
139 soup_multipart_new_from_message (SoupMessageHeaders *headers,
140 SoupMessageBody *body)
142 SoupMultipart *multipart;
143 const char *content_type, *boundary;
146 SoupBuffer *flattened;
147 const char *start, *split, *end, *body_end;
148 SoupMessageHeaders *part_headers;
149 SoupBuffer *part_body;
151 content_type = soup_message_headers_get_content_type (headers, ¶ms);
155 boundary = g_hash_table_lookup (params, "boundary");
156 if (strncmp (content_type, "multipart/", 10) != 0 || !boundary) {
157 g_hash_table_destroy (params);
161 multipart = soup_multipart_new_internal (
162 g_strdup (content_type), g_strdup (boundary));
163 g_hash_table_destroy (params);
165 flattened = soup_message_body_flatten (body);
166 body_end = flattened->data + flattened->length;
167 boundary = multipart->boundary;
168 boundary_len = strlen (boundary);
171 start = find_boundary (flattened->data, body_end,
172 boundary, boundary_len);
174 soup_multipart_free (multipart);
175 soup_buffer_free (flattened);
179 while (start[2 + boundary_len] != '-') {
180 end = find_boundary (start + 2 + boundary_len, body_end,
181 boundary, boundary_len);
183 soup_multipart_free (multipart);
184 soup_buffer_free (flattened);
188 split = strstr (start, "\r\n\r\n");
189 if (!split || split > end) {
190 soup_multipart_free (multipart);
191 soup_buffer_free (flattened);
196 /* @start points to the start of the boundary line
197 * preceding this part, and @split points to the end
198 * of the headers / start of the body.
200 * We tell soup_headers_parse() to start parsing at
201 * @start, because it skips the first line of the
202 * input anyway (expecting it to be either a
203 * Request-Line or Status-Line).
205 part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
206 g_ptr_array_add (multipart->headers, part_headers);
207 if (!soup_headers_parse (start, split - 2 - start,
209 soup_multipart_free (multipart);
210 soup_buffer_free (flattened);
214 /* @split, as previously mentioned, points to the
215 * start of the body, and @end points to the start of
216 * the following boundary line, which is to say 2 bytes
217 * after the end of the body.
219 part_body = soup_buffer_new_subbuffer (flattened,
220 split - flattened->data,
222 g_ptr_array_add (multipart->bodies, part_body);
227 soup_buffer_free (flattened);
232 * soup_multipart_get_length:
233 * @multipart: a #SoupMultipart
235 * Gets the number of body parts in @multipart
237 * Return value: the number of body parts in @multipart
242 soup_multipart_get_length (SoupMultipart *multipart)
244 return multipart->bodies->len;
248 * soup_multipart_get_part:
249 * @multipart: a #SoupMultipart
250 * @part: the part number to get (counting from 0)
251 * @headers: (out) (transfer none): return location for the MIME part
253 * @body: (out) (transfer none): return location for the MIME part
256 * Gets the indicated body part from @multipart.
258 * Return value: %TRUE on success, %FALSE if @part is out of range (in
259 * which case @headers and @body won't be set)
264 soup_multipart_get_part (SoupMultipart *multipart, int part,
265 SoupMessageHeaders **headers, SoupBuffer **body)
267 if (part < 0 || part >= multipart->bodies->len)
269 *headers = multipart->headers->pdata[part];
270 *body = multipart->bodies->pdata[part];
275 * soup_multipart_append_part:
276 * @multipart: a #SoupMultipart
277 * @headers: the MIME part headers
278 * @body: the MIME part body
280 * Adds a new MIME part to @multipart with the given headers and body.
281 * (The multipart will make its own copies of @headers and @body, so
282 * you should free your copies if you are not using them for anything
288 soup_multipart_append_part (SoupMultipart *multipart,
289 SoupMessageHeaders *headers,
292 SoupMessageHeaders *headers_copy;
293 SoupMessageHeadersIter iter;
294 const char *name, *value;
296 /* Copying @headers is annoying, but the alternatives seem
299 * 1) We don't want to use g_boxed_copy, because
300 * SoupMessageHeaders actually implements that as just a
301 * ref, which would be confusing since SoupMessageHeaders
302 * is mutable and the caller might modify @headers after
305 * 2) We can't change SoupMessageHeaders to not just do a ref
306 * from g_boxed_copy, because that would break language
307 * bindings (which need to be able to hold a ref on
308 * msg->request_headers, but don't want to duplicate it).
310 * 3) We don't want to steal the reference to @headers,
311 * because then we'd have to either also steal the
312 * reference to @body (which would be inconsistent with
313 * other SoupBuffer methods), or NOT steal the reference to
314 * @body, in which case there'd be inconsistency just
315 * between the two arguments of this method!
317 headers_copy = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
318 soup_message_headers_iter_init (&iter, headers);
319 while (soup_message_headers_iter_next (&iter, &name, &value))
320 soup_message_headers_append (headers_copy, name, value);
322 g_ptr_array_add (multipart->headers, headers_copy);
323 g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
327 * soup_multipart_append_form_string:
328 * @multipart: a multipart (presumably of type "multipart/form-data")
329 * @control_name: the name of the control associated with @data
330 * @data: the body data
332 * Adds a new MIME part containing @data to @multipart, using
333 * "Content-Disposition: form-data", as per the HTML forms
334 * specification. See soup_form_request_new_from_multipart() for more
340 soup_multipart_append_form_string (SoupMultipart *multipart,
341 const char *control_name, const char *data)
345 body = soup_buffer_new (SOUP_MEMORY_COPY, data, strlen (data));
346 soup_multipart_append_form_file (multipart, control_name,
348 soup_buffer_free (body);
352 * soup_multipart_append_form_file:
353 * @multipart: a multipart (presumably of type "multipart/form-data")
354 * @control_name: the name of the control associated with this file
355 * @filename: the name of the file, or %NULL if not known
356 * @content_type: the MIME type of the file, or %NULL if not known
357 * @body: the file data
359 * Adds a new MIME part containing @body to @multipart, using
360 * "Content-Disposition: form-data", as per the HTML forms
361 * specification. See soup_form_request_new_from_multipart() for more
367 soup_multipart_append_form_file (SoupMultipart *multipart,
368 const char *control_name, const char *filename,
369 const char *content_type, SoupBuffer *body)
371 SoupMessageHeaders *headers;
372 GString *disposition;
374 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
375 disposition = g_string_new ("form-data; ");
376 soup_header_g_string_append_param_quoted (disposition, "name", control_name);
378 g_string_append (disposition, "; ");
379 soup_header_g_string_append_param_quoted (disposition, "filename", filename);
381 soup_message_headers_append (headers, "Content-Disposition",
383 g_string_free (disposition, TRUE);
386 soup_message_headers_append (headers, "Content-Type",
390 g_ptr_array_add (multipart->headers, headers);
391 g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
395 * soup_multipart_to_message:
396 * @multipart: a #SoupMultipart
397 * @dest_headers: the headers of the HTTP message to serialize @multipart to
398 * @dest_body: the body of the HTTP message to serialize @multipart to
400 * Serializes @multipart to @dest_headers and @dest_body.
405 soup_multipart_to_message (SoupMultipart *multipart,
406 SoupMessageHeaders *dest_headers,
407 SoupMessageBody *dest_body)
409 SoupMessageHeaders *part_headers;
410 SoupBuffer *part_body;
411 SoupMessageHeadersIter iter;
412 const char *name, *value;
417 params = g_hash_table_new (g_str_hash, g_str_equal);
418 g_hash_table_insert (params, "boundary", multipart->boundary);
419 soup_message_headers_set_content_type (dest_headers,
420 multipart->mime_type,
422 g_hash_table_destroy (params);
424 for (i = 0; i < multipart->bodies->len; i++) {
425 part_headers = multipart->headers->pdata[i];
426 part_body = multipart->bodies->pdata[i];
428 str = g_string_new (i == 0 ? NULL : "\r\n");
429 g_string_append (str, "--");
430 g_string_append (str, multipart->boundary);
431 g_string_append (str, "\r\n");
432 soup_message_headers_iter_init (&iter, part_headers);
433 while (soup_message_headers_iter_next (&iter, &name, &value))
434 g_string_append_printf (str, "%s: %s\r\n", name, value);
435 g_string_append (str, "\r\n");
436 soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
438 g_string_free (str, FALSE);
440 soup_message_body_append_buffer (dest_body, part_body);
443 str = g_string_new ("\r\n--");
444 g_string_append (str, multipart->boundary);
445 g_string_append (str, "--\r\n");
446 soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
448 g_string_free (str, FALSE);
450 /* (The "\r\n" after the close-delimiter seems wrong according
451 * to my reading of RFCs 2046 and 2616, but that's what
452 * everyone else does.)
457 * soup_multipart_free:
458 * @multipart: a #SoupMultipart
465 soup_multipart_free (SoupMultipart *multipart)
469 g_free (multipart->mime_type);
470 g_free (multipart->boundary);
471 for (i = 0; i < multipart->headers->len; i++)
472 soup_message_headers_free (multipart->headers->pdata[i]);
473 g_ptr_array_free (multipart->headers, TRUE);
474 for (i = 0; i < multipart->bodies->len; i++)
475 soup_buffer_free (multipart->bodies->pdata[i]);
476 g_ptr_array_free (multipart->bodies, TRUE);
478 g_slice_free (SoupMultipart, multipart);
481 static SoupMultipart *
482 soup_multipart_copy (SoupMultipart *multipart)
487 copy = soup_multipart_new_internal (g_strdup (multipart->mime_type),
488 g_strdup (multipart->boundary));
489 for (i = 0; i < multipart->bodies->len; i++) {
490 soup_multipart_append_part (copy,
491 multipart->headers->pdata[i],
492 multipart->bodies->pdata[i]);
498 soup_multipart_get_type (void)
500 static volatile gsize type_volatile = 0;
502 if (g_once_init_enter (&type_volatile)) {
503 GType type = g_boxed_type_register_static (
504 g_intern_static_string ("SoupMultipart"),
505 (GBoxedCopyFunc) soup_multipart_copy,
506 (GBoxedFreeFunc) soup_multipart_free);
507 g_once_init_leave (&type_volatile, type);
509 return type_volatile;