Git init
[profile/ivi/libsoup2.4.git] / libsoup / soup-multipart.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-multipart.c: multipart HTTP message bodies
4  *
5  * Copyright (C) 2008 Red Hat, Inc.
6  */
7
8 #include <string.h>
9
10 #include "soup-multipart.h"
11 #include "soup-headers.h"
12
13 /**
14  * SECTION:soup-multipart
15  * @short_description: multipart HTTP message bodies
16  * @see_also: #SoupMessageBody, #SoupMessageHeaders
17  *
18  **/
19
20 /**
21  * SoupMultipart:
22  *
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>.
27  *
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.
34  *
35  * Since: 2.26
36  **/
37
38 struct SoupMultipart {
39         char *mime_type, *boundary;
40         GPtrArray *headers, *bodies;
41 };
42
43 static SoupMultipart *
44 soup_multipart_new_internal (char *mime_type, char *boundary)
45 {
46         SoupMultipart *multipart;
47
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 ();
53
54         return multipart;
55 }
56
57 static char *
58 generate_boundary (void)
59 {
60         static int counter;
61         struct {
62                 GTimeVal timeval;
63                 int counter;
64         } data;
65
66         /* avoid valgrind warning */
67         if (sizeof (data) != sizeof (data.timeval) + sizeof (data.counter))
68                 memset (&data, 0, sizeof (data));
69
70         g_get_current_time (&data.timeval);
71         data.counter = counter++;
72
73         /* The maximum boundary string length is 69 characters, and a
74          * stringified SHA256 checksum is 64 bytes long.
75          */
76         return g_compute_checksum_for_data (G_CHECKSUM_SHA256,
77                                             (const guchar *)&data,
78                                             sizeof (data));
79 }
80
81 /**
82  * soup_multipart_new:
83  * @mime_type: the MIME type of the multipart to create.
84  *
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/".
88  *
89  * Return value: a new empty #SoupMultipart of the given @mime_type
90  *
91  * Since: 2.26
92  **/
93 SoupMultipart *
94 soup_multipart_new (const char *mime_type)
95 {
96         return soup_multipart_new_internal (g_strdup (mime_type),
97                                             generate_boundary ());
98 }
99
100 static const char *
101 find_boundary (const char *start, const char *end,
102                const char *boundary, int boundary_len)
103 {
104         const char *b;
105
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" */
110                 if (b[1] != '-' ||
111                     memcmp (b + 2, boundary, boundary_len) != 0)
112                         continue;
113
114                 /* Check that it's at start of line */
115                 if (!(b == start || (b[-1] == '\n' && b[-2] == '\r')))
116                         continue;
117
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'))
121                         return b;
122         }
123         return NULL;
124 }
125
126 /**
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
130  *
131  * Parses @headers and @body to form a new #SoupMultipart
132  *
133  * Return value: a new #SoupMultipart (or %NULL if the message couldn't
134  * be parsed or wasn't multipart).
135  *
136  * Since: 2.26
137  **/
138 SoupMultipart *
139 soup_multipart_new_from_message (SoupMessageHeaders *headers,
140                                  SoupMessageBody *body)
141 {
142         SoupMultipart *multipart;
143         const char *content_type, *boundary;
144         GHashTable *params;
145         int boundary_len;
146         SoupBuffer *flattened;
147         const char *start, *split, *end, *body_end;
148         SoupMessageHeaders *part_headers;
149         SoupBuffer *part_body;
150
151         content_type = soup_message_headers_get_content_type (headers, &params);
152         if (!content_type)
153                 return NULL;
154
155         boundary = g_hash_table_lookup (params, "boundary");
156         if (strncmp (content_type, "multipart/", 10) != 0 || !boundary) {
157                 g_hash_table_destroy (params);
158                 return NULL;
159         }
160
161         multipart = soup_multipart_new_internal (
162                 g_strdup (content_type), g_strdup (boundary));
163         g_hash_table_destroy (params);
164
165         flattened = soup_message_body_flatten (body);
166         body_end = flattened->data + flattened->length;
167         boundary = multipart->boundary;
168         boundary_len = strlen (boundary);
169
170         /* skip preamble */
171         start = find_boundary (flattened->data, body_end,
172                                boundary, boundary_len);
173         if (!start) {
174                 soup_multipart_free (multipart);
175                 soup_buffer_free (flattened);
176                 return NULL;
177         }
178
179         while (start[2 + boundary_len] != '-') {
180                 end = find_boundary (start + 2 + boundary_len, body_end,
181                                      boundary, boundary_len);
182                 if (!end) {
183                         soup_multipart_free (multipart);
184                         soup_buffer_free (flattened);
185                         return NULL;
186                 }
187
188                 split = strstr (start, "\r\n\r\n");
189                 if (!split || split > end) {
190                         soup_multipart_free (multipart);
191                         soup_buffer_free (flattened);
192                         return NULL;
193                 }
194                 split += 4;
195
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.
199                  *
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).
204                  */
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,
208                                          part_headers)) {
209                         soup_multipart_free (multipart);
210                         soup_buffer_free (flattened);
211                         return NULL;
212                 }
213
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.
218                  */
219                 part_body = soup_buffer_new_subbuffer (flattened,
220                                                        split - flattened->data,
221                                                        end - 2 - split);
222                 g_ptr_array_add (multipart->bodies, part_body);
223
224                 start = end;
225         }
226
227         soup_buffer_free (flattened);
228         return multipart;
229 }
230
231 /**
232  * soup_multipart_get_length:
233  * @multipart: a #SoupMultipart
234  *
235  * Gets the number of body parts in @multipart
236  *
237  * Return value: the number of body parts in @multipart
238  *
239  * Since: 2.26
240  **/
241 int
242 soup_multipart_get_length (SoupMultipart *multipart)
243 {
244         return multipart->bodies->len;
245 }
246
247 /**
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
252  * headers
253  * @body: (out) (transfer none): return location for the MIME part
254  * body
255  *
256  * Gets the indicated body part from @multipart.
257  *
258  * Return value: %TRUE on success, %FALSE if @part is out of range (in
259  * which case @headers and @body won't be set)
260  *
261  * Since: 2.26
262  **/
263 gboolean
264 soup_multipart_get_part (SoupMultipart *multipart, int part,
265                          SoupMessageHeaders **headers, SoupBuffer **body)
266 {
267         if (part < 0 || part >= multipart->bodies->len)
268                 return FALSE;
269         *headers = multipart->headers->pdata[part];
270         *body = multipart->bodies->pdata[part];
271         return TRUE;
272 }
273
274 /**
275  * soup_multipart_append_part:
276  * @multipart: a #SoupMultipart
277  * @headers: the MIME part headers
278  * @body: the MIME part body
279  *
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
283  * else.)
284  *
285  * Since: 2.26
286  **/
287 void
288 soup_multipart_append_part (SoupMultipart      *multipart,
289                             SoupMessageHeaders *headers,
290                             SoupBuffer         *body)
291 {
292         SoupMessageHeaders *headers_copy;
293         SoupMessageHeadersIter iter;
294         const char *name, *value;
295
296         /* Copying @headers is annoying, but the alternatives seem
297          * worse:
298          *
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
303          *    appending it.
304          *
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).
309          *
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!
316          */
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);
321
322         g_ptr_array_add (multipart->headers, headers_copy);
323         g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
324 }
325
326 /**
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
331  *
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
335  * details.
336  *
337  * Since: 2.26
338  **/ 
339 void
340 soup_multipart_append_form_string (SoupMultipart *multipart,
341                                    const char *control_name, const char *data)
342 {
343         SoupBuffer *body;
344
345         body = soup_buffer_new (SOUP_MEMORY_COPY, data, strlen (data));
346         soup_multipart_append_form_file (multipart, control_name,
347                                          NULL, NULL, body);
348         soup_buffer_free (body);
349 }
350
351 /**
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
358  *
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
362  * details.
363  *
364  * Since: 2.26
365  **/ 
366 void
367 soup_multipart_append_form_file (SoupMultipart *multipart,
368                                  const char *control_name, const char *filename,
369                                  const char *content_type, SoupBuffer *body)
370 {
371         SoupMessageHeaders *headers;
372         GString *disposition;
373
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);
377         if (filename) {
378                 g_string_append (disposition, "; ");
379                 soup_header_g_string_append_param_quoted (disposition, "filename", filename);
380         }
381         soup_message_headers_append (headers, "Content-Disposition",
382                                      disposition->str);
383         g_string_free (disposition, TRUE);
384
385         if (content_type) {
386                 soup_message_headers_append (headers, "Content-Type",
387                                              content_type);
388         }
389
390         g_ptr_array_add (multipart->headers, headers);
391         g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
392 }
393
394 /**
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
399  *
400  * Serializes @multipart to @dest_headers and @dest_body.
401  *
402  * Since: 2.26
403  **/
404 void
405 soup_multipart_to_message (SoupMultipart *multipart,
406                            SoupMessageHeaders *dest_headers,
407                            SoupMessageBody *dest_body)
408 {
409         SoupMessageHeaders *part_headers;
410         SoupBuffer *part_body;
411         SoupMessageHeadersIter iter;
412         const char *name, *value;
413         GString *str;
414         GHashTable *params;
415         int i;
416
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,
421                                                params);
422         g_hash_table_destroy (params);
423
424         for (i = 0; i < multipart->bodies->len; i++) {
425                 part_headers = multipart->headers->pdata[i];
426                 part_body = multipart->bodies->pdata[i];
427
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,
437                                           str->str, str->len);
438                 g_string_free (str, FALSE);
439
440                 soup_message_body_append_buffer (dest_body, part_body);
441         }
442
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,
447                                   str->str, str->len);
448         g_string_free (str, FALSE);
449
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.)
453          */
454 }
455
456 /**
457  * soup_multipart_free:
458  * @multipart: a #SoupMultipart
459  *
460  * Frees @multipart
461  *
462  * Since: 2.26
463  **/
464 void
465 soup_multipart_free (SoupMultipart *multipart)
466 {
467         int i;
468
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);
477
478         g_slice_free (SoupMultipart, multipart);
479 }
480
481 static SoupMultipart *
482 soup_multipart_copy (SoupMultipart *multipart)
483 {
484         SoupMultipart *copy;
485         int i;
486
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]);
493         }
494         return copy;
495 }
496
497 GType
498 soup_multipart_get_type (void)
499 {
500         static volatile gsize type_volatile = 0;
501
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);
508         }
509         return type_volatile;
510 }