soup-auth-manager: add soup_auth_manager_use_auth()
[platform/upstream/libsoup.git] / libsoup / soup-form.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* soup-form.c : utility functions for HTML forms */
3
4 /*
5  * Copyright 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-form.h"
15 #include "soup.h"
16
17 /**
18  * SECTION:soup-form
19  * @short_description: HTML form handling
20  * @see_also: #SoupMultipart
21  *
22  * libsoup contains several help methods for processing HTML forms as
23  * defined by <ulink
24  * url="http://www.w3.org/TR/html401/interact/forms.html#h-17.13">the
25  * HTML 4.01 specification</ulink>.
26  **/
27
28 /**
29  * SOUP_FORM_MIME_TYPE_URLENCODED:
30  *
31  * A macro containing the value
32  * <literal>"application/x-www-form-urlencoded"</literal>; the default
33  * MIME type for POSTing HTML form data.
34  *
35  * Since: 2.26
36  **/
37
38 /**
39  * SOUP_FORM_MIME_TYPE_MULTIPART:
40  *
41  * A macro containing the value
42  * <literal>"multipart/form-data"</literal>; the MIME type used for
43  * posting form data that contains files to be uploaded.
44  *
45  * Since: 2.26
46  **/
47
48 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
49 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
50
51 static gboolean
52 form_decode (char *part)
53 {
54         unsigned char *s, *d;
55
56         s = d = (unsigned char *)part;
57         do {
58                 if (*s == '%') {
59                         if (!g_ascii_isxdigit (s[1]) ||
60                             !g_ascii_isxdigit (s[2]))
61                                 return FALSE;
62                         *d++ = HEXCHAR (s);
63                         s += 2;
64                 } else if (*s == '+')
65                         *d++ = ' ';
66                 else
67                         *d++ = *s;
68         } while (*s++);
69
70         return TRUE;
71 }
72
73 /**
74  * soup_form_decode:
75  * @encoded_form: data of type "application/x-www-form-urlencoded"
76  *
77  * Decodes @form, which is an urlencoded dataset as defined in the
78  * HTML 4.01 spec.
79  *
80  * Return value: (element-type utf8 utf8) (transfer full): a hash
81  * table containing the name/value pairs from @encoded_form, which you
82  * can free with g_hash_table_destroy().
83  **/
84 GHashTable *
85 soup_form_decode (const char *encoded_form)
86 {
87         GHashTable *form_data_set;
88         char **pairs, *eq, *name, *value;
89         int i;
90
91         form_data_set = g_hash_table_new_full (g_str_hash, g_str_equal,
92                                                g_free, NULL);
93         pairs = g_strsplit (encoded_form, "&", -1);
94         for (i = 0; pairs[i]; i++) {
95                 name = pairs[i];
96                 eq = strchr (name, '=');
97                 if (eq) {
98                         *eq = '\0';
99                         value = eq + 1;
100                 } else
101                         value = NULL;
102                 if (!value || !form_decode (name) || !form_decode (value)) {
103                         g_free (name);
104                         continue;
105                 }
106
107                 g_hash_table_replace (form_data_set, name, value);
108         }
109         g_free (pairs);
110
111         return form_data_set;
112 }
113
114 /**
115  * soup_form_decode_multipart:
116  * @msg: a #SoupMessage containing a "multipart/form-data" request body
117  * @file_control_name: (allow-none): the name of the HTML file upload control, or %NULL
118  * @filename: (out) (allow-none): return location for the name of the uploaded file, or %NULL
119  * @content_type: (out) (allow-none): return location for the MIME type of the uploaded file, or %NULL
120  * @file: (out) (allow-none): return location for the uploaded file data, or %NULL
121  *
122  * Decodes the "multipart/form-data" request in @msg; this is a
123  * convenience method for the case when you have a single file upload
124  * control in a form. (Or when you don't have any file upload
125  * controls, but are still using "multipart/form-data" anyway.) Pass
126  * the name of the file upload control in @file_control_name, and
127  * soup_form_decode_multipart() will extract the uploaded file data
128  * into @filename, @content_type, and @file. All of the other form
129  * control data will be returned (as strings, as with
130  * soup_form_decode()) in the returned #GHashTable.
131  *
132  * You may pass %NULL for @filename, @content_type and/or @file if you do not
133  * care about those fields. soup_form_decode_multipart() may also
134  * return %NULL in those fields if the client did not provide that
135  * information. You must free the returned filename and content-type
136  * with g_free(), and the returned file data with soup_buffer_free().
137  *
138  * If you have a form with more than one file upload control, you will
139  * need to decode it manually, using soup_multipart_new_from_message()
140  * and soup_multipart_get_part().
141  *
142  * Return value: (element-type utf8 utf8) (transfer full): a hash
143  * table containing the name/value pairs (other than
144  * @file_control_name) from @msg, which you can free with
145  * g_hash_table_destroy(). On error, it will return %NULL.
146  *
147  * Since: 2.26
148  **/
149 GHashTable *
150 soup_form_decode_multipart (SoupMessage *msg, const char *file_control_name,
151                             char **filename, char **content_type,
152                             SoupBuffer **file)
153 {
154         SoupMultipart *multipart;
155         GHashTable *form_data_set, *params;
156         SoupMessageHeaders *part_headers;
157         SoupBuffer *part_body;
158         char *disposition, *name;
159         int i;
160
161         g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
162
163         multipart = soup_multipart_new_from_message (msg->request_headers,
164                                                      msg->request_body);
165         if (!multipart)
166                 return NULL;
167
168         if (filename)
169                 *filename = NULL;
170         if (content_type)
171                 *content_type = NULL;
172         if (file)
173                 *file = NULL;
174
175         form_data_set = g_hash_table_new_full (g_str_hash, g_str_equal,
176                                                g_free, g_free);
177         for (i = 0; i < soup_multipart_get_length (multipart); i++) {
178                 soup_multipart_get_part (multipart, i, &part_headers, &part_body);
179                 if (!soup_message_headers_get_content_disposition (
180                             part_headers, &disposition, &params))
181                         continue;
182                 name = g_hash_table_lookup (params, "name");
183                 if (g_ascii_strcasecmp (disposition, "form-data") != 0 ||
184                     !name) {
185                         g_free (disposition);
186                         g_hash_table_destroy (params);
187                         continue;
188                 }
189
190                 if (file_control_name && !strcmp (name, file_control_name)) {
191                         if (filename)
192                                 *filename = g_strdup (g_hash_table_lookup (params, "filename"));
193                         if (content_type)
194                                 *content_type = g_strdup (soup_message_headers_get_content_type (part_headers, NULL));
195                         if (file)
196                                 *file = soup_buffer_copy (part_body);
197                 } else {
198                         g_hash_table_insert (form_data_set,
199                                              g_strdup (name),
200                                              g_strndup (part_body->data,
201                                                         part_body->length));
202                 }
203
204                 g_free (disposition);
205                 g_hash_table_destroy (params);
206         }
207
208         soup_multipart_free (multipart);
209         return form_data_set;
210 }
211
212 static void
213 append_form_encoded (GString *str, const char *in)
214 {
215         const unsigned char *s = (const unsigned char *)in;
216
217         while (*s) {
218                 if (*s == ' ') {
219                         g_string_append_c (str, '+');
220                         s++;
221                 } else if (!g_ascii_isalnum (*s))
222                         g_string_append_printf (str, "%%%02X", (int)*s++);
223                 else
224                         g_string_append_c (str, *s++);
225         }
226 }
227
228 static void
229 encode_pair (GString *str, const char *name, const char *value)
230 {
231         g_return_if_fail (name != NULL);
232         g_return_if_fail (value != NULL);
233
234         if (str->len)
235                 g_string_append_c (str, '&');
236         append_form_encoded (str, name);
237         g_string_append_c (str, '=');
238         append_form_encoded (str, value);
239 }
240
241 /**
242  * soup_form_encode:
243  * @first_field: name of the first form field
244  * @...: value of @first_field, followed by additional field names
245  * and values, terminated by %NULL.
246  *
247  * Encodes the given field names and values into a value of type
248  * "application/x-www-form-urlencoded", as defined in the HTML 4.01
249  * spec.
250  *
251  * This method requires you to know the names of the form fields (or
252  * at the very least, the total number of fields) at compile time; for
253  * working with dynamic forms, use soup_form_encode_hash() or
254  * soup_form_encode_datalist().
255  *
256  * Return value: the encoded form
257  **/
258 char *
259 soup_form_encode (const char *first_field, ...)
260 {
261         va_list args;
262         char *encoded;
263
264         va_start (args, first_field);
265         encoded = soup_form_encode_valist (first_field, args);
266         va_end (args);
267
268         return encoded;
269 }
270
271 /**
272  * soup_form_encode_hash:
273  * @form_data_set: (element-type utf8 utf8): a hash table containing
274  * name/value pairs (as strings)
275  *
276  * Encodes @form_data_set into a value of type
277  * "application/x-www-form-urlencoded", as defined in the HTML 4.01
278  * spec.
279  *
280  * Note that the HTML spec states that "The control names/values are
281  * listed in the order they appear in the document." Since this method
282  * takes a hash table, it cannot enforce that; if you care about the
283  * ordering of the form fields, use soup_form_encode_datalist().
284  *
285  * Return value: the encoded form
286  **/
287 char *
288 soup_form_encode_hash (GHashTable *form_data_set)
289 {
290         GString *str = g_string_new (NULL);
291         GHashTableIter iter;
292         gpointer name, value;
293
294         g_hash_table_iter_init (&iter, form_data_set);
295         while (g_hash_table_iter_next (&iter, &name, &value))
296                 encode_pair (str, name, value);
297         return g_string_free (str, FALSE);
298 }
299
300 static void
301 datalist_encode_foreach (GQuark key_id, gpointer value, gpointer str)
302 {
303         encode_pair (str, g_quark_to_string (key_id), value);
304 }
305
306 /**
307  * soup_form_encode_datalist:
308  * @form_data_set: a datalist containing name/value pairs
309  *
310  * Encodes @form_data_set into a value of type
311  * "application/x-www-form-urlencoded", as defined in the HTML 4.01
312  * spec. Unlike soup_form_encode_hash(), this preserves the ordering
313  * of the form elements, which may be required in some situations.
314  *
315  * Return value: the encoded form
316  **/
317 char *
318 soup_form_encode_datalist (GData **form_data_set)
319 {
320         GString *str = g_string_new (NULL);
321
322         g_datalist_foreach (form_data_set, datalist_encode_foreach, str);
323         return g_string_free (str, FALSE);
324 }
325
326 /**
327  * soup_form_encode_valist:
328  * @first_field: name of the first form field
329  * @args: pointer to additional values, as in soup_form_encode()
330  *
331  * See soup_form_encode(). This is mostly an internal method, used by
332  * various other methods such as soup_uri_set_query_from_fields() and
333  * soup_form_request_new().
334  *
335  * Return value: the encoded form
336  **/
337 char *
338 soup_form_encode_valist (const char *first_field, va_list args)
339 {
340         GString *str = g_string_new (NULL);
341         const char *name, *value;
342
343         name = first_field;
344         value = va_arg (args, const char *);
345         while (name && value) {
346                 encode_pair (str, name, value);
347
348                 name = va_arg (args, const char *);
349                 if (name)
350                         value = va_arg (args, const char *);
351         }
352
353         return g_string_free (str, FALSE);
354 }
355
356 static SoupMessage *
357 soup_form_request_for_data (const char *method, const char *uri_string,
358                             char *form_data)
359 {
360         SoupMessage *msg;
361         SoupURI *uri;
362
363         uri = soup_uri_new (uri_string);
364         if (!uri)
365                 return NULL;
366
367         if (!strcmp (method, "GET")) {
368                 g_free (uri->query);
369                 uri->query = form_data;
370
371                 msg = soup_message_new_from_uri (method, uri);
372         } else if (!strcmp (method, "POST") || !strcmp (method, "PUT")) {
373                 msg = soup_message_new_from_uri (method, uri);
374
375                 soup_message_set_request (
376                         msg, SOUP_FORM_MIME_TYPE_URLENCODED,
377                         SOUP_MEMORY_TAKE,
378                         form_data, strlen (form_data));
379         } else {
380                 g_warning ("invalid method passed to soup_form_request_new");
381                 g_free (form_data);
382
383                 /* Don't crash */
384                 msg = soup_message_new_from_uri (method, uri);
385         }
386         soup_uri_free (uri);
387
388         return msg;
389 }
390
391 /**
392  * soup_form_request_new:
393  * @method: the HTTP method, either "GET" or "POST"
394  * @uri: the URI to send the form data to
395  * @first_field: name of the first form field
396  * @...: value of @first_field, followed by additional field names
397  * and values, terminated by %NULL.
398  *
399  * Creates a new %SoupMessage and sets it up to send the given data
400  * to @uri via @method. (That is, if @method is "GET", it will encode
401  * the form data into @uri's query field, and if @method is "POST", it
402  * will encode it into the %SoupMessage's request_body.)
403  *
404  * Return value: (transfer full): the new %SoupMessage
405  **/
406 SoupMessage *
407 soup_form_request_new (const char *method, const char *uri,
408                        const char  *first_field, ...)
409 {
410         va_list args;
411         char *form_data;
412
413         va_start (args, first_field);
414         form_data = soup_form_encode_valist (first_field, args);
415         va_end (args);
416
417         return soup_form_request_for_data (method, uri, form_data);
418 }
419
420 /**
421  * soup_form_request_new_from_hash:
422  * @method: the HTTP method, either "GET" or "POST"
423  * @uri: the URI to send the form data to
424  * @form_data_set: (element-type utf8 utf8): the data to send to @uri
425  *
426  * Creates a new %SoupMessage and sets it up to send @form_data_set to
427  * @uri via @method, as with soup_form_request_new().
428  *
429  * Return value: (transfer full): the new %SoupMessage
430  **/
431 SoupMessage *
432 soup_form_request_new_from_hash (const char *method, const char *uri,
433                                  GHashTable *form_data_set)
434 {
435         return soup_form_request_for_data (
436                 method, uri, soup_form_encode_hash (form_data_set));
437 }
438
439 /**
440  * soup_form_request_new_from_datalist:
441  * @method: the HTTP method, either "GET" or "POST"
442  * @uri: the URI to send the form data to
443  * @form_data_set: the data to send to @uri
444  *
445  * Creates a new %SoupMessage and sets it up to send @form_data_set to
446  * @uri via @method, as with soup_form_request_new().
447  *
448  * Return value: (transfer full): the new %SoupMessage
449  **/
450 SoupMessage *
451 soup_form_request_new_from_datalist (const char *method, const char *uri,
452                                      GData **form_data_set)
453 {
454         return soup_form_request_for_data (
455                 method, uri, soup_form_encode_datalist (form_data_set));
456 }
457
458 /**
459  * soup_form_request_new_from_multipart:
460  * @uri: the URI to send the form data to
461  * @multipart: a "multipart/form-data" #SoupMultipart
462  *
463  * Creates a new %SoupMessage and sets it up to send @multipart to
464  * @uri via POST.
465  *
466  * To send a <literal>"multipart/form-data"</literal> POST, first
467  * create a #SoupMultipart, using %SOUP_FORM_MIME_TYPE_MULTIPART as
468  * the MIME type. Then use soup_multipart_append_form_string() and
469  * soup_multipart_append_form_file() to add the value of each form
470  * control to the multipart. (These are just convenience methods, and
471  * you can use soup_multipart_append_part() if you need greater
472  * control over the part headers.) Finally, call
473  * soup_form_request_new_from_multipart() to serialize the multipart
474  * structure and create a #SoupMessage.
475  *
476  * Return value: (transfer full): the new %SoupMessage
477  *
478  * Since: 2.26
479  **/
480 SoupMessage *
481 soup_form_request_new_from_multipart (const char *uri,
482                                       SoupMultipart *multipart)
483 {
484         SoupMessage *msg;
485
486         msg = soup_message_new ("POST", uri);
487         soup_multipart_to_message (multipart, msg->request_headers,
488                                    msg->request_body);
489         return msg;
490 }