tizen 2.3.1 release
[external/gupnp.git] / libgupnp / gupnp-context.c
1 /*
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  * Copyright (C) 2009 Nokia Corporation, all rights reserved.
4  *
5  * Author: Jorn Baayen <jorn@openedhand.com>
6  *         Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
7  *                               <zeeshan.ali@nokia.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:gupnp-context
27  * @short_description: Context object wrapping shared networking bits.
28  *
29  * #GUPnPContext wraps the networking bits that are used by the various
30  * GUPnP classes. It automatically starts a web server on demand.
31  *
32  * For debugging, it is possible to see the messages being sent and received by
33  * exporting #GUPNP_DEBUG.
34  */
35
36 #include <config.h>
37 #include <errno.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <sys/utsname.h>
44 #include <sys/ioctl.h>
45 #include <sys/socket.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <libsoup/soup-address.h>
49 #include <glib/gstdio.h>
50
51 #include "gupnp-context.h"
52 #include "gupnp-context-private.h"
53 #include "gupnp-marshal.h"
54 #include "gena-protocol.h"
55 #include "http-headers.h"
56
57 G_DEFINE_TYPE (GUPnPContext,
58                gupnp_context,
59                GSSDP_TYPE_CLIENT);
60
61 struct _GUPnPContextPrivate {
62         guint        port;
63
64         guint        subscription_timeout;
65
66         SoupSession *session;
67
68         SoupServer  *server; /* Started on demand */
69         char        *server_url;
70
71         GList       *host_path_datas;
72 };
73
74 enum {
75         PROP_0,
76         PROP_PORT,
77         PROP_SERVER,
78         PROP_SESSION,
79         PROP_SUBSCRIPTION_TIMEOUT
80 };
81
82 typedef struct {
83         char *local_path;
84
85         GRegex *regex;
86 } UserAgent;
87
88 typedef struct {
89         char *local_path;
90         char *server_path;
91
92         GList *user_agents;
93 } HostPathData;
94
95 /*
96  * Generates the default server ID.
97  **/
98 static char *
99 make_server_id (void)
100 {
101         struct utsname sysinfo;
102
103         uname (&sysinfo);
104
105         return g_strdup_printf ("%s/%s UPnP/1.0 GUPnP/%s",
106                                 sysinfo.sysname,
107                                 sysinfo.release,
108                                 VERSION);
109 }
110
111 static void
112 gupnp_context_init (GUPnPContext *context)
113 {
114         char *server_id;
115
116         context->priv =
117                 G_TYPE_INSTANCE_GET_PRIVATE (context,
118                                              GUPNP_TYPE_CONTEXT,
119                                              GUPnPContextPrivate);
120
121         server_id = make_server_id ();
122         gssdp_client_set_server_id (GSSDP_CLIENT (context), server_id);
123         g_free (server_id);
124 }
125
126 static GObject *
127 gupnp_context_constructor (GType                  type,
128                            guint                  n_props,
129                            GObjectConstructParam *props)
130 {
131         GObject *object;
132         GUPnPContext *context;
133         char *user_agent;
134
135         object = G_OBJECT_CLASS (gupnp_context_parent_class)->constructor
136                 (type, n_props, props);
137         context = GUPNP_CONTEXT (object);
138
139         context->priv->session = soup_session_async_new_with_options
140                 (SOUP_SESSION_IDLE_TIMEOUT,
141                  60,
142                  SOUP_SESSION_ASYNC_CONTEXT,
143                  gssdp_client_get_main_context (GSSDP_CLIENT (context)),
144                  NULL);
145
146         user_agent = g_strdup_printf ("%s GUPnP/" VERSION " DLNADOC/1.50",
147                                       g_get_application_name ()? : "");
148         g_object_set (context->priv->session,
149                       SOUP_SESSION_USER_AGENT,
150                       user_agent,
151                       NULL);
152         g_free (user_agent);
153
154         if (g_getenv ("GUPNP_DEBUG")) {
155                 SoupLogger *logger;
156                 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
157                 soup_session_add_feature (context->priv->session,
158                                           SOUP_SESSION_FEATURE (logger));
159         }
160
161         return object;
162 }
163
164 static void
165 gupnp_context_set_property (GObject      *object,
166                             guint         property_id,
167                             const GValue *value,
168                             GParamSpec   *pspec)
169 {
170         GUPnPContext *context;
171
172         context = GUPNP_CONTEXT (object);
173
174         switch (property_id) {
175         case PROP_PORT:
176                 context->priv->port = g_value_get_uint (value);
177                 break;
178         case PROP_SUBSCRIPTION_TIMEOUT:
179                 context->priv->subscription_timeout = g_value_get_uint (value);
180                 break;
181         default:
182                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
183                 break;
184         }
185 }
186
187 static void
188 gupnp_context_get_property (GObject    *object,
189                             guint       property_id,
190                             GValue     *value,
191                             GParamSpec *pspec)
192 {
193         GUPnPContext *context;
194
195         context = GUPNP_CONTEXT (object);
196
197         switch (property_id) {
198         case PROP_PORT:
199                 g_value_set_uint (value,
200                                   gupnp_context_get_port (context));
201                 break;
202         case PROP_SERVER:
203                 g_value_set_object (value,
204                                     gupnp_context_get_server (context));
205                 break;
206         case PROP_SESSION:
207                 g_value_set_object (value,
208                                     gupnp_context_get_session (context));
209                 break;
210         case PROP_SUBSCRIPTION_TIMEOUT:
211                 g_value_set_uint (value,
212                                   gupnp_context_get_subscription_timeout
213                                                                    (context));
214                 break;
215         default:
216                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
217                 break;
218         }
219 }
220
221 static void
222 gupnp_context_dispose (GObject *object)
223 {
224         GUPnPContext *context;
225         GObjectClass *object_class;
226
227         context = GUPNP_CONTEXT (object);
228
229         if (context->priv->session) {
230                 g_object_unref (context->priv->session);
231                 context->priv->session = NULL;
232         }
233
234         if (context->priv->server) {
235                 g_object_unref (context->priv->server);
236                 context->priv->server = NULL;
237         }
238
239         while (context->priv->host_path_datas) {
240                 HostPathData *data;
241
242                 data = (HostPathData *) context->priv->host_path_datas->data;
243
244                 gupnp_context_unhost_path (context, data->server_path);
245         }
246
247         /* Call super */
248         object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
249         object_class->dispose (object);
250 }
251
252 static void
253 gupnp_context_finalize (GObject *object)
254 {
255         GUPnPContext *context;
256         GObjectClass *object_class;
257
258         context = GUPNP_CONTEXT (object);
259
260         g_free (context->priv->server_url);
261
262         /* Call super */
263         object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
264         object_class->finalize (object);
265 }
266
267 static void
268 gupnp_context_class_init (GUPnPContextClass *klass)
269 {
270         GObjectClass *object_class;
271
272         object_class = G_OBJECT_CLASS (klass);
273
274         object_class->constructor  = gupnp_context_constructor;
275         object_class->set_property = gupnp_context_set_property;
276         object_class->get_property = gupnp_context_get_property;
277         object_class->dispose      = gupnp_context_dispose;
278         object_class->finalize     = gupnp_context_finalize;
279
280         g_type_class_add_private (klass, sizeof (GUPnPContextPrivate));
281
282         /**
283          * GUPnPContext:port:
284          *
285          * The port to run on. Set to 0 if you don't care what port to run on.
286          **/
287         g_object_class_install_property
288                 (object_class,
289                  PROP_PORT,
290                  g_param_spec_uint ("port",
291                                     "Port",
292                                     "Port to run on",
293                                     0, G_MAXUINT, SOUP_ADDRESS_ANY_PORT,
294                                     G_PARAM_READWRITE |
295                                     G_PARAM_CONSTRUCT_ONLY |
296                                     G_PARAM_STATIC_NAME |
297                                     G_PARAM_STATIC_NICK |
298                                     G_PARAM_STATIC_BLURB));
299
300         /**
301          * GUPnPContext:server:
302          *
303          * The #SoupServer HTTP server used by GUPnP.
304          **/
305         g_object_class_install_property
306                 (object_class,
307                  PROP_SERVER,
308                  g_param_spec_object ("server",
309                                       "SoupServer",
310                                       "SoupServer HTTP server",
311                                       SOUP_TYPE_SERVER,
312                                       G_PARAM_READABLE |
313                                       G_PARAM_STATIC_NAME |
314                                       G_PARAM_STATIC_NICK |
315                                       G_PARAM_STATIC_BLURB));
316
317         /**
318          * GUPnPContext:session:
319          *
320          * The #SoupSession object used by GUPnP.
321          **/
322         g_object_class_install_property
323                 (object_class,
324                  PROP_SESSION,
325                  g_param_spec_object ("session",
326                                       "SoupSession",
327                                       "SoupSession object",
328                                       SOUP_TYPE_SESSION,
329                                       G_PARAM_READABLE |
330                                       G_PARAM_STATIC_NAME |
331                                       G_PARAM_STATIC_NICK |
332                                       G_PARAM_STATIC_BLURB));
333
334         /**
335          * GUPnPContext:subscription-timeout:
336          *
337          * The preferred subscription timeout: the number of seconds after
338          * which subscriptions are renewed. Set to '0' if subscriptions 
339          * are never to time out.
340          **/
341         g_object_class_install_property
342                 (object_class,
343                  PROP_SUBSCRIPTION_TIMEOUT,
344                  g_param_spec_uint ("subscription-timeout",
345                                     "Subscription timeout",
346                                     "Subscription timeout",
347                                     0,
348                                     GENA_MAX_TIMEOUT,
349                                     GENA_DEFAULT_TIMEOUT,
350                                     G_PARAM_READWRITE |
351                                     G_PARAM_CONSTRUCT_ONLY |
352                                     G_PARAM_STATIC_NAME |
353                                     G_PARAM_STATIC_NICK |
354                                     G_PARAM_STATIC_BLURB));
355 }
356
357 /**
358  * gupnp_context_get_session:
359  * @context: A #GUPnPContext
360  *
361  * Get the #SoupSession object that GUPnP is using.
362  *
363  * Return value: (transfer none): The #SoupSession used by GUPnP. Do not unref
364  * this when finished.
365  **/
366 SoupSession *
367 gupnp_context_get_session (GUPnPContext *context)
368 {
369         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
370
371         return context->priv->session;
372 }
373
374 /*
375  * Default server handler: Return 404 not found.
376  **/
377 static void
378 default_server_handler (SoupServer        *server,
379                         SoupMessage       *msg, 
380                         const char        *path,
381                         GHashTable        *query,
382                         SoupClientContext *client,
383                         gpointer           user_data)
384 {
385         soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
386 }
387
388 /**
389  * gupnp_context_get_server:
390  * @context: A #GUPnPContext
391  *
392  * Get the #SoupServer HTTP server that GUPnP is using.
393  *
394  * Return value: The #SoupServer used by GUPnP. Do not unref this when finished.
395  **/
396 SoupServer *
397 gupnp_context_get_server (GUPnPContext *context)
398 {
399         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
400
401         if (context->priv->server == NULL) {
402                 const char *ip;
403
404                 ip =  gssdp_client_get_host_ip (GSSDP_CLIENT (context));
405                 SoupAddress *addr = soup_address_new (ip, context->priv->port);
406                 soup_address_resolve_sync (addr, NULL);
407
408                 context->priv->server = soup_server_new
409                         (SOUP_SERVER_PORT,
410                          context->priv->port,
411                          SOUP_SERVER_ASYNC_CONTEXT,
412                          gssdp_client_get_main_context (GSSDP_CLIENT (context)),
413                          SOUP_SERVER_INTERFACE,
414                          addr,
415                          NULL);
416                 g_object_unref (addr);
417
418                 soup_server_add_handler (context->priv->server, NULL,
419                                          default_server_handler, context,
420                                          NULL);
421
422                 soup_server_run_async (context->priv->server);
423         }
424
425         return context->priv->server;
426 }
427
428 /*
429  * Makes an URL that refers to our server.
430  **/
431 static char *
432 make_server_url (GUPnPContext *context)
433 {
434         SoupServer *server;
435         guint port;
436
437         /* What port are we running on? */
438         server = gupnp_context_get_server (context);
439         port = soup_server_get_port (server);
440
441         /* Put it all together */
442         return g_strdup_printf
443                         ("http://%s:%u",
444                          gssdp_client_get_host_ip (GSSDP_CLIENT (context)),
445                          port);
446 }
447
448 const char *
449 _gupnp_context_get_server_url (GUPnPContext *context)
450 {
451         if (context->priv->server_url == NULL)
452                 context->priv->server_url = make_server_url (context);
453
454         return (const char *) context->priv->server_url;
455 }
456
457 /**
458  * gupnp_context_new:
459  * @main_context: A #GMainContext, or %NULL to use the default one
460  * @interface: The network interface to use, or %NULL to auto-detect.
461  * @port: Port to run on, or 0 if you don't care what port is used.
462  * @error: A location to store a #GError, or %NULL
463  *
464  * Create a new #GUPnPContext with the specified @main_context, @interface and
465  * @port.
466  *
467  * Return value: A new #GUPnPContext object, or %NULL on an error
468  **/
469 GUPnPContext *
470 gupnp_context_new (GMainContext *main_context,
471                    const char   *interface,
472                    guint         port,
473                    GError      **error)
474 {
475         return g_object_new (GUPNP_TYPE_CONTEXT,
476                              "main-context", main_context,
477                              "interface", interface,
478                              "port", port,
479                              "error", error,
480                              NULL);
481 }
482
483 /**
484  * gupnp_context_get_host_ip:
485  * @context: A #GUPnPContext
486  *
487  * Get the IP address we advertise ourselves as using.
488  *
489  * Return value: The IP address. This string should not be freed.
490  *
491  * Deprecated:0.12.7: The "host-ip" property has moved to the base class
492  * #GSSDPClient so newer applications should use
493  * #gssdp_client_get_host_ip instead.
494  **/
495 const char *
496 gupnp_context_get_host_ip (GUPnPContext *context)
497 {
498         return gssdp_client_get_host_ip (GSSDP_CLIENT (context));
499 }
500
501 /**
502  * gupnp_context_get_port:
503  * @context: A #GUPnPContext
504  *
505  * Get the port that the SOAP server is running on.
506  
507  * Return value: The port the SOAP server is running on.
508  **/
509 guint
510 gupnp_context_get_port (GUPnPContext *context)
511 {
512         SoupServer *server;
513
514         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
515
516         server = gupnp_context_get_server (context);
517         return soup_server_get_port (server);
518 }
519
520 /**
521  * gupnp_context_set_subscription_timeout:
522  * @context: A #GUPnPContext
523  * @timeout: Event subscription timeout in seconds
524  *
525  * Sets the event subscription timeout to @timeout. Use 0 if you don't
526  * want subscriptions to time out. Note that any client side subscriptions
527  * will automatically be renewed.
528  **/
529 void
530 gupnp_context_set_subscription_timeout (GUPnPContext *context,
531                                         guint         timeout)
532 {
533         g_return_if_fail (GUPNP_IS_CONTEXT (context));
534
535         context->priv->subscription_timeout = timeout;
536
537         g_object_notify (G_OBJECT (context), "subscription-timeout");
538 }
539
540 /**
541  * gupnp_context_get_subscription_timeout:
542  * @context: A #GUPnPContext
543  *
544  * Get the event subscription timeout (in seconds), or 0 meaning there is no
545  * timeout.
546  * 
547  * Return value: The event subscription timeout in seconds.
548  **/
549 guint
550 gupnp_context_get_subscription_timeout (GUPnPContext *context)
551 {
552         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
553
554         return context->priv->subscription_timeout;
555 }
556
557 /* Construct a local path from @requested path, removing the last slash
558  * if any to make sure we append the locale suffix in a canonical way. */
559 static char *
560 construct_local_path (const char   *requested_path,
561                       const char   *user_agent,
562                       HostPathData *host_path_data)
563 {
564         GString *str;
565         char *local_path;
566         int len;
567
568         local_path = NULL;
569
570         if (user_agent != NULL) {
571                 GList *node;
572
573                 for (node = host_path_data->user_agents;
574                      node;
575                      node = node->next) {
576                         UserAgent *agent;
577
578                         agent = node->data;
579
580                         if (g_regex_match (agent->regex,
581                                            user_agent,
582                                            0,
583                                            NULL)) {
584                                 local_path = agent->local_path;
585                         }
586                 }
587         }
588
589         if (local_path == NULL)
590                 local_path = host_path_data->local_path;
591
592         if (!requested_path || *requested_path == 0)
593                 return g_strdup (local_path);
594
595         if (*requested_path != '/')
596                 return NULL; /* Absolute paths only */
597
598         str = g_string_new (local_path);
599
600         /* Skip the length of the path relative to which @requested_path
601          * is specified. */
602         requested_path += strlen (host_path_data->server_path);
603
604         /* Strip the last slashes to make sure we append the locale suffix
605          * in a canonical way. */
606         len = strlen (requested_path);
607         while (requested_path[len - 1] == '/')
608                 len--;
609
610         g_string_append_len (str,
611                              requested_path,
612                              len);
613
614         return g_string_free (str, FALSE);
615 }
616
617 /* Append locale suffix to @local_path. */
618 static char *
619 append_locale (const char *local_path, GList *locales)
620 {
621         if (!locales)
622                 return g_strdup (local_path);
623
624         return g_strdup_printf ("%s.%s",
625                                 local_path,
626                                 (char *) locales->data);
627 }
628
629 /* Redirect @msg to the same URI, but with a slash appended. */
630 static void
631 redirect_to_folder (SoupMessage *msg)
632 {
633         char *uri, *redir_uri;
634
635         uri = soup_uri_to_string (soup_message_get_uri (msg),
636                                   FALSE);
637         redir_uri = g_strdup_printf ("%s/", uri);
638         soup_message_headers_append (msg->response_headers,
639                                      "Location", redir_uri);
640         soup_message_set_status (msg,
641                                  SOUP_STATUS_MOVED_PERMANENTLY);
642         g_free (redir_uri);
643         g_free (uri);
644 }
645
646 /* Serve @path. Note that we do not need to check for path including bogus
647  * '..' as libsoup does this for us. */
648 static void
649 host_path_handler (SoupServer        *server,
650                    SoupMessage       *msg, 
651                    const char        *path,
652                    GHashTable        *query,
653                    SoupClientContext *client,
654                    gpointer           user_data)
655 {
656         char *local_path, *path_to_open;
657         struct stat st;
658         int status;
659         GList *locales, *orig_locales;
660         GMappedFile *mapped_file;
661         GError *error;
662         HostPathData *host_path_data;
663         const char *user_agent;
664
665         orig_locales = NULL;
666         locales      = NULL;
667         local_path   = NULL;
668         path_to_open = NULL;
669         host_path_data = (HostPathData *) user_data;
670
671         if (msg->method != SOUP_METHOD_GET &&
672             msg->method != SOUP_METHOD_HEAD) {
673                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
674
675                 goto DONE;
676         }
677
678         user_agent = soup_message_headers_get_one (msg->request_headers,
679                                                    "User-Agent");
680
681         /* Construct base local path */
682         local_path = construct_local_path (path, user_agent, host_path_data);
683         if (!local_path) {
684                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
685
686                 goto DONE;
687         }
688
689         /* Get preferred locales */
690         orig_locales = locales = http_request_get_accept_locales (msg);
691
692  AGAIN:
693         /* Add locale suffix if available */
694         path_to_open = append_locale (local_path, locales);
695
696         /* See what we've got */
697         if (g_stat (path_to_open, &st) == -1) {
698                 if (errno == EPERM)
699                         soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
700                 else if (errno == ENOENT) {
701                         if (locales) {
702                                 g_free (path_to_open);
703
704                                 locales = locales->next;
705
706                                 goto AGAIN;
707                         } else
708                                 soup_message_set_status (msg,
709                                                          SOUP_STATUS_NOT_FOUND);
710                 } else
711                         soup_message_set_status
712                                 (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
713
714                 goto DONE;
715         }
716
717         /* Handle directories */
718         if (S_ISDIR (st.st_mode)) {
719                 if (!g_str_has_suffix (path, "/")) {
720                         redirect_to_folder (msg);
721
722                         goto DONE;
723                 }
724
725                 /* This incorporates the locale portion in the folder name
726                  * intentionally. */
727                 g_free (local_path);
728                 local_path = g_build_filename (path_to_open,
729                                                "index.html",
730                                                NULL);
731
732                 g_free (path_to_open);
733
734                 goto AGAIN;
735         }
736
737         /* Map file */
738         error = NULL;
739         mapped_file = g_mapped_file_new (path_to_open, FALSE, &error);
740
741         if (mapped_file == NULL) {
742                 g_warning ("Unable to map file %s: %s",
743                            path_to_open, error->message);
744
745                 g_error_free (error);
746
747                 soup_message_set_status (msg,
748                                          SOUP_STATUS_INTERNAL_SERVER_ERROR);
749
750                 goto DONE;
751         }
752
753         /* Handle method (GET or HEAD) */
754         status = SOUP_STATUS_OK;
755
756         if (msg->method == SOUP_METHOD_GET) {
757                 gsize offset, length;
758                 gboolean have_range;
759                 SoupBuffer *buffer;
760
761                 /* Find out range */
762                 have_range = FALSE;
763
764                 offset = 0;
765                 length = st.st_size;
766
767                 if (!http_request_get_range (msg,
768                                              &have_range,
769                                              &offset,
770                                              &length)) {
771                         soup_message_set_status
772                                 (msg,
773                                  SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
774
775                         goto DONE;
776                 }
777
778                 if (have_range && (length > st.st_size - offset ||
779                                    st.st_size < 0 ||
780                                    (off_t) offset >= st.st_size)) {
781                         soup_message_set_status
782                                 (msg,
783                                  SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
784
785                         goto DONE;
786                 }
787
788                 /* Add requested content */
789                 buffer = soup_buffer_new_with_owner
790                              (g_mapped_file_get_contents (mapped_file) + offset,
791                               length,
792                               mapped_file,
793                               (GDestroyNotify) g_mapped_file_unref);
794
795                 soup_message_body_append_buffer (msg->response_body, buffer);
796
797                 soup_buffer_free (buffer);
798
799                 /* Set status */
800                 if (have_range) {
801                         http_response_set_content_range (msg,
802                                                          offset,
803                                                          offset + length,
804                                                          st.st_size);
805
806                         status = SOUP_STATUS_PARTIAL_CONTENT;
807                 }
808
809         } else if (msg->method == SOUP_METHOD_HEAD) {
810                 char *length;
811
812                 length = g_strdup_printf ("%lu", (gulong) st.st_size);
813                 soup_message_headers_append (msg->response_headers,
814                                              "Content-Length",
815                                              length);
816                 g_free (length);
817
818         } else {
819                 soup_message_set_status (msg,
820                                          SOUP_STATUS_METHOD_NOT_ALLOWED);
821
822                 goto DONE;
823         }
824
825         /* Set Content-Type */
826         http_response_set_content_type (msg,
827                                         path_to_open, 
828                                         (guchar *) g_mapped_file_get_contents
829                                                                 (mapped_file),
830                                         st.st_size);
831
832         /* Set Content-Language */
833         if (locales)
834                http_response_set_content_locale (msg, locales->data);
835
836         /* Set Accept-Ranges */
837         soup_message_headers_append (msg->response_headers,
838                                      "Accept-Ranges",
839                                      "bytes");
840
841         /* Set status */
842         soup_message_set_status (msg, status);
843
844  DONE:
845         /* Cleanup */
846         g_free (path_to_open);
847         g_free (local_path);
848
849         while (orig_locales) {
850                 g_free (orig_locales->data);
851                 orig_locales = g_list_delete_link (orig_locales, orig_locales);
852         }
853 }
854
855 static UserAgent *
856 user_agent_new (const char *local_path,
857                 GRegex     *regex)
858 {
859         UserAgent *agent;
860
861         agent = g_slice_new0 (UserAgent);
862
863         agent->local_path = g_strdup (local_path);
864         agent->regex = g_regex_ref (regex);
865
866         return agent;
867 }
868
869 static void
870 user_agent_free (UserAgent *agent)
871 {
872         g_free (agent->local_path);
873         g_regex_unref (agent->regex);
874
875         g_slice_free (UserAgent, agent);
876 }
877
878 static HostPathData *
879 host_path_data_new (const char *local_path,
880                     const char *server_path)
881 {
882         HostPathData *path_data;
883
884         path_data = g_slice_new0 (HostPathData);
885
886         path_data->local_path  = g_strdup (local_path);
887         path_data->server_path = g_strdup (server_path);
888
889         return path_data;
890 }
891
892 static void
893 host_path_data_free (HostPathData *path_data)
894 {
895         g_free (path_data->local_path);
896         g_free (path_data->server_path);
897         while (path_data->user_agents) {
898                 UserAgent *agent;
899
900                 agent = path_data->user_agents->data;
901
902                 user_agent_free (agent);
903
904                 path_data->user_agents = g_list_delete_link (
905                                 path_data->user_agents,
906                                 path_data->user_agents);
907         }
908
909         g_slice_free (HostPathData, path_data);
910 }
911
912 /**
913  * gupnp_context_host_path:
914  * @context: A #GUPnPContext
915  * @local_path: Path to the local file or folder to be hosted
916  * @server_path: Web server path where @local_path should be hosted
917  *
918  * Start hosting @local_path at @server_path. Files with the path
919  * @local_path.LOCALE (if they exist) will be served up when LOCALE is
920  * specified in the request's Accept-Language header.
921  **/
922 void
923 gupnp_context_host_path (GUPnPContext *context,
924                          const char   *local_path,
925                          const char   *server_path)
926 {
927         SoupServer *server;
928         HostPathData *path_data;
929
930         g_return_if_fail (GUPNP_IS_CONTEXT (context));
931         g_return_if_fail (local_path != NULL);
932         g_return_if_fail (server_path != NULL);
933
934         server = gupnp_context_get_server (context);
935
936         path_data = host_path_data_new (local_path,
937                                         server_path);
938
939         soup_server_add_handler (server,
940                                  server_path,
941                                  host_path_handler,
942                                  path_data,
943                                  NULL);
944
945         context->priv->host_path_datas =
946                 g_list_append (context->priv->host_path_datas,
947                                path_data);
948 }
949
950 static unsigned int
951 path_compare_func (HostPathData *path_data,
952                    const char   *server_path)
953 {
954         return strcmp (path_data->server_path, server_path);
955 }
956
957 /**
958  * gupnp_context_host_path_for_agent:
959  * @context: A #GUPnPContext
960  * @local_path: Path to the local file or folder to be hosted
961  * @server_path: Web server path already being hosted
962  * @user_agent: The user-agent as a #GRegex.
963  *
964  * Use this method to serve different local path to specific user-agent(s). The
965  * path @server_path must already be hosted by @context.
966  *
967  * Return value: %TRUE on success, %FALSE otherwise.
968  **/
969 gboolean
970 gupnp_context_host_path_for_agent (GUPnPContext *context,
971                                    const char   *local_path,
972                                    const char   *server_path,
973                                    GRegex       *user_agent)
974 {
975         GList *node;
976
977         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), FALSE);
978         g_return_val_if_fail (local_path != NULL, FALSE);
979         g_return_val_if_fail (server_path != NULL, FALSE);
980         g_return_val_if_fail (user_agent != NULL, FALSE);
981
982         node = g_list_find_custom (context->priv->host_path_datas,
983                                    server_path,
984                                    (GCompareFunc) path_compare_func);
985         if (node != NULL) {
986                 HostPathData *path_data;
987                 UserAgent *agent;
988
989                 path_data = (HostPathData *) node->data;
990                 agent = user_agent_new (local_path, user_agent);
991
992                 path_data->user_agents = g_list_append (path_data->user_agents,
993                                                         agent);
994
995                 return TRUE;
996         } else
997                 return FALSE;
998 }
999
1000 /**
1001  * gupnp_context_unhost_path:
1002  * @context: A #GUPnPContext
1003  * @server_path: Web server path where the file or folder is hosted
1004  *
1005  * Stop hosting the file or folder at @server_path.
1006  **/
1007 void
1008 gupnp_context_unhost_path (GUPnPContext *context,
1009                            const char   *server_path)
1010 {
1011         SoupServer *server;
1012         HostPathData *path_data;
1013         GList *node;
1014
1015         g_return_if_fail (GUPNP_IS_CONTEXT (context));
1016         g_return_if_fail (server_path != NULL);
1017
1018         server = gupnp_context_get_server (context);
1019
1020         node = g_list_find_custom (context->priv->host_path_datas,
1021                                    server_path,
1022                                    (GCompareFunc) path_compare_func);
1023         g_return_if_fail (node != NULL);
1024
1025         path_data = (HostPathData *) node->data;
1026         context->priv->host_path_datas = g_list_delete_link (
1027                         context->priv->host_path_datas,
1028                         node);
1029
1030         soup_server_remove_handler (server, server_path);
1031         host_path_data_free (path_data);
1032 }