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 #GUPNP_DEBUG.
43 #include <sys/utsname.h>
44 #include <sys/ioctl.h>
45 #include <sys/socket.h>
46 #include <sys/types.h>
48 #include <libsoup/soup-address.h>
49 #include <glib/gstdio.h>
51 #include "gupnp-context.h"
52 #include "gupnp-context-private.h"
53 #include "gupnp-error.h"
54 #include "gupnp-marshal.h"
55 #include "gena-protocol.h"
56 #include "http-headers.h"
58 #define GUPNP_CONTEXT_DEFAULT_LANGUAGE "en"
61 gupnp_context_initable_iface_init (gpointer g_iface,
65 G_DEFINE_TYPE_EXTENDED (GUPnPContext,
71 gupnp_context_initable_iface_init));
73 struct _GUPnPContextPrivate {
76 guint subscription_timeout;
80 SoupServer *server; /* Started on demand */
82 char *default_language;
84 GList *host_path_datas;
92 PROP_SUBSCRIPTION_TIMEOUT,
105 char *default_language;
110 static GInitableIface* initable_parent_iface = NULL;
113 * Generates the default server ID.
116 make_server_id (void)
118 struct utsname sysinfo;
122 return g_strdup_printf ("%s/%s UPnP/1.0 GUPnP/%s",
129 gupnp_context_init (GUPnPContext *context)
134 G_TYPE_INSTANCE_GET_PRIVATE (context,
136 GUPnPContextPrivate);
138 server_id = make_server_id ();
139 gssdp_client_set_server_id (GSSDP_CLIENT (context), server_id);
144 gupnp_context_initable_init (GInitable *initable,
145 GCancellable *cancellable,
149 GError *inner_error = NULL;
150 GUPnPContext *context;
152 if (!initable_parent_iface->init(initable,
155 g_propagate_error (error, inner_error);
160 context = GUPNP_CONTEXT (initable);
162 context->priv->session = soup_session_async_new_with_options
163 (SOUP_SESSION_IDLE_TIMEOUT,
165 SOUP_SESSION_ASYNC_CONTEXT,
166 g_main_context_get_thread_default (),
169 user_agent = g_strdup_printf ("%s GUPnP/" VERSION " DLNADOC/1.50",
170 g_get_application_name ()? : "");
171 g_object_set (context->priv->session,
172 SOUP_SESSION_USER_AGENT,
177 if (g_getenv ("GUPNP_DEBUG")) {
179 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
180 soup_session_add_feature (context->priv->session,
181 SOUP_SESSION_FEATURE (logger));
184 soup_session_add_feature_by_type (context->priv->session,
185 SOUP_TYPE_CONTENT_DECODER);
187 /* Create the server already if the port is not null*/
188 if (context->priv->port != 0) {
189 gupnp_context_get_server (context);
191 if (context->priv->server == NULL) {
192 g_object_unref (context->priv->session);
193 context->priv->session = NULL;
197 GUPNP_SERVER_ERROR_OTHER,
198 "Could not create HTTP server on port %d",
199 context->priv->port);
209 gupnp_context_initable_iface_init (gpointer g_iface,
212 GInitableIface *iface = (GInitableIface *)g_iface;
213 initable_parent_iface = g_type_interface_peek_parent (iface);
214 iface->init = gupnp_context_initable_init;
218 gupnp_context_set_property (GObject *object,
223 GUPnPContext *context;
225 context = GUPNP_CONTEXT (object);
227 switch (property_id) {
229 context->priv->port = g_value_get_uint (value);
231 case PROP_SUBSCRIPTION_TIMEOUT:
232 context->priv->subscription_timeout = g_value_get_uint (value);
234 case PROP_DEFAULT_LANGUAGE:
235 gupnp_context_set_default_language (context,
236 g_value_get_string (value));
239 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
245 gupnp_context_get_property (GObject *object,
250 GUPnPContext *context;
252 context = GUPNP_CONTEXT (object);
254 switch (property_id) {
256 g_value_set_uint (value,
257 gupnp_context_get_port (context));
260 g_value_set_object (value,
261 gupnp_context_get_server (context));
264 g_value_set_object (value,
265 gupnp_context_get_session (context));
267 case PROP_SUBSCRIPTION_TIMEOUT:
268 g_value_set_uint (value,
269 gupnp_context_get_subscription_timeout
272 case PROP_DEFAULT_LANGUAGE:
273 g_value_set_string (value,
274 gupnp_context_get_default_language
278 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
284 gupnp_context_dispose (GObject *object)
286 GUPnPContext *context;
287 GObjectClass *object_class;
289 context = GUPNP_CONTEXT (object);
291 if (context->priv->session) {
292 g_object_unref (context->priv->session);
293 context->priv->session = NULL;
296 while (context->priv->host_path_datas) {
299 data = (HostPathData *) context->priv->host_path_datas->data;
301 gupnp_context_unhost_path (context, data->server_path);
304 if (context->priv->server) {
305 g_object_unref (context->priv->server);
306 context->priv->server = NULL;
310 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
311 object_class->dispose (object);
315 gupnp_context_finalize (GObject *object)
317 GUPnPContext *context;
318 GObjectClass *object_class;
320 context = GUPNP_CONTEXT (object);
322 if (context->priv->default_language) {
323 g_free (context->priv->default_language);
324 context->priv->default_language = NULL;
327 g_free (context->priv->server_url);
330 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
331 object_class->finalize (object);
335 gupnp_context_class_init (GUPnPContextClass *klass)
337 GObjectClass *object_class;
339 object_class = G_OBJECT_CLASS (klass);
341 object_class->set_property = gupnp_context_set_property;
342 object_class->get_property = gupnp_context_get_property;
343 object_class->dispose = gupnp_context_dispose;
344 object_class->finalize = gupnp_context_finalize;
346 g_type_class_add_private (klass, sizeof (GUPnPContextPrivate));
351 * The port to run on. Set to 0 if you don't care what port to run on.
353 g_object_class_install_property
356 g_param_spec_uint ("port",
359 0, G_MAXUINT, SOUP_ADDRESS_ANY_PORT,
361 G_PARAM_CONSTRUCT_ONLY |
362 G_PARAM_STATIC_NAME |
363 G_PARAM_STATIC_NICK |
364 G_PARAM_STATIC_BLURB));
367 * GUPnPContext:server:
369 * The #SoupServer HTTP server used by GUPnP.
371 g_object_class_install_property
374 g_param_spec_object ("server",
376 "SoupServer HTTP server",
379 G_PARAM_STATIC_NAME |
380 G_PARAM_STATIC_NICK |
381 G_PARAM_STATIC_BLURB));
384 * GUPnPContext:session:
386 * The #SoupSession object used by GUPnP.
388 g_object_class_install_property
391 g_param_spec_object ("session",
393 "SoupSession object",
396 G_PARAM_STATIC_NAME |
397 G_PARAM_STATIC_NICK |
398 G_PARAM_STATIC_BLURB));
401 * GUPnPContext:subscription-timeout:
403 * The preferred subscription timeout: the number of seconds after
404 * which subscriptions are renewed. Set to '0' if subscriptions
405 * are never to time out.
407 g_object_class_install_property
409 PROP_SUBSCRIPTION_TIMEOUT,
410 g_param_spec_uint ("subscription-timeout",
411 "Subscription timeout",
412 "Subscription timeout",
415 GENA_DEFAULT_TIMEOUT,
417 G_PARAM_CONSTRUCT_ONLY |
418 G_PARAM_STATIC_NAME |
419 G_PARAM_STATIC_NICK |
420 G_PARAM_STATIC_BLURB));
422 * GUPnPContext:default-language:
424 * The content of the Content-Language header id the client
425 * sends Accept-Language and no language-specific pages to serve
426 * exist. The property defaults to 'en'.
428 g_object_class_install_property
430 PROP_DEFAULT_LANGUAGE,
431 g_param_spec_string ("default-language",
434 GUPNP_CONTEXT_DEFAULT_LANGUAGE,
437 G_PARAM_STATIC_NAME |
438 G_PARAM_STATIC_NICK |
439 G_PARAM_STATIC_BLURB));
443 * gupnp_context_get_session:
444 * @context: A #GUPnPContext
446 * Get the #SoupSession object that GUPnP is using.
448 * Return value: (transfer none): The #SoupSession used by GUPnP. Do not unref
449 * this when finished.
452 gupnp_context_get_session (GUPnPContext *context)
454 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
456 return context->priv->session;
460 * Default server handler: Return 404 not found.
463 default_server_handler (SoupServer *server,
467 SoupClientContext *client,
470 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
474 * gupnp_context_get_server:
475 * @context: A #GUPnPContext
477 * Get the #SoupServer HTTP server that GUPnP is using.
479 * Returns: (transfer none): The #SoupServer used by GUPnP. Do not unref this when finished.
482 gupnp_context_get_server (GUPnPContext *context)
484 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
486 if (context->priv->server == NULL) {
489 ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context));
490 SoupAddress *addr = soup_address_new (ip, context->priv->port);
491 soup_address_resolve_sync (addr, NULL);
493 context->priv->server = soup_server_new
496 SOUP_SERVER_ASYNC_CONTEXT,
497 g_main_context_get_thread_default (),
498 SOUP_SERVER_INTERFACE,
501 g_object_unref (addr);
503 if (context->priv->server) {
504 soup_server_add_handler (context->priv->server,
506 default_server_handler,
510 soup_server_run_async (context->priv->server);
514 return context->priv->server;
518 * Makes an URL that refers to our server.
521 make_server_url (GUPnPContext *context)
526 /* What port are we running on? */
527 server = gupnp_context_get_server (context);
528 port = soup_server_get_port (server);
530 /* Put it all together */
531 return g_strdup_printf
533 gssdp_client_get_host_ip (GSSDP_CLIENT (context)),
538 _gupnp_context_get_server_url (GUPnPContext *context)
540 if (context->priv->server_url == NULL)
541 context->priv->server_url = make_server_url (context);
543 return (const char *) context->priv->server_url;
548 * @main_context: (allow-none): Deprecated: 0.17.2: Always set to %NULL. If you
549 * want to use a different context, use g_main_context_push_thread_default().
550 * @interface: (allow-none): The network interface to use, or %NULL to
552 * @port: Port to run on, or 0 if you don't care what port is used.
553 * @error: A location to store a #GError, or %NULL
555 * Create a new #GUPnPContext with the specified @main_context, @interface and
558 * Return value: A new #GUPnPContext object, or %NULL on an error
561 gupnp_context_new (GMainContext *main_context,
562 const char *interface,
567 g_warning ("gupnp_context_new::main_context is deprecated."
568 " Use g_main_context_push_thread_default()"
571 return g_initable_new (GUPNP_TYPE_CONTEXT,
574 "interface", interface,
580 * gupnp_context_get_host_ip:
581 * @context: A #GUPnPContext
583 * Get the IP address we advertise ourselves as using.
585 * Return value: The IP address. This string should not be freed.
587 * Deprecated:0.12.7: The "host-ip" property has moved to the base class
588 * #GSSDPClient so newer applications should use
589 * #gssdp_client_get_host_ip instead.
592 gupnp_context_get_host_ip (GUPnPContext *context)
594 return gssdp_client_get_host_ip (GSSDP_CLIENT (context));
598 * gupnp_context_get_port:
599 * @context: A #GUPnPContext
601 * Get the port that the SOAP server is running on.
603 * Return value: The port the SOAP server is running on.
606 gupnp_context_get_port (GUPnPContext *context)
610 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
612 server = gupnp_context_get_server (context);
613 return soup_server_get_port (server);
617 * gupnp_context_set_subscription_timeout:
618 * @context: A #GUPnPContext
619 * @timeout: Event subscription timeout in seconds
621 * Sets the event subscription timeout to @timeout. Use 0 if you don't
622 * want subscriptions to time out. Note that any client side subscriptions
623 * will automatically be renewed.
626 gupnp_context_set_subscription_timeout (GUPnPContext *context,
629 g_return_if_fail (GUPNP_IS_CONTEXT (context));
631 context->priv->subscription_timeout = timeout;
633 g_object_notify (G_OBJECT (context), "subscription-timeout");
637 * gupnp_context_get_subscription_timeout:
638 * @context: A #GUPnPContext
640 * Get the event subscription timeout (in seconds), or 0 meaning there is no
643 * Return value: The event subscription timeout in seconds.
646 gupnp_context_get_subscription_timeout (GUPnPContext *context)
648 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
650 return context->priv->subscription_timeout;
654 host_path_data_set_language (HostPathData *data, const char *language)
656 char *old_language = data->default_language;
658 if ((old_language != NULL) && (!strcmp (language, old_language)))
661 data->default_language = g_strdup (language);
664 if (old_language != NULL)
665 g_free (old_language);
669 * gupnp_context_set_default_language:
670 * @context: A #GUPnPContext
671 * @language A language tag as defined in RFC 2616 3.10
673 * Set the default language for the Content-Length header to @language.
675 * If the client sends an Accept-Language header the UPnP HTTP server
676 * is required to send a Content-Language header in return. If there are
677 * no files hosted in languages which match the requested ones the
678 * Content-Language header is set to this value. The default value is "en".
681 gupnp_context_set_default_language (GUPnPContext *context,
682 const char *language)
684 g_return_if_fail (GUPNP_IS_CONTEXT (context));
685 g_return_if_fail (language != NULL);
688 char *old_language = context->priv->default_language;
690 if ((old_language != NULL) && (!strcmp (language, old_language)))
693 context->priv->default_language = g_strdup (language);
695 g_list_foreach (context->priv->host_path_datas,
696 (GFunc) host_path_data_set_language,
697 (gpointer) language);
699 if (old_language != NULL)
700 g_free (old_language);
704 * gupnp_context_get_default_language:
705 * @context: A #GUPnPContext
707 * Get the default Content-Language header for this context.
709 * Returns: (transfer none): The default content of the Content-Language
713 gupnp_context_get_default_language (GUPnPContext *context)
715 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
717 return context->priv->default_language;
720 /* Construct a local path from @requested path, removing the last slash
721 * if any to make sure we append the locale suffix in a canonical way. */
723 construct_local_path (const char *requested_path,
724 const char *user_agent,
725 HostPathData *host_path_data)
733 if (user_agent != NULL) {
736 for (node = host_path_data->user_agents;
743 if (g_regex_match (agent->regex,
747 local_path = agent->local_path;
752 if (local_path == NULL)
753 local_path = host_path_data->local_path;
755 if (!requested_path || *requested_path == 0)
756 return g_strdup (local_path);
758 if (*requested_path != '/')
759 return NULL; /* Absolute paths only */
761 str = g_string_new (local_path);
763 /* Skip the length of the path relative to which @requested_path
765 requested_path += strlen (host_path_data->server_path);
767 /* Strip the last slashes to make sure we append the locale suffix
768 * in a canonical way. */
769 len = strlen (requested_path);
770 while (requested_path[len - 1] == '/')
773 g_string_append_len (str,
777 return g_string_free (str, FALSE);
780 /* Append locale suffix to @local_path. */
782 append_locale (const char *local_path, GList *locales)
785 return g_strdup (local_path);
787 return g_strdup_printf ("%s.%s",
789 (char *) locales->data);
792 /* Redirect @msg to the same URI, but with a slash appended. */
794 redirect_to_folder (SoupMessage *msg)
796 char *uri, *redir_uri;
798 uri = soup_uri_to_string (soup_message_get_uri (msg),
800 redir_uri = g_strdup_printf ("%s/", uri);
801 soup_message_headers_append (msg->response_headers,
802 "Location", redir_uri);
803 soup_message_set_status (msg,
804 SOUP_STATUS_MOVED_PERMANENTLY);
809 /* Serve @path. Note that we do not need to check for path including bogus
810 * '..' as libsoup does this for us. */
812 host_path_handler (SoupServer *server,
816 SoupClientContext *client,
819 char *local_path, *path_to_open;
822 GList *locales, *orig_locales;
823 GMappedFile *mapped_file;
825 HostPathData *host_path_data;
826 const char *user_agent;
832 host_path_data = (HostPathData *) user_data;
834 if (msg->method != SOUP_METHOD_GET &&
835 msg->method != SOUP_METHOD_HEAD) {
836 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
841 user_agent = soup_message_headers_get_one (msg->request_headers,
844 /* Construct base local path */
845 local_path = construct_local_path (path, user_agent, host_path_data);
847 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
852 /* Get preferred locales */
853 orig_locales = locales = http_request_get_accept_locales (msg);
856 /* Add locale suffix if available */
857 path_to_open = append_locale (local_path, locales);
859 /* See what we've got */
860 if (g_stat (path_to_open, &st) == -1) {
862 soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
863 else if (errno == ENOENT) {
865 g_free (path_to_open);
867 locales = locales->next;
871 soup_message_set_status (msg,
872 SOUP_STATUS_NOT_FOUND);
874 soup_message_set_status
875 (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
880 /* Handle directories */
881 if (S_ISDIR (st.st_mode)) {
882 if (!g_str_has_suffix (path, "/")) {
883 redirect_to_folder (msg);
888 /* This incorporates the locale portion in the folder name
891 local_path = g_build_filename (path_to_open,
895 g_free (path_to_open);
902 mapped_file = g_mapped_file_new (path_to_open, FALSE, &error);
904 if (mapped_file == NULL) {
905 g_warning ("Unable to map file %s: %s",
906 path_to_open, error->message);
908 g_error_free (error);
910 soup_message_set_status (msg,
911 SOUP_STATUS_INTERNAL_SERVER_ERROR);
916 /* Handle method (GET or HEAD) */
917 status = SOUP_STATUS_OK;
919 if (msg->method == SOUP_METHOD_GET) {
928 if (soup_message_headers_get_ranges (msg->request_headers,
934 /* We do not support mulipart/byteranges so only first first */
935 /* range from request is handled */
936 if (have_range && (ranges[0].end > st.st_size ||
938 ranges[0].start >= st.st_size ||
939 ranges[0].start > ranges[0].end)) {
940 soup_message_set_status
942 SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
943 soup_message_headers_free_ranges (msg->request_headers,
949 /* Add requested content */
950 buffer = soup_buffer_new_with_owner
951 (g_mapped_file_get_contents (mapped_file),
952 g_mapped_file_get_length (mapped_file),
954 (GDestroyNotify) g_mapped_file_unref);
956 /* Set range and status */
958 SoupBuffer *range_buffer;
960 soup_message_body_truncate (msg->response_body);
961 soup_message_headers_set_content_range (
962 msg->response_headers,
966 range_buffer = soup_buffer_new_subbuffer (
969 ranges[0].end - ranges[0].start + 1);
970 soup_message_body_append_buffer (msg->response_body,
972 status = SOUP_STATUS_PARTIAL_CONTENT;
974 soup_message_headers_free_ranges (msg->request_headers,
976 soup_buffer_free (range_buffer);
978 soup_message_body_append_buffer (msg->response_body, buffer);
980 soup_buffer_free (buffer);
981 } else if (msg->method == SOUP_METHOD_HEAD) {
984 length = g_strdup_printf ("%lu", (gulong) st.st_size);
985 soup_message_headers_append (msg->response_headers,
991 soup_message_set_status (msg,
992 SOUP_STATUS_METHOD_NOT_ALLOWED);
997 /* Set Content-Type */
998 http_response_set_content_type (msg,
1000 (guchar *) g_mapped_file_get_contents
1004 /* Set Content-Language */
1006 http_response_set_content_locale (msg, locales->data);
1007 else if (soup_message_headers_get_one (msg->request_headers,
1008 "Accept-Language")) {
1009 soup_message_headers_append (msg->response_headers,
1011 host_path_data->default_language);
1014 /* Set Accept-Ranges */
1015 soup_message_headers_append (msg->response_headers,
1020 soup_message_set_status (msg, status);
1024 g_free (path_to_open);
1025 g_free (local_path);
1027 while (orig_locales) {
1028 g_free (orig_locales->data);
1029 orig_locales = g_list_delete_link (orig_locales, orig_locales);
1034 user_agent_new (const char *local_path,
1039 agent = g_slice_new0 (UserAgent);
1041 agent->local_path = g_strdup (local_path);
1042 agent->regex = g_regex_ref (regex);
1048 user_agent_free (UserAgent *agent)
1050 g_free (agent->local_path);
1051 g_regex_unref (agent->regex);
1053 g_slice_free (UserAgent, agent);
1056 static HostPathData *
1057 host_path_data_new (const char *local_path,
1058 const char *server_path,
1059 const char *default_language)
1061 HostPathData *path_data;
1063 path_data = g_slice_new0 (HostPathData);
1065 path_data->local_path = g_strdup (local_path);
1066 path_data->server_path = g_strdup (server_path);
1067 path_data->default_language = g_strdup (default_language);
1073 host_path_data_free (HostPathData *path_data)
1075 g_free (path_data->local_path);
1076 g_free (path_data->server_path);
1077 g_free (path_data->default_language);
1079 while (path_data->user_agents) {
1082 agent = path_data->user_agents->data;
1084 user_agent_free (agent);
1086 path_data->user_agents = g_list_delete_link (
1087 path_data->user_agents,
1088 path_data->user_agents);
1091 g_slice_free (HostPathData, path_data);
1095 * gupnp_context_host_path:
1096 * @context: A #GUPnPContext
1097 * @local_path: Path to the local file or folder to be hosted
1098 * @server_path: Web server path where @local_path should be hosted
1100 * Start hosting @local_path at @server_path. Files with the path
1101 * @local_path.LOCALE (if they exist) will be served up when LOCALE is
1102 * specified in the request's Accept-Language header.
1105 gupnp_context_host_path (GUPnPContext *context,
1106 const char *local_path,
1107 const char *server_path)
1110 HostPathData *path_data;
1112 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1113 g_return_if_fail (local_path != NULL);
1114 g_return_if_fail (server_path != NULL);
1116 server = gupnp_context_get_server (context);
1118 path_data = host_path_data_new (local_path,
1120 context->priv->default_language);
1122 soup_server_add_handler (server,
1128 context->priv->host_path_datas =
1129 g_list_append (context->priv->host_path_datas,
1134 path_compare_func (HostPathData *path_data,
1135 const char *server_path)
1137 /* ignore default language */
1138 return strcmp (path_data->server_path, server_path);
1142 * gupnp_context_host_path_for_agent:
1143 * @context: A #GUPnPContext
1144 * @local_path: Path to the local file or folder to be hosted
1145 * @server_path: Web server path already being hosted
1146 * @user_agent: The user-agent as a #GRegex.
1148 * Use this method to serve different local path to specific user-agent(s). The
1149 * path @server_path must already be hosted by @context.
1151 * Return value: %TRUE on success, %FALSE otherwise.
1154 gupnp_context_host_path_for_agent (GUPnPContext *context,
1155 const char *local_path,
1156 const char *server_path,
1161 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), FALSE);
1162 g_return_val_if_fail (local_path != NULL, FALSE);
1163 g_return_val_if_fail (server_path != NULL, FALSE);
1164 g_return_val_if_fail (user_agent != NULL, FALSE);
1166 node = g_list_find_custom (context->priv->host_path_datas,
1168 (GCompareFunc) path_compare_func);
1170 HostPathData *path_data;
1173 path_data = (HostPathData *) node->data;
1174 agent = user_agent_new (local_path, user_agent);
1176 path_data->user_agents = g_list_append (path_data->user_agents,
1185 * gupnp_context_unhost_path:
1186 * @context: A #GUPnPContext
1187 * @server_path: Web server path where the file or folder is hosted
1189 * Stop hosting the file or folder at @server_path.
1192 gupnp_context_unhost_path (GUPnPContext *context,
1193 const char *server_path)
1196 HostPathData *path_data;
1199 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1200 g_return_if_fail (server_path != NULL);
1202 server = gupnp_context_get_server (context);
1204 node = g_list_find_custom (context->priv->host_path_datas,
1206 (GCompareFunc) path_compare_func);
1207 g_return_if_fail (node != NULL);
1209 path_data = (HostPathData *) node->data;
1210 context->priv->host_path_datas = g_list_delete_link (
1211 context->priv->host_path_datas,
1214 soup_server_remove_handler (server, server_path);
1215 host_path_data_free (path_data);