• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11 
12 #include <string.h>
13 
14 #include "soup-multipart.h"
15 #include "soup.h"
16 
17 /**
18  * SECTION:soup-multipart
19  * @short_description: multipart HTTP message bodies
20  * @see_also: #SoupMessageBody, #SoupMessageHeaders
21  *
22  **/
23 
24 /**
25  * SoupMultipart:
26  *
27  * Represents a multipart HTTP message body, parsed according to the
28  * syntax of RFC 2046. Of particular interest to HTTP are
29  * <literal>multipart/byte-ranges</literal> and
30  * <literal>multipart/form-data</literal>.
31  *
32  * Although the headers of a #SoupMultipart body part will contain the
33  * full headers from that body part, libsoup does not interpret them
34  * according to MIME rules. For example, each body part is assumed to
35  * have "binary" Content-Transfer-Encoding, even if its headers
36  * explicitly state otherwise. In other words, don't try to use
37  * #SoupMultipart for handling real MIME multiparts.
38  *
39  * Since: 2.26
40  **/
41 
42 struct SoupMultipart {
43 	char *mime_type, *boundary;
44 	GPtrArray *headers, *bodies;
45 };
46 
47 static SoupMultipart *
soup_multipart_new_internal(char * mime_type,char * boundary)48 soup_multipart_new_internal (char *mime_type, char *boundary)
49 {
50 	SoupMultipart *multipart;
51 
52 	multipart = g_slice_new (SoupMultipart);
53 	multipart->mime_type = mime_type;
54 	multipart->boundary = boundary;
55 	multipart->headers = g_ptr_array_new_with_free_func ((GDestroyNotify)soup_message_headers_free);
56 	multipart->bodies = g_ptr_array_new_with_free_func ((GDestroyNotify)soup_buffer_free);
57 
58 	return multipart;
59 }
60 
61 static char *
generate_boundary(void)62 generate_boundary (void)
63 {
64 	guint32 data[2];
65 
66 	data[0] = g_random_int ();
67 	data[1] = g_random_int ();
68 
69 	/* The maximum boundary string length is 69 characters, and a
70 	 * stringified SHA256 checksum is 64 bytes long.
71 	 */
72 	return g_compute_checksum_for_data (G_CHECKSUM_SHA256,
73 					    (const guchar *)&data,
74 					    sizeof (data));
75 }
76 
77 /**
78  * soup_multipart_new:
79  * @mime_type: the MIME type of the multipart to create.
80  *
81  * Creates a new empty #SoupMultipart with a randomly-generated
82  * boundary string. Note that @mime_type must be the full MIME type,
83  * including "multipart/".
84  *
85  * Return value: a new empty #SoupMultipart of the given @mime_type
86  *
87  * Since: 2.26
88  **/
89 SoupMultipart *
soup_multipart_new(const char * mime_type)90 soup_multipart_new (const char *mime_type)
91 {
92 	return soup_multipart_new_internal (g_strdup (mime_type),
93 					    generate_boundary ());
94 }
95 
96 static const char *
find_boundary(const char * start,const char * end,const char * boundary,int boundary_len)97 find_boundary (const char *start, const char *end,
98 	       const char *boundary, int boundary_len)
99 {
100 	const char *b;
101 
102 	for (b = memchr (start, '-', end - start);
103 	     b && b + boundary_len + 4 < end;
104 	     b = memchr (b + 2, '-', end - (b + 2))) {
105 		/* Check for "--boundary" */
106 		if (b[1] != '-' ||
107 		    memcmp (b + 2, boundary, boundary_len) != 0)
108 			continue;
109 
110 		/* Check that it's at start of line */
111 		if (!(b == start || (b[-1] == '\n' && b[-2] == '\r')))
112 			continue;
113 
114 		/* Check for "--" or "\r\n" after boundary */
115 		if ((b[boundary_len + 2] == '-' && b[boundary_len + 3] == '-') ||
116 		    (b[boundary_len + 2] == '\r' && b[boundary_len + 3] == '\n'))
117 			return b;
118 	}
119 	return NULL;
120 }
121 
122 /**
123  * soup_multipart_new_from_message:
124  * @headers: the headers of the HTTP message to parse
125  * @body: the body of the HTTP message to parse
126  *
127  * Parses @headers and @body to form a new #SoupMultipart
128  *
129  * Return value: (nullable): a new #SoupMultipart (or %NULL if the
130  * message couldn't be parsed or wasn't multipart).
131  *
132  * Since: 2.26
133  **/
134 SoupMultipart *
soup_multipart_new_from_message(SoupMessageHeaders * headers,SoupMessageBody * body)135 soup_multipart_new_from_message (SoupMessageHeaders *headers,
136 				 SoupMessageBody *body)
137 {
138 	SoupMultipart *multipart;
139 	const char *content_type, *boundary;
140 	GHashTable *params;
141 	int boundary_len;
142 	SoupBuffer *flattened;
143 	const char *start, *split, *end, *body_end;
144 	SoupMessageHeaders *part_headers;
145 	SoupBuffer *part_body;
146 
147 	content_type = soup_message_headers_get_content_type (headers, &params);
148 	if (!content_type)
149 		return NULL;
150 
151 	boundary = g_hash_table_lookup (params, "boundary");
152 	if (strncmp (content_type, "multipart/", 10) != 0 || !boundary) {
153 		g_hash_table_destroy (params);
154 		return NULL;
155 	}
156 
157 	multipart = soup_multipart_new_internal (
158 		g_strdup (content_type), g_strdup (boundary));
159 	g_hash_table_destroy (params);
160 
161 	flattened = soup_message_body_flatten (body);
162 	body_end = flattened->data + flattened->length;
163 	boundary = multipart->boundary;
164 	boundary_len = strlen (boundary);
165 
166 	/* skip preamble */
167 	start = find_boundary (flattened->data, body_end,
168 			       boundary, boundary_len);
169 	if (!start) {
170 		soup_multipart_free (multipart);
171 		soup_buffer_free (flattened);
172 		return NULL;
173 	}
174 
175 	while (start[2 + boundary_len] != '-') {
176 		end = find_boundary (start + 2 + boundary_len, body_end,
177 				     boundary, boundary_len);
178 		if (!end) {
179 			soup_multipart_free (multipart);
180 			soup_buffer_free (flattened);
181 			return NULL;
182 		}
183 
184 		split = strstr (start, "\r\n\r\n");
185 		if (!split || split > end) {
186 			soup_multipart_free (multipart);
187 			soup_buffer_free (flattened);
188 			return NULL;
189 		}
190 		split += 4;
191 
192 		/* @start points to the start of the boundary line
193 		 * preceding this part, and @split points to the end
194 		 * of the headers / start of the body.
195 		 *
196 		 * We tell soup_headers_parse() to start parsing at
197 		 * @start, because it skips the first line of the
198 		 * input anyway (expecting it to be either a
199 		 * Request-Line or Status-Line).
200 		 */
201 		part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
202 		g_ptr_array_add (multipart->headers, part_headers);
203 		if (!soup_headers_parse (start, split - 2 - start,
204 					 part_headers)) {
205 			soup_multipart_free (multipart);
206 			soup_buffer_free (flattened);
207 			return NULL;
208 		}
209 
210 		/* @split, as previously mentioned, points to the
211 		 * start of the body, and @end points to the start of
212 		 * the following boundary line, which is to say 2 bytes
213 		 * after the end of the body.
214 		 */
215 		part_body = soup_buffer_new_subbuffer (flattened,
216 						       split - flattened->data,
217 						       end - 2 - split);
218 		g_ptr_array_add (multipart->bodies, part_body);
219 
220 		start = end;
221 	}
222 
223 	soup_buffer_free (flattened);
224 	return multipart;
225 }
226 
227 /**
228  * soup_multipart_get_length:
229  * @multipart: a #SoupMultipart
230  *
231  * Gets the number of body parts in @multipart
232  *
233  * Return value: the number of body parts in @multipart
234  *
235  * Since: 2.26
236  **/
237 int
soup_multipart_get_length(SoupMultipart * multipart)238 soup_multipart_get_length (SoupMultipart *multipart)
239 {
240 	return multipart->bodies->len;
241 }
242 
243 /**
244  * soup_multipart_get_part:
245  * @multipart: a #SoupMultipart
246  * @part: the part number to get (counting from 0)
247  * @headers: (out) (transfer none): return location for the MIME part
248  * headers
249  * @body: (out) (transfer none): return location for the MIME part
250  * body
251  *
252  * Gets the indicated body part from @multipart.
253  *
254  * Return value: %TRUE on success, %FALSE if @part is out of range (in
255  * which case @headers and @body won't be set)
256  *
257  * Since: 2.26
258  **/
259 gboolean
soup_multipart_get_part(SoupMultipart * multipart,int part,SoupMessageHeaders ** headers,SoupBuffer ** body)260 soup_multipart_get_part (SoupMultipart *multipart, int part,
261 			 SoupMessageHeaders **headers, SoupBuffer **body)
262 {
263 	if (part < 0 || part >= multipart->bodies->len)
264 		return FALSE;
265 	*headers = multipart->headers->pdata[part];
266 	*body = multipart->bodies->pdata[part];
267 	return TRUE;
268 }
269 
270 /**
271  * soup_multipart_append_part:
272  * @multipart: a #SoupMultipart
273  * @headers: the MIME part headers
274  * @body: the MIME part body
275  *
276  * Adds a new MIME part to @multipart with the given headers and body.
277  * (The multipart will make its own copies of @headers and @body, so
278  * you should free your copies if you are not using them for anything
279  * else.)
280  *
281  * Since: 2.26
282  **/
283 void
soup_multipart_append_part(SoupMultipart * multipart,SoupMessageHeaders * headers,SoupBuffer * body)284 soup_multipart_append_part (SoupMultipart      *multipart,
285 			    SoupMessageHeaders *headers,
286 			    SoupBuffer         *body)
287 {
288 	SoupMessageHeaders *headers_copy;
289 	SoupMessageHeadersIter iter;
290 	const char *name, *value;
291 
292 	/* Copying @headers is annoying, but the alternatives seem
293 	 * worse:
294 	 *
295 	 * 1) We don't want to use g_boxed_copy, because
296 	 *    SoupMessageHeaders actually implements that as just a
297 	 *    ref, which would be confusing since SoupMessageHeaders
298 	 *    is mutable and the caller might modify @headers after
299 	 *    appending it.
300 	 *
301 	 * 2) We can't change SoupMessageHeaders to not just do a ref
302 	 *    from g_boxed_copy, because that would break language
303 	 *    bindings (which need to be able to hold a ref on
304 	 *    msg->request_headers, but don't want to duplicate it).
305 	 *
306 	 * 3) We don't want to steal the reference to @headers,
307 	 *    because then we'd have to either also steal the
308 	 *    reference to @body (which would be inconsistent with
309 	 *    other SoupBuffer methods), or NOT steal the reference to
310 	 *    @body, in which case there'd be inconsistency just
311 	 *    between the two arguments of this method!
312 	 */
313 	headers_copy = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
314 	soup_message_headers_iter_init (&iter, headers);
315 	while (soup_message_headers_iter_next (&iter, &name, &value))
316 		soup_message_headers_append (headers_copy, name, value);
317 
318 	g_ptr_array_add (multipart->headers, headers_copy);
319 	g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
320 }
321 
322 /**
323  * soup_multipart_append_form_string:
324  * @multipart: a multipart (presumably of type "multipart/form-data")
325  * @control_name: the name of the control associated with @data
326  * @data: the body data
327  *
328  * Adds a new MIME part containing @data to @multipart, using
329  * "Content-Disposition: form-data", as per the HTML forms
330  * specification. See soup_form_request_new_from_multipart() for more
331  * details.
332  *
333  * Since: 2.26
334  **/
335 void
soup_multipart_append_form_string(SoupMultipart * multipart,const char * control_name,const char * data)336 soup_multipart_append_form_string (SoupMultipart *multipart,
337 				   const char *control_name, const char *data)
338 {
339 	SoupBuffer *body;
340 
341 	body = soup_buffer_new (SOUP_MEMORY_COPY, data, strlen (data));
342 	soup_multipart_append_form_file (multipart, control_name,
343 					 NULL, NULL, body);
344 	soup_buffer_free (body);
345 }
346 
347 /**
348  * soup_multipart_append_form_file:
349  * @multipart: a multipart (presumably of type "multipart/form-data")
350  * @control_name: the name of the control associated with this file
351  * @filename: the name of the file, or %NULL if not known
352  * @content_type: the MIME type of the file, or %NULL if not known
353  * @body: the file data
354  *
355  * Adds a new MIME part containing @body to @multipart, using
356  * "Content-Disposition: form-data", as per the HTML forms
357  * specification. See soup_form_request_new_from_multipart() for more
358  * details.
359  *
360  * Since: 2.26
361  **/
362 void
soup_multipart_append_form_file(SoupMultipart * multipart,const char * control_name,const char * filename,const char * content_type,SoupBuffer * body)363 soup_multipart_append_form_file (SoupMultipart *multipart,
364 				 const char *control_name, const char *filename,
365 				 const char *content_type, SoupBuffer *body)
366 {
367 	SoupMessageHeaders *headers;
368 	GString *disposition;
369 
370 	headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
371 	disposition = g_string_new ("form-data; ");
372 	soup_header_g_string_append_param_quoted (disposition, "name", control_name);
373 	if (filename) {
374 		g_string_append (disposition, "; ");
375 		soup_header_g_string_append_param_quoted (disposition, "filename", filename);
376 	}
377 	soup_message_headers_append (headers, "Content-Disposition",
378 				     disposition->str);
379 	g_string_free (disposition, TRUE);
380 
381 	if (content_type) {
382 		soup_message_headers_append (headers, "Content-Type",
383 					     content_type);
384 	}
385 
386 	g_ptr_array_add (multipart->headers, headers);
387 	g_ptr_array_add (multipart->bodies, soup_buffer_copy (body));
388 }
389 
390 /**
391  * soup_multipart_to_message:
392  * @multipart: a #SoupMultipart
393  * @dest_headers: the headers of the HTTP message to serialize @multipart to
394  * @dest_body: the body of the HTTP message to serialize @multipart to
395  *
396  * Serializes @multipart to @dest_headers and @dest_body.
397  *
398  * Since: 2.26
399  **/
400 void
soup_multipart_to_message(SoupMultipart * multipart,SoupMessageHeaders * dest_headers,SoupMessageBody * dest_body)401 soup_multipart_to_message (SoupMultipart *multipart,
402 			   SoupMessageHeaders *dest_headers,
403 			   SoupMessageBody *dest_body)
404 {
405 	SoupMessageHeaders *part_headers;
406 	SoupBuffer *part_body;
407 	SoupMessageHeadersIter iter;
408 	const char *name, *value;
409 	GString *str;
410 	GHashTable *params;
411 	guint i;
412 
413 	params = g_hash_table_new (g_str_hash, g_str_equal);
414 	g_hash_table_insert (params, "boundary", multipart->boundary);
415 	soup_message_headers_set_content_type (dest_headers,
416 					       multipart->mime_type,
417 					       params);
418 	g_hash_table_destroy (params);
419 
420 	for (i = 0; i < multipart->bodies->len; i++) {
421 		part_headers = multipart->headers->pdata[i];
422 		part_body = multipart->bodies->pdata[i];
423 
424 		str = g_string_new (i == 0 ? NULL : "\r\n");
425 		g_string_append (str, "--");
426 		g_string_append (str, multipart->boundary);
427 		g_string_append (str, "\r\n");
428 		soup_message_headers_iter_init (&iter, part_headers);
429 		while (soup_message_headers_iter_next (&iter, &name, &value))
430 			g_string_append_printf (str, "%s: %s\r\n", name, value);
431 		g_string_append (str, "\r\n");
432 		soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
433 					  str->str, str->len);
434 		g_string_free (str, FALSE);
435 
436 		soup_message_body_append_buffer (dest_body, part_body);
437 	}
438 
439 	str = g_string_new ("\r\n--");
440 	g_string_append (str, multipart->boundary);
441 	g_string_append (str, "--\r\n");
442 	soup_message_body_append (dest_body, SOUP_MEMORY_TAKE,
443 				  str->str, str->len);
444 	g_string_free (str, FALSE);
445 
446 	/* (The "\r\n" after the close-delimiter seems wrong according
447 	 * to my reading of RFCs 2046 and 2616, but that's what
448 	 * everyone else does.)
449 	 */
450 }
451 
452 /**
453  * soup_multipart_free:
454  * @multipart: a #SoupMultipart
455  *
456  * Frees @multipart
457  *
458  * Since: 2.26
459  **/
460 void
soup_multipart_free(SoupMultipart * multipart)461 soup_multipart_free (SoupMultipart *multipart)
462 {
463 	g_free (multipart->mime_type);
464 	g_free (multipart->boundary);
465 	g_ptr_array_free (multipart->headers, TRUE);
466 	g_ptr_array_free (multipart->bodies, TRUE);
467 
468 	g_slice_free (SoupMultipart, multipart);
469 }
470 
471 static SoupMultipart *
soup_multipart_copy(SoupMultipart * multipart)472 soup_multipart_copy (SoupMultipart *multipart)
473 {
474 	SoupMultipart *copy;
475 	guint i;
476 
477 	copy = soup_multipart_new_internal (g_strdup (multipart->mime_type),
478 					    g_strdup (multipart->boundary));
479 	for (i = 0; i < multipart->bodies->len; i++) {
480 		soup_multipart_append_part (copy,
481 					    multipart->headers->pdata[i],
482 					    multipart->bodies->pdata[i]);
483 	}
484 	return copy;
485 }
486 
487 G_DEFINE_BOXED_TYPE (SoupMultipart, soup_multipart, soup_multipart_copy, soup_multipart_free)
488