Add gobject introspection
[profile/ivi/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., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, 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 /* Sets the Accept-Language on @message with the language taken from the
113  * current locale. */
114 void
115 http_request_set_accept_language (SoupMessage *message)
116 {
117         char *locale, *lang;
118         int dash_index;
119         GString *tmp;
120
121 #ifdef G_OS_WIN32
122         /* TODO: Use GetSystemDefaultLangID or similar */
123         return;
124 #else
125
126         locale = setlocale (LC_MESSAGES, NULL);
127
128         if (locale == NULL)
129                 return;
130
131         if (strcmp (locale, "C") == 0)
132                 return;
133
134         lang = g_strdup (locale);
135
136         dash_index = http_language_from_locale (lang);
137
138         tmp = g_string_new (lang);
139         g_string_append (tmp, ";q=1");
140
141         /* Append preference for basic (non-country specific) language version
142          * if applicable */
143         if (dash_index > 0) {
144                 g_string_append (tmp, ", ");
145
146                 lang[dash_index] = '\0';
147                 g_string_append (tmp, lang);
148                 g_string_append (tmp, ";q=0.5");
149         }
150
151         g_free (lang);
152
153         soup_message_headers_append (message->request_headers,
154                                      "Accept-Language",
155                                      tmp->str);
156
157         g_string_free (tmp, TRUE);
158 #endif
159 }
160
161 static double
162 get_quality (const char *val)
163 {
164         val = strstr (val, ";q=");
165         if (!val)
166                 return 1;
167
168         val += strlen (";q=");
169         return atof (val);
170 }
171
172 static int
173 sort_locales_by_quality (const char *a,
174                          const char *b)
175 {
176         const double diff = get_quality (a) - get_quality (b);
177
178         if (diff == 0.0)
179                 return 0;
180         else if (diff > 0)
181                 return -1;
182
183         return 1;
184 }
185
186 /* Parses the Accept-Language header in @message, and returns its values
187  * in an ordered list in UNIX locale format */
188 GList *
189 http_request_get_accept_locales (SoupMessage *message)
190 {
191         const char *header;
192         char **bits;
193         int i;
194         GList *locales;
195
196         header = soup_message_headers_get_one (message->request_headers,
197                                                "Accept-Language");
198         if (header == NULL)
199                 return NULL;
200
201         locales = NULL;
202
203         bits = g_strsplit (header, ",", -1);
204
205         /* Transform to list */
206         for (i = 0; bits[i] != NULL; i++) {
207                 bits[i] = g_strstrip (bits[i]);
208
209                 switch (bits[i][0]) {
210                 case '\0':
211                         /* Empty */
212                 case '*':
213                         /* Wildcard: ignore */
214                         g_free (bits[i]);
215
216                         break;
217                 default:
218                         locale_from_http_language (bits[i]);
219
220                         /* Because bits is sorted in ascending order */
221                         locales = g_list_prepend (locales, bits[i]);
222
223                         break;
224                 }
225         }
226
227         g_free (bits);
228
229         locales = g_list_sort (locales, (GCompareFunc) sort_locales_by_quality);
230
231         return locales;
232 }
233
234 /* Set Accept-Language header according to @locale. */
235 void
236 http_response_set_content_locale (SoupMessage *msg,
237                                   const char  *locale)
238 {
239         char *lang;
240
241         lang = g_strdup (locale);
242         http_language_from_locale (lang);
243
244         soup_message_headers_append (msg->response_headers,
245                                      "Content-Language",
246                                      lang);
247
248         g_free (lang);
249 }
250
251 /* Set Content-Type header guessed from @path, @data and @data_size using
252  * g_content_type_guess(). */
253 void
254 http_response_set_content_type (SoupMessage  *msg,
255                                 const char   *path,
256                                 const guchar *data,
257                                 gsize         data_size)
258 {
259         char *content_type, *mime;
260
261         content_type = g_content_type_guess
262                                 (path,
263                                  data,
264                                  data_size,
265                                  NULL);
266         mime = g_content_type_get_mime_type (content_type);
267         if (mime == NULL)
268                 mime = g_strdup ("application/octet-stream");
269         else if (strcmp (mime, "application/xml") == 0) {
270                 g_free (mime);
271                 mime = g_strdup ("text/xml; charset=\"utf-8\"");
272         }
273
274         soup_message_headers_append (msg->response_headers,
275                                      "Content-Type",
276                                      mime);
277
278         g_free (mime);
279         g_free (content_type);
280 }
281
282 /* Set Content-Encoding header to gzip and append compressed body */
283 void
284 http_response_set_body_gzip (SoupMessage *msg,
285                              const char  *body,
286                              const gsize  length)
287 {
288         GZlibCompressor *compressor;
289         gboolean finished = FALSE;
290         gsize converted = 0;
291
292         soup_message_headers_append (msg->response_headers,
293                                      "Content-Encoding", "gzip");
294
295         compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
296
297         while (! finished) {
298                 GError *error = NULL;
299                 char buf[65536];
300                 gsize bytes_read = 0;
301                 gsize bytes_written = 0;
302
303                 switch (g_converter_convert (G_CONVERTER (compressor),
304                                              body + converted,
305                                              length - converted,
306                                              buf, sizeof (buf),
307                                              G_CONVERTER_INPUT_AT_END,
308                                              &bytes_read, &bytes_written,
309                                              &error)) {
310                 case G_CONVERTER_ERROR:
311                         g_warning ("Error compressing response: %s",
312                                    error->message);
313                         g_error_free (error);
314                         g_object_unref (compressor);
315                         return;
316                 case G_CONVERTER_CONVERTED:
317                         converted += bytes_read;
318                         break;
319                 case G_CONVERTER_FINISHED:
320                         finished = TRUE;
321                         break;
322                 case G_CONVERTER_FLUSHED:
323                         break;
324                 }
325
326                 if (bytes_written)
327                         soup_message_body_append (msg->response_body,
328                                                   SOUP_MEMORY_COPY,
329                                                   buf, bytes_written);
330         }
331
332         g_object_unref (compressor);
333 }