tizen 2.3.1 release
[external/gupnp.git] / libgupnp / http-headers.c
1 /*
2  * Copyright (C) 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include <config.h>
23
24 #include <locale.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gio/gio.h>
28
29 #include "http-headers.h"
30
31 /* Converts @lang from HTTP language tag format into locale format.
32  * Return value: The index of the '-' character. */
33 static int
34 http_language_from_locale (char *lang)
35 {
36         gboolean tolower;
37         int i, dash_index;
38
39         tolower = FALSE;
40         dash_index = -1;
41
42         for (i = 0; lang[i] != '\0'; i++) {
43                 switch (lang[i]) {
44                 case '_':
45                         /* Underscores to dashes */
46                         lang[i] = '-';
47
48                         /* Lowercase country bit */
49                         tolower = TRUE;
50
51                         /* Save dash index */
52                         dash_index = i;
53
54                         break;
55                 case '.':
56                 case '@':
57                         /* Terminate our string here */
58                         lang[i] = '\0';
59
60                         return dash_index;
61                 default:
62                         if (tolower)
63                                 lang[i] = g_ascii_tolower (lang[i]);
64
65                         break;
66                 }
67         }
68
69         return dash_index;
70 }
71
72 /* Converts @lang from locale format into HTTP language tag format.
73  * Return value: The index of the '_' character. */
74 static int
75 locale_from_http_language (char *lang)
76 {
77         gboolean toupper;
78         int i, underscore_index;
79
80         toupper = FALSE;
81         underscore_index = -1;
82
83         for (i = 0; lang[i] != '\0'; i++) {
84                 switch (lang[i]) {
85                 case '-':
86                         /* Dashes to underscores */
87                         lang[i] = '_';
88
89                         /* Uppercase country bit */
90                         toupper = TRUE;
91
92                         /* Save underscore index */
93                         underscore_index = i;
94
95                         break;
96                 case ';':
97                         /* Terminate our string here */
98                         lang[i] = '\0';
99
100                         return underscore_index;
101                 default:
102                         if (toupper)
103                                 lang[i] = g_ascii_toupper (lang[i]);
104
105                         break;
106                 }
107         }
108
109         return underscore_index;
110 }
111
112 /* Parses the HTTP Range header on @message and sets:
113  *
114  * @have_range to %TRUE if a range was specified,
115  * @offset to the requested offset (left unchanged if none specified),
116  * @length to the requested length (left unchanged if none specified).
117  *
118  * Both @offset and @length are expected to be initialised to their default
119  * values.
120  *
121  * Returns %TRUE on success. */
122 gboolean
123 http_request_get_range (SoupMessage *message,
124                         gboolean    *have_range,
125                         gsize       *offset,
126                         gsize       *length)
127 {
128         const char *header;
129         char **v;
130
131         header = soup_message_headers_get_one (message->request_headers,
132                                                "Range");
133         if (header == NULL) {
134                 *have_range = FALSE;
135
136                 return TRUE;
137         }
138
139         /* We have a Range header. Parse. */
140         if (strncmp (header, "bytes=", 6) != 0)
141                 return FALSE;
142
143         header += 6;
144
145         v = g_strsplit (header, "-", 2);
146
147         /* Get first byte position */
148         if (v[0] != NULL && *v[0] != 0)
149                 *offset = atoll (v[0]);
150
151         else {
152                 /* We don't support ranges without first byte position */
153                 g_strfreev (v);
154
155                 return FALSE;
156         }
157
158         /* Get last byte position if specified */
159         if (v[1] != NULL && *v[1] != 0)
160                 *length = atoll (v[1]) - *offset;
161         else
162                 *length = *length - *offset;
163
164         *have_range = TRUE;
165
166         /* Cleanup */
167         g_strfreev (v);
168
169         return TRUE;
170 }
171
172 /* Sets the Accept-Language on @message with the language taken from the
173  * current locale. */
174 void
175 http_request_set_accept_language (SoupMessage *message)
176 {
177         char *locale, *lang;
178         int dash_index;
179         GString *tmp;
180
181         locale = setlocale (LC_ALL, NULL);
182         if (locale == NULL)
183                 return;
184
185         if (strcmp (locale, "C") == 0)
186                 return;
187
188         lang = g_strdup (locale);
189
190         dash_index = http_language_from_locale (lang);
191
192         tmp = g_string_new (lang);
193         g_string_append (tmp, ";q=1");
194
195         /* Append preference for basic (non-country specific) language version
196          * if applicable */
197         if (dash_index > 0) {
198                 g_string_append (tmp, ", ");
199
200                 lang[dash_index] = '\0';
201                 g_string_append (tmp, lang);
202                 g_string_append (tmp, ";q=0.5");
203         }
204
205         g_free (lang);
206
207         soup_message_headers_append (message->request_headers,
208                                      "Accept-Language",
209                                      tmp->str);
210
211         g_string_free (tmp, TRUE);
212 }
213
214 static double
215 get_quality (const char *val)
216 {
217         val = strstr (val, ";q=");
218         if (!val)
219                 return 1;
220
221         val += strlen (";q=");
222         return atof (val);
223 }
224
225 static int
226 sort_locales_by_quality (const char *a,
227                          const char *b)
228 {
229         const double diff = get_quality (a) - get_quality (b);
230
231         if (diff == 0.0)
232                 return 0;
233         else if (diff > 0)
234                 return -1;
235
236         return 1;
237 }
238
239 /* Parses the Accept-Language header in @message, and returns its values
240  * in an ordered list in UNIX locale format */
241 GList *
242 http_request_get_accept_locales (SoupMessage *message)
243 {
244         const char *header;
245         char **bits;
246         int i;
247         GList *locales;
248
249         header = soup_message_headers_get_one (message->request_headers,
250                                                "Accept-Language");
251         if (header == NULL)
252                 return NULL;
253
254         locales = NULL;
255
256         bits = g_strsplit (header, ",", -1);
257
258         /* Transform to list */
259         for (i = 0; bits[i] != NULL; i++) {
260                 bits[i] = g_strstrip (bits[i]);
261
262                 switch (bits[i][0]) {
263                 case '\0':
264                         /* Empty */
265                 case '*':
266                         /* Wildcard: ignore */
267                         g_free (bits[i]);
268
269                         break;
270                 default:
271                         locale_from_http_language (bits[i]);
272
273                         /* Because bits is sorted in ascending order */
274                         locales = g_list_prepend (locales, bits[i]);
275
276                         break;
277                 }
278         }
279
280         g_free (bits);
281
282         locales = g_list_sort (locales, (GCompareFunc) sort_locales_by_quality);
283
284         return locales;
285 }
286
287 /* Set Accept-Language header according to @locale. */
288 void
289 http_response_set_content_locale (SoupMessage *msg,
290                                   const char  *locale)
291 {
292         char *lang;
293
294         lang = g_strdup (locale);
295         http_language_from_locale (lang);
296
297         soup_message_headers_append (msg->response_headers,
298                                      "Content-Language",
299                                      lang);
300
301         g_free (lang);
302 }
303
304 /* Set Content-Type header guessed from @path, @data and @data_size using
305  * g_content_type_guess(). */
306 void
307 http_response_set_content_type (SoupMessage  *msg,
308                                 const char   *path,
309                                 const guchar *data,
310                                 gsize         data_size)
311 {
312         char *content_type, *mime;
313
314         content_type = g_content_type_guess
315                                 (path,
316                                  data,
317                                  data_size,
318                                  NULL);
319         mime = g_content_type_get_mime_type (content_type);
320         if (mime == NULL)
321                 mime = g_strdup ("application/octet-stream");
322         else if (strcmp (mime, "application/xml") == 0) {
323                 g_free (mime);
324                 mime = g_strdup ("text/xml; charset=\"utf-8\"");
325         }
326
327         soup_message_headers_append (msg->response_headers,
328                                      "Content-Type",
329                                      mime);
330
331         g_free (mime);
332         g_free (content_type);
333 }
334
335 /* Set Content-Range header */
336 void
337 http_response_set_content_range (SoupMessage  *msg,
338                                  gsize         offset,
339                                  gsize         length,
340                                  gsize         total)
341 {
342         char *content_range;
343
344         content_range = g_strdup_printf
345                 ("bytes %" G_GSIZE_FORMAT "-%"
346                  G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT,
347                  offset,
348                  offset + length,
349                  total);
350
351         soup_message_headers_append (msg->response_headers,
352                                      "Content-Range",
353                                      content_range);
354
355         g_free (content_range);
356 }