2 * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3 * Copyright (C) 2009 Nokia Corporation.
5 * Author: Jorn Baayen <jorn@openedhand.com>
6 * Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
7 * <zeeshan.ali@nokia.com>
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.
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.
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., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
26 * SECTION:gupnp-context
27 * @short_description: Context object wrapping shared networking bits.
29 * #GUPnPContext wraps the networking bits that are used by the various
30 * GUPnP classes. It automatically starts a web server on demand.
32 * For debugging, it is possible to see the messages being sent and received by
33 * exporting <envar>GUPNP_DEBUG</envar>.
47 #include <sys/utsname.h>
49 #include <sys/types.h>
51 #include <libsoup/soup-address.h>
52 #include <glib/gstdio.h>
54 #include "gupnp-acl.h"
55 #include "gupnp-acl-private.h"
56 #include "gupnp-context.h"
57 #include "gupnp-context-private.h"
58 #include "gupnp-error.h"
59 #include "gupnp-marshal.h"
60 #include "gena-protocol.h"
61 #include "http-headers.h"
63 #define GUPNP_CONTEXT_DEFAULT_LANGUAGE "en"
66 gupnp_acl_server_handler (SoupServer *server,
70 SoupClientContext *client,
74 gupnp_context_initable_iface_init (gpointer g_iface,
78 G_DEFINE_TYPE_EXTENDED (GUPnPContext,
84 gupnp_context_initable_iface_init));
86 struct _GUPnPContextPrivate {
89 guint subscription_timeout;
93 SoupServer *server; /* Started on demand */
95 char *default_language;
97 GList *host_path_datas;
107 PROP_SUBSCRIPTION_TIMEOUT,
108 PROP_DEFAULT_LANGUAGE,
121 char *default_language;
126 static GInitableIface* initable_parent_iface = NULL;
129 * Generates the default server ID.
132 make_server_id (void)
135 OSVERSIONINFO versioninfo;
136 versioninfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
137 if (GetVersionEx (&versioninfo)) {
138 return g_strdup_printf ("Microsoft Windows/%ld.%ld"
139 " UPnP/1.0 GUPnP/%s",
140 versioninfo.dwMajorVersion,
141 versioninfo.dwMinorVersion,
144 return g_strdup_printf ("Microsoft Windows UPnP/1.0 GUPnP/%s",
148 struct utsname sysinfo;
152 return g_strdup_printf ("%s/%s UPnP/1.0 GUPnP/%s",
160 gupnp_context_init (GUPnPContext *context)
165 G_TYPE_INSTANCE_GET_PRIVATE (context,
167 GUPnPContextPrivate);
169 server_id = make_server_id ();
170 gssdp_client_set_server_id (GSSDP_CLIENT (context), server_id);
175 gupnp_context_initable_init (GInitable *initable,
176 GCancellable *cancellable,
180 GError *inner_error = NULL;
181 GUPnPContext *context;
183 if (!initable_parent_iface->init(initable,
186 g_propagate_error (error, inner_error);
191 context = GUPNP_CONTEXT (initable);
193 context->priv->session = soup_session_async_new_with_options
194 (SOUP_SESSION_IDLE_TIMEOUT,
196 SOUP_SESSION_ASYNC_CONTEXT,
197 g_main_context_get_thread_default (),
200 user_agent = g_strdup_printf ("%s GUPnP/" VERSION " DLNADOC/1.50",
201 g_get_prgname ()? : "");
202 g_object_set (context->priv->session,
203 SOUP_SESSION_USER_AGENT,
208 if (g_getenv ("GUPNP_DEBUG")) {
210 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
211 soup_session_add_feature (context->priv->session,
212 SOUP_SESSION_FEATURE (logger));
215 soup_session_add_feature_by_type (context->priv->session,
216 SOUP_TYPE_CONTENT_DECODER);
218 /* Create the server already if the port is not null*/
219 if (context->priv->port != 0) {
220 gupnp_context_get_server (context);
222 if (context->priv->server == NULL) {
223 g_object_unref (context->priv->session);
224 context->priv->session = NULL;
228 GUPNP_SERVER_ERROR_OTHER,
229 "Could not create HTTP server on port %d",
230 context->priv->port);
240 gupnp_context_initable_iface_init (gpointer g_iface,
241 G_GNUC_UNUSED gpointer iface_data)
243 GInitableIface *iface = (GInitableIface *)g_iface;
244 initable_parent_iface = g_type_interface_peek_parent (iface);
245 iface->init = gupnp_context_initable_init;
249 gupnp_context_set_property (GObject *object,
254 GUPnPContext *context;
256 context = GUPNP_CONTEXT (object);
258 switch (property_id) {
260 context->priv->port = g_value_get_uint (value);
262 case PROP_SUBSCRIPTION_TIMEOUT:
263 context->priv->subscription_timeout = g_value_get_uint (value);
265 case PROP_DEFAULT_LANGUAGE:
266 gupnp_context_set_default_language (context,
267 g_value_get_string (value));
270 gupnp_context_set_acl (context, g_value_get_object (value));
274 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
280 gupnp_context_get_property (GObject *object,
285 GUPnPContext *context;
287 context = GUPNP_CONTEXT (object);
289 switch (property_id) {
291 g_value_set_uint (value,
292 gupnp_context_get_port (context));
295 g_value_set_object (value,
296 gupnp_context_get_server (context));
299 g_value_set_object (value,
300 gupnp_context_get_session (context));
302 case PROP_SUBSCRIPTION_TIMEOUT:
303 g_value_set_uint (value,
304 gupnp_context_get_subscription_timeout
307 case PROP_DEFAULT_LANGUAGE:
308 g_value_set_string (value,
309 gupnp_context_get_default_language
313 g_value_set_object (value,
314 gupnp_context_get_acl (context));
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
324 gupnp_context_dispose (GObject *object)
326 GUPnPContext *context;
327 GObjectClass *object_class;
329 context = GUPNP_CONTEXT (object);
331 if (context->priv->session) {
332 g_object_unref (context->priv->session);
333 context->priv->session = NULL;
336 while (context->priv->host_path_datas) {
339 data = (HostPathData *) context->priv->host_path_datas->data;
341 gupnp_context_unhost_path (context, data->server_path);
344 if (context->priv->server) {
345 g_object_unref (context->priv->server);
346 context->priv->server = NULL;
350 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
351 object_class->dispose (object);
355 gupnp_context_finalize (GObject *object)
357 GUPnPContext *context;
358 GObjectClass *object_class;
360 context = GUPNP_CONTEXT (object);
362 g_free (context->priv->default_language);
363 g_free (context->priv->server_url);
366 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
367 object_class->finalize (object);
371 gupnp_context_constructor (GType type,
372 guint n_construct_params,
373 GObjectConstructParam *construct_params)
375 GObjectClass *object_class;
376 guint port = 0, msearch_port = 0;
377 int i, msearch_idx = -1;
379 for (i = 0; i < n_construct_params; i++) {
380 const char *par_name;
382 par_name = construct_params[i].pspec->name;
384 if (strcmp (par_name, "port") == 0)
385 port = g_value_get_uint (construct_params[i].value);
386 else if (strcmp (par_name, "msearch-port") == 0) {
388 msearch_port = g_value_get_uint
389 (construct_params[i].value);
393 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
395 /* Override msearch-port property if port is set, the property exists
396 * and wasn't provided otherwise */
397 if (port != 0 && msearch_idx != -1 && msearch_port == 0) {
398 g_value_set_uint (construct_params[msearch_idx].value, port);
401 return object_class->constructor (type,
408 gupnp_context_class_init (GUPnPContextClass *klass)
410 GObjectClass *object_class;
412 object_class = G_OBJECT_CLASS (klass);
414 object_class->set_property = gupnp_context_set_property;
415 object_class->get_property = gupnp_context_get_property;
416 object_class->dispose = gupnp_context_dispose;
417 object_class->finalize = gupnp_context_finalize;
418 object_class->constructor = gupnp_context_constructor;
420 g_type_class_add_private (klass, sizeof (GUPnPContextPrivate));
425 * The port to run on. Set to 0 if you don't care what port to run on.
427 g_object_class_install_property
430 g_param_spec_uint ("port",
433 0, G_MAXUINT, SOUP_ADDRESS_ANY_PORT,
435 G_PARAM_CONSTRUCT_ONLY |
436 G_PARAM_STATIC_NAME |
437 G_PARAM_STATIC_NICK |
438 G_PARAM_STATIC_BLURB));
441 * GUPnPContext:server:
443 * The #SoupServer HTTP server used by GUPnP.
445 g_object_class_install_property
448 g_param_spec_object ("server",
450 "SoupServer HTTP server",
453 G_PARAM_STATIC_NAME |
454 G_PARAM_STATIC_NICK |
455 G_PARAM_STATIC_BLURB));
458 * GUPnPContext:session:
460 * The #SoupSession object used by GUPnP.
462 g_object_class_install_property
465 g_param_spec_object ("session",
467 "SoupSession object",
470 G_PARAM_STATIC_NAME |
471 G_PARAM_STATIC_NICK |
472 G_PARAM_STATIC_BLURB));
475 * GUPnPContext:subscription-timeout:
477 * The preferred subscription timeout: the number of seconds after
478 * which subscriptions are renewed. Set to '0' if subscriptions
479 * are never to time out.
481 g_object_class_install_property
483 PROP_SUBSCRIPTION_TIMEOUT,
484 g_param_spec_uint ("subscription-timeout",
485 "Subscription timeout",
486 "Subscription timeout",
489 GENA_DEFAULT_TIMEOUT,
491 G_PARAM_CONSTRUCT_ONLY |
492 G_PARAM_STATIC_NAME |
493 G_PARAM_STATIC_NICK |
494 G_PARAM_STATIC_BLURB));
496 * GUPnPContext:default-language:
498 * The content of the Content-Language header id the client
499 * sends Accept-Language and no language-specific pages to serve
500 * exist. The property defaults to 'en'.
504 g_object_class_install_property
506 PROP_DEFAULT_LANGUAGE,
507 g_param_spec_string ("default-language",
510 GUPNP_CONTEXT_DEFAULT_LANGUAGE,
513 G_PARAM_STATIC_NAME |
514 G_PARAM_STATIC_NICK |
515 G_PARAM_STATIC_BLURB));
520 * An access control list.
524 g_object_class_install_property
527 g_param_spec_object ("acl",
528 "Access control list",
529 "Access control list",
533 G_PARAM_STATIC_STRINGS));
537 * gupnp_context_get_session:
538 * @context: A #GUPnPContext
540 * Get the #SoupSession object that GUPnP is using.
542 * Return value: (transfer none): The #SoupSession used by GUPnP. Do not unref
543 * this when finished.
548 gupnp_context_get_session (GUPnPContext *context)
550 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
552 return context->priv->session;
556 * Default server handler: Return 404 not found.
559 default_server_handler (G_GNUC_UNUSED SoupServer *server,
561 G_GNUC_UNUSED const char *path,
562 G_GNUC_UNUSED GHashTable *query,
563 G_GNUC_UNUSED SoupClientContext *client,
564 G_GNUC_UNUSED gpointer user_data)
566 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
570 * gupnp_context_get_server:
571 * @context: A #GUPnPContext
573 * Get the #SoupServer HTTP server that GUPnP is using.
575 * Returns: (transfer none): The #SoupServer used by GUPnP. Do not unref this when finished.
578 gupnp_context_get_server (GUPnPContext *context)
580 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
582 if (context->priv->server == NULL) {
585 ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context));
586 SoupAddress *addr = soup_address_new (ip, context->priv->port);
587 soup_address_resolve_sync (addr, NULL);
589 context->priv->server = soup_server_new
592 SOUP_SERVER_ASYNC_CONTEXT,
593 g_main_context_get_thread_default (),
594 SOUP_SERVER_INTERFACE,
597 g_object_unref (addr);
599 if (context->priv->server) {
600 soup_server_add_handler (context->priv->server,
602 default_server_handler,
606 soup_server_run_async (context->priv->server);
610 return context->priv->server;
614 * Makes an URL that refers to our server.
617 make_server_url (GUPnPContext *context)
622 /* What port are we running on? */
623 server = gupnp_context_get_server (context);
624 port = soup_server_get_port (server);
626 /* Put it all together */
627 return g_strdup_printf
629 gssdp_client_get_host_ip (GSSDP_CLIENT (context)),
634 _gupnp_context_get_server_url (GUPnPContext *context)
636 if (context->priv->server_url == NULL)
637 context->priv->server_url = make_server_url (context);
639 return (const char *) context->priv->server_url;
644 * @main_context: (allow-none): Deprecated: 0.17.2: Always set to %NULL. If you
645 * want to use a different context, use g_main_context_push_thread_default().
646 * @iface: (allow-none): The network interface to use, or %NULL to
648 * @port: Port to run on, or 0 if you don't care what port is used.
649 * @error: A location to store a #GError, or %NULL
651 * Create a new #GUPnPContext with the specified @main_context, @iface and
654 * Return value: A new #GUPnPContext object, or %NULL on an error
657 gupnp_context_new (GMainContext *main_context,
663 g_warning ("gupnp_context_new::main_context is deprecated."
664 " Use g_main_context_push_thread_default()"
667 return g_initable_new (GUPNP_TYPE_CONTEXT,
676 * gupnp_context_get_host_ip:
677 * @context: A #GUPnPContext
679 * Get the IP address we advertise ourselves as using.
681 * Return value: The IP address. This string should not be freed.
683 * Deprecated:0.12.7: The "host-ip" property has moved to the base class
684 * #GSSDPClient so newer applications should use
685 * #gssdp_client_get_host_ip instead.
688 gupnp_context_get_host_ip (GUPnPContext *context)
690 return gssdp_client_get_host_ip (GSSDP_CLIENT (context));
694 * gupnp_context_get_port:
695 * @context: A #GUPnPContext
697 * Get the port that the SOAP server is running on.
699 * Return value: The port the SOAP server is running on.
702 gupnp_context_get_port (GUPnPContext *context)
706 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
708 server = gupnp_context_get_server (context);
709 return soup_server_get_port (server);
713 * gupnp_context_set_subscription_timeout:
714 * @context: A #GUPnPContext
715 * @timeout: Event subscription timeout in seconds
717 * Sets the event subscription timeout to @timeout. Use 0 if you don't
718 * want subscriptions to time out. Note that any client side subscriptions
719 * will automatically be renewed.
722 gupnp_context_set_subscription_timeout (GUPnPContext *context,
725 g_return_if_fail (GUPNP_IS_CONTEXT (context));
727 context->priv->subscription_timeout = timeout;
729 g_object_notify (G_OBJECT (context), "subscription-timeout");
733 * gupnp_context_get_subscription_timeout:
734 * @context: A #GUPnPContext
736 * Get the event subscription timeout (in seconds), or 0 meaning there is no
739 * Return value: The event subscription timeout in seconds.
742 gupnp_context_get_subscription_timeout (GUPnPContext *context)
744 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
746 return context->priv->subscription_timeout;
750 host_path_data_set_language (HostPathData *data, const char *language)
752 char *old_language = data->default_language;
754 if ((old_language != NULL) && (!strcmp (language, old_language)))
757 data->default_language = g_strdup (language);
759 g_free (old_language);
763 * gupnp_context_set_default_language:
764 * @context: A #GUPnPContext
765 * @language: A language tag as defined in RFC 2616 3.10
767 * Set the default language for the Content-Length header to @language.
769 * If the client sends an Accept-Language header the UPnP HTTP server
770 * is required to send a Content-Language header in return. If there are
771 * no files hosted in languages which match the requested ones the
772 * Content-Language header is set to this value. The default value is "en".
777 gupnp_context_set_default_language (GUPnPContext *context,
778 const char *language)
780 g_return_if_fail (GUPNP_IS_CONTEXT (context));
781 g_return_if_fail (language != NULL);
784 char *old_language = context->priv->default_language;
786 if ((old_language != NULL) && (!strcmp (language, old_language)))
789 context->priv->default_language = g_strdup (language);
791 g_list_foreach (context->priv->host_path_datas,
792 (GFunc) host_path_data_set_language,
793 (gpointer) language);
795 g_free (old_language);
799 * gupnp_context_get_default_language:
800 * @context: A #GUPnPContext
802 * Get the default Content-Language header for this context.
804 * Returns: (transfer none): The default content of the Content-Language
810 gupnp_context_get_default_language (GUPnPContext *context)
812 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
814 return context->priv->default_language;
817 /* Construct a local path from @requested path, removing the last slash
818 * if any to make sure we append the locale suffix in a canonical way. */
820 construct_local_path (const char *requested_path,
821 const char *user_agent,
822 HostPathData *host_path_data)
830 if (user_agent != NULL) {
833 for (node = host_path_data->user_agents;
840 if (g_regex_match (agent->regex,
844 local_path = agent->local_path;
849 if (local_path == NULL)
850 local_path = host_path_data->local_path;
852 if (!requested_path || *requested_path == 0)
853 return g_strdup (local_path);
855 if (*requested_path != '/')
856 return NULL; /* Absolute paths only */
858 str = g_string_new (local_path);
860 /* Skip the length of the path relative to which @requested_path
862 requested_path += strlen (host_path_data->server_path);
864 /* Strip the last slashes to make sure we append the locale suffix
865 * in a canonical way. */
866 len = strlen (requested_path);
867 while (requested_path[len - 1] == '/')
870 g_string_append_len (str,
874 return g_string_free (str, FALSE);
877 /* Append locale suffix to @local_path. */
879 append_locale (const char *local_path, GList *locales)
882 return g_strdup (local_path);
884 return g_strdup_printf ("%s.%s",
886 (char *) locales->data);
889 /* Redirect @msg to the same URI, but with a slash appended. */
891 redirect_to_folder (SoupMessage *msg)
893 char *uri, *redir_uri;
895 uri = soup_uri_to_string (soup_message_get_uri (msg),
897 redir_uri = g_strdup_printf ("%s/", uri);
898 soup_message_headers_append (msg->response_headers,
899 "Location", redir_uri);
900 soup_message_set_status (msg,
901 SOUP_STATUS_MOVED_PERMANENTLY);
906 /* Serve @path. Note that we do not need to check for path including bogus
907 * '..' as libsoup does this for us. */
909 host_path_handler (G_GNUC_UNUSED SoupServer *server,
912 G_GNUC_UNUSED GHashTable *query,
913 G_GNUC_UNUSED SoupClientContext *client,
916 char *local_path, *path_to_open;
919 GList *locales, *orig_locales;
920 GMappedFile *mapped_file;
922 HostPathData *host_path_data;
923 const char *user_agent;
929 host_path_data = (HostPathData *) user_data;
931 if (msg->method != SOUP_METHOD_GET &&
932 msg->method != SOUP_METHOD_HEAD) {
933 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
938 /* Always send HTTP 1.1 for device description requests
939 * Also set Connection: close header, since the request originated
940 * from a HTTP 1.0 client
942 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
943 soup_message_set_http_version (msg, SOUP_HTTP_1_1);
944 soup_message_headers_append (msg->response_headers,
949 user_agent = soup_message_headers_get_one (msg->request_headers,
952 /* Construct base local path */
953 local_path = construct_local_path (path, user_agent, host_path_data);
955 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
960 /* Get preferred locales */
961 orig_locales = locales = http_request_get_accept_locales (msg);
964 /* Add locale suffix if available */
965 path_to_open = append_locale (local_path, locales);
967 /* See what we've got */
968 if (g_stat (path_to_open, &st) == -1) {
970 soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
971 else if (errno == ENOENT) {
973 g_free (path_to_open);
975 locales = locales->next;
979 soup_message_set_status (msg,
980 SOUP_STATUS_NOT_FOUND);
982 soup_message_set_status
983 (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
988 /* Handle directories */
989 if (S_ISDIR (st.st_mode)) {
990 if (!g_str_has_suffix (path, "/")) {
991 redirect_to_folder (msg);
996 /* This incorporates the locale portion in the folder name
999 local_path = g_build_filename (path_to_open,
1003 g_free (path_to_open);
1010 mapped_file = g_mapped_file_new (path_to_open, FALSE, &error);
1012 if (mapped_file == NULL) {
1013 g_warning ("Unable to map file %s: %s",
1014 path_to_open, error->message);
1016 g_error_free (error);
1018 soup_message_set_status (msg,
1019 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1024 /* Handle method (GET or HEAD) */
1025 status = SOUP_STATUS_OK;
1027 if (msg->method == SOUP_METHOD_GET) {
1028 gboolean have_range;
1033 /* Find out range */
1036 if (soup_message_headers_get_ranges (msg->request_headers,
1042 /* We do not support mulipart/byteranges so only first first */
1043 /* range from request is handled */
1044 if (have_range && (ranges[0].end > st.st_size ||
1046 ranges[0].start >= st.st_size ||
1047 ranges[0].start > ranges[0].end)) {
1048 soup_message_set_status
1050 SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
1051 soup_message_headers_free_ranges (msg->request_headers,
1057 /* Add requested content */
1058 buffer = soup_buffer_new_with_owner
1059 (g_mapped_file_get_contents (mapped_file),
1060 g_mapped_file_get_length (mapped_file),
1062 (GDestroyNotify) g_mapped_file_unref);
1064 /* Set range and status */
1066 SoupBuffer *range_buffer;
1068 soup_message_body_truncate (msg->response_body);
1069 soup_message_headers_set_content_range (
1070 msg->response_headers,
1074 range_buffer = soup_buffer_new_subbuffer (
1077 ranges[0].end - ranges[0].start + 1);
1078 soup_message_body_append_buffer (msg->response_body,
1080 status = SOUP_STATUS_PARTIAL_CONTENT;
1082 soup_message_headers_free_ranges (msg->request_headers,
1084 soup_buffer_free (range_buffer);
1086 soup_message_body_append_buffer (msg->response_body, buffer);
1088 soup_buffer_free (buffer);
1089 } else if (msg->method == SOUP_METHOD_HEAD) {
1092 length = g_strdup_printf ("%lu", (gulong) st.st_size);
1093 soup_message_headers_append (msg->response_headers,
1099 soup_message_set_status (msg,
1100 SOUP_STATUS_METHOD_NOT_ALLOWED);
1105 /* Set Content-Type */
1106 http_response_set_content_type (msg,
1108 (guchar *) g_mapped_file_get_contents
1112 /* Set Content-Language */
1114 http_response_set_content_locale (msg, locales->data);
1115 else if (soup_message_headers_get_one (msg->request_headers,
1116 "Accept-Language")) {
1117 soup_message_headers_append (msg->response_headers,
1119 host_path_data->default_language);
1122 /* Set Accept-Ranges */
1123 soup_message_headers_append (msg->response_headers,
1128 soup_message_set_status (msg, status);
1132 g_free (path_to_open);
1133 g_free (local_path);
1135 g_list_free_full (orig_locales, g_free);
1139 user_agent_new (const char *local_path,
1144 agent = g_slice_new0 (UserAgent);
1146 agent->local_path = g_strdup (local_path);
1147 agent->regex = g_regex_ref (regex);
1153 user_agent_free (UserAgent *agent)
1155 g_free (agent->local_path);
1156 g_regex_unref (agent->regex);
1158 g_slice_free (UserAgent, agent);
1161 static HostPathData *
1162 host_path_data_new (const char *local_path,
1163 const char *server_path,
1164 const char *default_language)
1166 HostPathData *path_data;
1168 path_data = g_slice_new0 (HostPathData);
1170 path_data->local_path = g_strdup (local_path);
1171 path_data->server_path = g_strdup (server_path);
1172 path_data->default_language = g_strdup (default_language);
1178 host_path_data_free (HostPathData *path_data)
1180 g_free (path_data->local_path);
1181 g_free (path_data->server_path);
1182 g_free (path_data->default_language);
1184 g_list_free_full (path_data->user_agents,
1185 (GDestroyNotify) user_agent_free);
1187 g_slice_free (HostPathData, path_data);
1191 * gupnp_context_host_path:
1192 * @context: A #GUPnPContext
1193 * @local_path: Path to the local file or folder to be hosted
1194 * @server_path: Web server path where @local_path should be hosted
1196 * Start hosting @local_path at @server_path. Files with the path
1197 * @local_path.LOCALE (if they exist) will be served up when LOCALE is
1198 * specified in the request's Accept-Language header.
1201 gupnp_context_host_path (GUPnPContext *context,
1202 const char *local_path,
1203 const char *server_path)
1206 HostPathData *path_data;
1208 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1209 g_return_if_fail (local_path != NULL);
1210 g_return_if_fail (server_path != NULL);
1212 server = gupnp_context_get_server (context);
1214 path_data = host_path_data_new (local_path,
1216 context->priv->default_language);
1218 soup_server_add_handler (server,
1224 context->priv->host_path_datas =
1225 g_list_append (context->priv->host_path_datas,
1230 path_compare_func (HostPathData *path_data,
1231 const char *server_path)
1233 /* ignore default language */
1234 return strcmp (path_data->server_path, server_path);
1238 * gupnp_context_host_path_for_agent:
1239 * @context: A #GUPnPContext
1240 * @local_path: Path to the local file or folder to be hosted
1241 * @server_path: Web server path already being hosted
1242 * @user_agent: The user-agent as a #GRegex.
1244 * Use this method to serve different local path to specific user-agent(s). The
1245 * path @server_path must already be hosted by @context.
1247 * Return value: %TRUE on success, %FALSE otherwise.
1252 gupnp_context_host_path_for_agent (GUPnPContext *context,
1253 const char *local_path,
1254 const char *server_path,
1259 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), FALSE);
1260 g_return_val_if_fail (local_path != NULL, FALSE);
1261 g_return_val_if_fail (server_path != NULL, FALSE);
1262 g_return_val_if_fail (user_agent != NULL, FALSE);
1264 node = g_list_find_custom (context->priv->host_path_datas,
1266 (GCompareFunc) path_compare_func);
1268 HostPathData *path_data;
1271 path_data = (HostPathData *) node->data;
1272 agent = user_agent_new (local_path, user_agent);
1274 path_data->user_agents = g_list_append (path_data->user_agents,
1283 * gupnp_context_unhost_path:
1284 * @context: A #GUPnPContext
1285 * @server_path: Web server path where the file or folder is hosted
1287 * Stop hosting the file or folder at @server_path.
1290 gupnp_context_unhost_path (GUPnPContext *context,
1291 const char *server_path)
1294 HostPathData *path_data;
1297 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1298 g_return_if_fail (server_path != NULL);
1300 server = gupnp_context_get_server (context);
1302 node = g_list_find_custom (context->priv->host_path_datas,
1304 (GCompareFunc) path_compare_func);
1305 g_return_if_fail (node != NULL);
1307 path_data = (HostPathData *) node->data;
1308 context->priv->host_path_datas = g_list_delete_link (
1309 context->priv->host_path_datas,
1312 soup_server_remove_handler (server, server_path);
1313 host_path_data_free (path_data);
1317 * gupnp_context_get_acl:
1318 * @context: A #GUPnPContext
1320 * Returns:(transfer none): The access control list associated with this context or %NULL
1326 gupnp_context_get_acl (GUPnPContext *context)
1328 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
1330 return context->priv->acl;
1334 * gupnp_context_set_acl:
1335 * @context: A #GUPnPContext
1336 * @acl: (allow-none): The new access control list or %NULL to remove the
1342 gupnp_context_set_acl (GUPnPContext *context, GUPnPAcl *acl)
1344 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1346 if (context->priv->acl != NULL) {
1347 g_object_unref (context->priv->acl);
1348 context->priv->acl = NULL;
1352 context->priv->acl = g_object_ref (acl);
1354 g_object_notify (G_OBJECT (context), "acl");
1358 gupnp_acl_async_callback (GUPnPAcl *acl,
1360 AclAsyncHandler *data)
1363 GError *error = NULL;
1365 allowed = gupnp_acl_is_allowed_finish (acl, res, &error);
1366 soup_server_unpause_message (data->server, data->message);
1368 soup_message_set_status (data->message, SOUP_STATUS_FORBIDDEN);
1370 data->handler->callback (data->server,
1375 data->handler->user_data);
1377 acl_async_handler_free (data);
1381 gupnp_acl_server_handler (SoupServer *server,
1385 SoupClientContext *client,
1388 AclServerHandler *handler = (AclServerHandler *) user_data;
1390 GUPnPDevice *device = NULL;
1392 if (handler->service) {
1393 g_object_get (handler->service,
1394 "root-device", &device,
1397 if (device != NULL) {
1398 g_object_unref (device);
1402 agent = soup_message_headers_get_one (msg->request_headers,
1405 if (handler->context->priv->acl != NULL) {
1406 if (gupnp_acl_can_sync (handler->context->priv->acl)) {
1407 if (!gupnp_acl_is_allowed (handler->context->priv->acl,
1411 soup_client_context_get_host (client),
1413 soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
1418 AclAsyncHandler *data;
1420 data = acl_async_handler_new (server, msg, path, query, client, handler);
1422 soup_server_pause_message (server, msg);
1423 gupnp_acl_is_allowed_async (handler->context->priv->acl,
1427 soup_client_context_get_host (client),
1430 (GAsyncReadyCallback) gupnp_acl_async_callback,
1437 /* Delegate to orignal callback */
1438 handler->callback (server, msg, path, query, client, handler->user_data);
1442 * gupnp_context_add_server_handler:
1443 * @context: a #GUPnPContext
1444 * @use_acl: %TRUE, if the path should query the GUPnPContext::acl before
1445 * serving the resource, %FALSE otherwise.
1446 * @path: the toplevel path for the handler.
1447 * @callback: callback to invoke for requests under @path
1448 * @user_data: the user_data passed to @callback
1449 * @destroy: (allow-none): A #GDestroyNotify for @user_data or %NULL if none.
1451 * Add a #SoupServerCallback to the #GUPnPContext<!-- -->'s #SoupServer.
1456 gupnp_context_add_server_handler (GUPnPContext *context,
1459 SoupServerCallback callback,
1461 GDestroyNotify destroy)
1463 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1466 AclServerHandler *handler;
1467 handler = acl_server_handler_new (NULL, context, callback, user_data, destroy);
1468 soup_server_add_handler (context->priv->server,
1470 gupnp_acl_server_handler,
1472 (GDestroyNotify) acl_server_handler_free);
1474 soup_server_add_handler (context->priv->server,
1482 _gupnp_context_add_server_handler_with_data (GUPnPContext *context,
1484 AclServerHandler *handler)
1486 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1488 soup_server_add_handler (context->priv->server,
1490 gupnp_acl_server_handler,
1492 (GDestroyNotify) acl_server_handler_free);
1496 * gupnp_context_remove_server_handler:
1497 * @context: a #GUPnPContext
1498 * @use_acl: %TRUE, if the path should query the GUPnPContext::acl before
1499 * serving the resource, %FALSE otherwise.
1500 * @path: the toplevel path for the handler.
1502 * Add a #SoupServerCallback to the #GUPnPContext<!-- -->'s #SoupServer.
1507 gupnp_context_remove_server_handler (GUPnPContext *context, const char *path)
1509 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1511 soup_server_remove_handler (context->priv->server, path);