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.
45 #include <sys/utsname.h>
47 #include <sys/types.h>
49 #include <libsoup/soup-address.h>
50 #include <glib/gstdio.h>
52 #include "gupnp-context.h"
53 #include "gupnp-context-private.h"
54 #include "gupnp-error.h"
55 #include "gupnp-marshal.h"
56 #include "gena-protocol.h"
57 #include "http-headers.h"
59 #define GUPNP_CONTEXT_DEFAULT_LANGUAGE "en"
62 gupnp_context_initable_iface_init (gpointer g_iface,
66 G_DEFINE_TYPE_EXTENDED (GUPnPContext,
72 gupnp_context_initable_iface_init));
74 struct _GUPnPContextPrivate {
77 guint subscription_timeout;
81 SoupServer *server; /* Started on demand */
83 char *default_language;
85 GList *host_path_datas;
93 PROP_SUBSCRIPTION_TIMEOUT,
106 char *default_language;
111 static GInitableIface* initable_parent_iface = NULL;
114 * Generates the default server ID.
117 make_server_id (void)
120 OSVERSIONINFO versioninfo;
121 versioninfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
122 if (GetVersionEx (&versioninfo)) {
123 return g_strdup_printf ("Microsoft Windows/%ld.%ld"
124 " UPnP/1.0 GUPnP/%s",
125 versioninfo.dwMajorVersion,
126 versioninfo.dwMinorVersion,
129 return g_strdup_printf ("Microsoft Windows UPnP/1.0 GUPnP/%s",
133 struct utsname sysinfo;
137 return g_strdup_printf ("%s/%s UPnP/1.0 GUPnP/%s",
145 gupnp_context_init (GUPnPContext *context)
150 G_TYPE_INSTANCE_GET_PRIVATE (context,
152 GUPnPContextPrivate);
154 server_id = make_server_id ();
155 gssdp_client_set_server_id (GSSDP_CLIENT (context), server_id);
160 gupnp_context_initable_init (GInitable *initable,
161 GCancellable *cancellable,
165 GError *inner_error = NULL;
166 GUPnPContext *context;
168 if (!initable_parent_iface->init(initable,
171 g_propagate_error (error, inner_error);
176 context = GUPNP_CONTEXT (initable);
178 context->priv->session = soup_session_async_new_with_options
179 (SOUP_SESSION_IDLE_TIMEOUT,
181 SOUP_SESSION_ASYNC_CONTEXT,
182 g_main_context_get_thread_default (),
185 user_agent = g_strdup_printf ("%s GUPnP/" VERSION " DLNADOC/1.50",
186 g_get_application_name ()? : "");
187 g_object_set (context->priv->session,
188 SOUP_SESSION_USER_AGENT,
193 if (g_getenv ("GUPNP_DEBUG")) {
195 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
196 soup_session_add_feature (context->priv->session,
197 SOUP_SESSION_FEATURE (logger));
200 soup_session_add_feature_by_type (context->priv->session,
201 SOUP_TYPE_CONTENT_DECODER);
203 /* Create the server already if the port is not null*/
204 if (context->priv->port != 0) {
205 gupnp_context_get_server (context);
207 if (context->priv->server == NULL) {
208 g_object_unref (context->priv->session);
209 context->priv->session = NULL;
213 GUPNP_SERVER_ERROR_OTHER,
214 "Could not create HTTP server on port %d",
215 context->priv->port);
225 gupnp_context_initable_iface_init (gpointer g_iface,
228 GInitableIface *iface = (GInitableIface *)g_iface;
229 initable_parent_iface = g_type_interface_peek_parent (iface);
230 iface->init = gupnp_context_initable_init;
234 gupnp_context_set_property (GObject *object,
239 GUPnPContext *context;
241 context = GUPNP_CONTEXT (object);
243 switch (property_id) {
245 context->priv->port = g_value_get_uint (value);
247 case PROP_SUBSCRIPTION_TIMEOUT:
248 context->priv->subscription_timeout = g_value_get_uint (value);
250 case PROP_DEFAULT_LANGUAGE:
251 gupnp_context_set_default_language (context,
252 g_value_get_string (value));
255 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
261 gupnp_context_get_property (GObject *object,
266 GUPnPContext *context;
268 context = GUPNP_CONTEXT (object);
270 switch (property_id) {
272 g_value_set_uint (value,
273 gupnp_context_get_port (context));
276 g_value_set_object (value,
277 gupnp_context_get_server (context));
280 g_value_set_object (value,
281 gupnp_context_get_session (context));
283 case PROP_SUBSCRIPTION_TIMEOUT:
284 g_value_set_uint (value,
285 gupnp_context_get_subscription_timeout
288 case PROP_DEFAULT_LANGUAGE:
289 g_value_set_string (value,
290 gupnp_context_get_default_language
294 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
300 gupnp_context_dispose (GObject *object)
302 GUPnPContext *context;
303 GObjectClass *object_class;
305 context = GUPNP_CONTEXT (object);
307 if (context->priv->session) {
308 g_object_unref (context->priv->session);
309 context->priv->session = NULL;
312 while (context->priv->host_path_datas) {
315 data = (HostPathData *) context->priv->host_path_datas->data;
317 gupnp_context_unhost_path (context, data->server_path);
320 if (context->priv->server) {
321 g_object_unref (context->priv->server);
322 context->priv->server = NULL;
326 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
327 object_class->dispose (object);
331 gupnp_context_finalize (GObject *object)
333 GUPnPContext *context;
334 GObjectClass *object_class;
336 context = GUPNP_CONTEXT (object);
338 if (context->priv->default_language) {
339 g_free (context->priv->default_language);
340 context->priv->default_language = NULL;
343 g_free (context->priv->server_url);
346 object_class = G_OBJECT_CLASS (gupnp_context_parent_class);
347 object_class->finalize (object);
351 gupnp_context_class_init (GUPnPContextClass *klass)
353 GObjectClass *object_class;
355 object_class = G_OBJECT_CLASS (klass);
357 object_class->set_property = gupnp_context_set_property;
358 object_class->get_property = gupnp_context_get_property;
359 object_class->dispose = gupnp_context_dispose;
360 object_class->finalize = gupnp_context_finalize;
362 g_type_class_add_private (klass, sizeof (GUPnPContextPrivate));
367 * The port to run on. Set to 0 if you don't care what port to run on.
369 g_object_class_install_property
372 g_param_spec_uint ("port",
375 0, G_MAXUINT, SOUP_ADDRESS_ANY_PORT,
377 G_PARAM_CONSTRUCT_ONLY |
378 G_PARAM_STATIC_NAME |
379 G_PARAM_STATIC_NICK |
380 G_PARAM_STATIC_BLURB));
383 * GUPnPContext:server:
385 * The #SoupServer HTTP server used by GUPnP.
387 g_object_class_install_property
390 g_param_spec_object ("server",
392 "SoupServer HTTP server",
395 G_PARAM_STATIC_NAME |
396 G_PARAM_STATIC_NICK |
397 G_PARAM_STATIC_BLURB));
400 * GUPnPContext:session:
402 * The #SoupSession object used by GUPnP.
404 g_object_class_install_property
407 g_param_spec_object ("session",
409 "SoupSession object",
412 G_PARAM_STATIC_NAME |
413 G_PARAM_STATIC_NICK |
414 G_PARAM_STATIC_BLURB));
417 * GUPnPContext:subscription-timeout:
419 * The preferred subscription timeout: the number of seconds after
420 * which subscriptions are renewed. Set to '0' if subscriptions
421 * are never to time out.
423 g_object_class_install_property
425 PROP_SUBSCRIPTION_TIMEOUT,
426 g_param_spec_uint ("subscription-timeout",
427 "Subscription timeout",
428 "Subscription timeout",
431 GENA_DEFAULT_TIMEOUT,
433 G_PARAM_CONSTRUCT_ONLY |
434 G_PARAM_STATIC_NAME |
435 G_PARAM_STATIC_NICK |
436 G_PARAM_STATIC_BLURB));
438 * GUPnPContext:default-language:
440 * The content of the Content-Language header id the client
441 * sends Accept-Language and no language-specific pages to serve
442 * exist. The property defaults to 'en'.
444 g_object_class_install_property
446 PROP_DEFAULT_LANGUAGE,
447 g_param_spec_string ("default-language",
450 GUPNP_CONTEXT_DEFAULT_LANGUAGE,
453 G_PARAM_STATIC_NAME |
454 G_PARAM_STATIC_NICK |
455 G_PARAM_STATIC_BLURB));
459 * gupnp_context_get_session:
460 * @context: A #GUPnPContext
462 * Get the #SoupSession object that GUPnP is using.
464 * Return value: (transfer none): The #SoupSession used by GUPnP. Do not unref
465 * this when finished.
468 gupnp_context_get_session (GUPnPContext *context)
470 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
472 return context->priv->session;
476 * Default server handler: Return 404 not found.
479 default_server_handler (SoupServer *server,
483 SoupClientContext *client,
486 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
490 * gupnp_context_get_server:
491 * @context: A #GUPnPContext
493 * Get the #SoupServer HTTP server that GUPnP is using.
495 * Returns: (transfer none): The #SoupServer used by GUPnP. Do not unref this when finished.
498 gupnp_context_get_server (GUPnPContext *context)
500 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
502 if (context->priv->server == NULL) {
505 ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context));
506 SoupAddress *addr = soup_address_new (ip, context->priv->port);
507 soup_address_resolve_sync (addr, NULL);
509 context->priv->server = soup_server_new
512 SOUP_SERVER_ASYNC_CONTEXT,
513 g_main_context_get_thread_default (),
514 SOUP_SERVER_INTERFACE,
517 g_object_unref (addr);
519 if (context->priv->server) {
520 soup_server_add_handler (context->priv->server,
522 default_server_handler,
526 soup_server_run_async (context->priv->server);
530 return context->priv->server;
534 * Makes an URL that refers to our server.
537 make_server_url (GUPnPContext *context)
542 /* What port are we running on? */
543 server = gupnp_context_get_server (context);
544 port = soup_server_get_port (server);
546 /* Put it all together */
547 return g_strdup_printf
549 gssdp_client_get_host_ip (GSSDP_CLIENT (context)),
554 _gupnp_context_get_server_url (GUPnPContext *context)
556 if (context->priv->server_url == NULL)
557 context->priv->server_url = make_server_url (context);
559 return (const char *) context->priv->server_url;
564 * @main_context: (allow-none): Deprecated: 0.17.2: Always set to %NULL. If you
565 * want to use a different context, use g_main_context_push_thread_default().
566 * @iface: (allow-none): The network interface to use, or %NULL to
568 * @port: Port to run on, or 0 if you don't care what port is used.
569 * @error: A location to store a #GError, or %NULL
571 * Create a new #GUPnPContext with the specified @main_context, @iface and
574 * Return value: A new #GUPnPContext object, or %NULL on an error
577 gupnp_context_new (GMainContext *main_context,
583 g_warning ("gupnp_context_new::main_context is deprecated."
584 " Use g_main_context_push_thread_default()"
587 return g_initable_new (GUPNP_TYPE_CONTEXT,
596 * gupnp_context_get_host_ip:
597 * @context: A #GUPnPContext
599 * Get the IP address we advertise ourselves as using.
601 * Return value: The IP address. This string should not be freed.
603 * Deprecated:0.12.7: The "host-ip" property has moved to the base class
604 * #GSSDPClient so newer applications should use
605 * #gssdp_client_get_host_ip instead.
608 gupnp_context_get_host_ip (GUPnPContext *context)
610 return gssdp_client_get_host_ip (GSSDP_CLIENT (context));
614 * gupnp_context_get_port:
615 * @context: A #GUPnPContext
617 * Get the port that the SOAP server is running on.
619 * Return value: The port the SOAP server is running on.
622 gupnp_context_get_port (GUPnPContext *context)
626 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
628 server = gupnp_context_get_server (context);
629 return soup_server_get_port (server);
633 * gupnp_context_set_subscription_timeout:
634 * @context: A #GUPnPContext
635 * @timeout: Event subscription timeout in seconds
637 * Sets the event subscription timeout to @timeout. Use 0 if you don't
638 * want subscriptions to time out. Note that any client side subscriptions
639 * will automatically be renewed.
642 gupnp_context_set_subscription_timeout (GUPnPContext *context,
645 g_return_if_fail (GUPNP_IS_CONTEXT (context));
647 context->priv->subscription_timeout = timeout;
649 g_object_notify (G_OBJECT (context), "subscription-timeout");
653 * gupnp_context_get_subscription_timeout:
654 * @context: A #GUPnPContext
656 * Get the event subscription timeout (in seconds), or 0 meaning there is no
659 * Return value: The event subscription timeout in seconds.
662 gupnp_context_get_subscription_timeout (GUPnPContext *context)
664 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), 0);
666 return context->priv->subscription_timeout;
670 host_path_data_set_language (HostPathData *data, const char *language)
672 char *old_language = data->default_language;
674 if ((old_language != NULL) && (!strcmp (language, old_language)))
677 data->default_language = g_strdup (language);
680 if (old_language != NULL)
681 g_free (old_language);
685 * gupnp_context_set_default_language:
686 * @context: A #GUPnPContext
687 * @language A language tag as defined in RFC 2616 3.10
689 * Set the default language for the Content-Length header to @language.
691 * If the client sends an Accept-Language header the UPnP HTTP server
692 * is required to send a Content-Language header in return. If there are
693 * no files hosted in languages which match the requested ones the
694 * Content-Language header is set to this value. The default value is "en".
697 gupnp_context_set_default_language (GUPnPContext *context,
698 const char *language)
700 g_return_if_fail (GUPNP_IS_CONTEXT (context));
701 g_return_if_fail (language != NULL);
704 char *old_language = context->priv->default_language;
706 if ((old_language != NULL) && (!strcmp (language, old_language)))
709 context->priv->default_language = g_strdup (language);
711 g_list_foreach (context->priv->host_path_datas,
712 (GFunc) host_path_data_set_language,
713 (gpointer) language);
715 if (old_language != NULL)
716 g_free (old_language);
720 * gupnp_context_get_default_language:
721 * @context: A #GUPnPContext
723 * Get the default Content-Language header for this context.
725 * Returns: (transfer none): The default content of the Content-Language
729 gupnp_context_get_default_language (GUPnPContext *context)
731 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
733 return context->priv->default_language;
736 /* Construct a local path from @requested path, removing the last slash
737 * if any to make sure we append the locale suffix in a canonical way. */
739 construct_local_path (const char *requested_path,
740 const char *user_agent,
741 HostPathData *host_path_data)
749 if (user_agent != NULL) {
752 for (node = host_path_data->user_agents;
759 if (g_regex_match (agent->regex,
763 local_path = agent->local_path;
768 if (local_path == NULL)
769 local_path = host_path_data->local_path;
771 if (!requested_path || *requested_path == 0)
772 return g_strdup (local_path);
774 if (*requested_path != '/')
775 return NULL; /* Absolute paths only */
777 str = g_string_new (local_path);
779 /* Skip the length of the path relative to which @requested_path
781 requested_path += strlen (host_path_data->server_path);
783 /* Strip the last slashes to make sure we append the locale suffix
784 * in a canonical way. */
785 len = strlen (requested_path);
786 while (requested_path[len - 1] == '/')
789 g_string_append_len (str,
793 return g_string_free (str, FALSE);
796 /* Append locale suffix to @local_path. */
798 append_locale (const char *local_path, GList *locales)
801 return g_strdup (local_path);
803 return g_strdup_printf ("%s.%s",
805 (char *) locales->data);
808 /* Redirect @msg to the same URI, but with a slash appended. */
810 redirect_to_folder (SoupMessage *msg)
812 char *uri, *redir_uri;
814 uri = soup_uri_to_string (soup_message_get_uri (msg),
816 redir_uri = g_strdup_printf ("%s/", uri);
817 soup_message_headers_append (msg->response_headers,
818 "Location", redir_uri);
819 soup_message_set_status (msg,
820 SOUP_STATUS_MOVED_PERMANENTLY);
825 /* Serve @path. Note that we do not need to check for path including bogus
826 * '..' as libsoup does this for us. */
828 host_path_handler (SoupServer *server,
832 SoupClientContext *client,
835 char *local_path, *path_to_open;
838 GList *locales, *orig_locales;
839 GMappedFile *mapped_file;
841 HostPathData *host_path_data;
842 const char *user_agent;
848 host_path_data = (HostPathData *) user_data;
850 if (msg->method != SOUP_METHOD_GET &&
851 msg->method != SOUP_METHOD_HEAD) {
852 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
857 user_agent = soup_message_headers_get_one (msg->request_headers,
860 /* Construct base local path */
861 local_path = construct_local_path (path, user_agent, host_path_data);
863 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
868 /* Get preferred locales */
869 orig_locales = locales = http_request_get_accept_locales (msg);
872 /* Add locale suffix if available */
873 path_to_open = append_locale (local_path, locales);
875 /* See what we've got */
876 if (g_stat (path_to_open, &st) == -1) {
878 soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
879 else if (errno == ENOENT) {
881 g_free (path_to_open);
883 locales = locales->next;
887 soup_message_set_status (msg,
888 SOUP_STATUS_NOT_FOUND);
890 soup_message_set_status
891 (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
896 /* Handle directories */
897 if (S_ISDIR (st.st_mode)) {
898 if (!g_str_has_suffix (path, "/")) {
899 redirect_to_folder (msg);
904 /* This incorporates the locale portion in the folder name
907 local_path = g_build_filename (path_to_open,
911 g_free (path_to_open);
918 mapped_file = g_mapped_file_new (path_to_open, FALSE, &error);
920 if (mapped_file == NULL) {
921 g_warning ("Unable to map file %s: %s",
922 path_to_open, error->message);
924 g_error_free (error);
926 soup_message_set_status (msg,
927 SOUP_STATUS_INTERNAL_SERVER_ERROR);
932 /* Handle method (GET or HEAD) */
933 status = SOUP_STATUS_OK;
935 if (msg->method == SOUP_METHOD_GET) {
944 if (soup_message_headers_get_ranges (msg->request_headers,
950 /* We do not support mulipart/byteranges so only first first */
951 /* range from request is handled */
952 if (have_range && (ranges[0].end > st.st_size ||
954 ranges[0].start >= st.st_size ||
955 ranges[0].start > ranges[0].end)) {
956 soup_message_set_status
958 SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
959 soup_message_headers_free_ranges (msg->request_headers,
965 /* Add requested content */
966 buffer = soup_buffer_new_with_owner
967 (g_mapped_file_get_contents (mapped_file),
968 g_mapped_file_get_length (mapped_file),
970 (GDestroyNotify) g_mapped_file_unref);
972 /* Set range and status */
974 SoupBuffer *range_buffer;
976 soup_message_body_truncate (msg->response_body);
977 soup_message_headers_set_content_range (
978 msg->response_headers,
982 range_buffer = soup_buffer_new_subbuffer (
985 ranges[0].end - ranges[0].start + 1);
986 soup_message_body_append_buffer (msg->response_body,
988 status = SOUP_STATUS_PARTIAL_CONTENT;
990 soup_message_headers_free_ranges (msg->request_headers,
992 soup_buffer_free (range_buffer);
994 soup_message_body_append_buffer (msg->response_body, buffer);
996 soup_buffer_free (buffer);
997 } else if (msg->method == SOUP_METHOD_HEAD) {
1000 length = g_strdup_printf ("%lu", (gulong) st.st_size);
1001 soup_message_headers_append (msg->response_headers,
1007 soup_message_set_status (msg,
1008 SOUP_STATUS_METHOD_NOT_ALLOWED);
1013 /* Set Content-Type */
1014 http_response_set_content_type (msg,
1016 (guchar *) g_mapped_file_get_contents
1020 /* Set Content-Language */
1022 http_response_set_content_locale (msg, locales->data);
1023 else if (soup_message_headers_get_one (msg->request_headers,
1024 "Accept-Language")) {
1025 soup_message_headers_append (msg->response_headers,
1027 host_path_data->default_language);
1030 /* Set Accept-Ranges */
1031 soup_message_headers_append (msg->response_headers,
1036 soup_message_set_status (msg, status);
1040 g_free (path_to_open);
1041 g_free (local_path);
1043 while (orig_locales) {
1044 g_free (orig_locales->data);
1045 orig_locales = g_list_delete_link (orig_locales, orig_locales);
1050 user_agent_new (const char *local_path,
1055 agent = g_slice_new0 (UserAgent);
1057 agent->local_path = g_strdup (local_path);
1058 agent->regex = g_regex_ref (regex);
1064 user_agent_free (UserAgent *agent)
1066 g_free (agent->local_path);
1067 g_regex_unref (agent->regex);
1069 g_slice_free (UserAgent, agent);
1072 static HostPathData *
1073 host_path_data_new (const char *local_path,
1074 const char *server_path,
1075 const char *default_language)
1077 HostPathData *path_data;
1079 path_data = g_slice_new0 (HostPathData);
1081 path_data->local_path = g_strdup (local_path);
1082 path_data->server_path = g_strdup (server_path);
1083 path_data->default_language = g_strdup (default_language);
1089 host_path_data_free (HostPathData *path_data)
1091 g_free (path_data->local_path);
1092 g_free (path_data->server_path);
1093 g_free (path_data->default_language);
1095 while (path_data->user_agents) {
1098 agent = path_data->user_agents->data;
1100 user_agent_free (agent);
1102 path_data->user_agents = g_list_delete_link (
1103 path_data->user_agents,
1104 path_data->user_agents);
1107 g_slice_free (HostPathData, path_data);
1111 * gupnp_context_host_path:
1112 * @context: A #GUPnPContext
1113 * @local_path: Path to the local file or folder to be hosted
1114 * @server_path: Web server path where @local_path should be hosted
1116 * Start hosting @local_path at @server_path. Files with the path
1117 * @local_path.LOCALE (if they exist) will be served up when LOCALE is
1118 * specified in the request's Accept-Language header.
1121 gupnp_context_host_path (GUPnPContext *context,
1122 const char *local_path,
1123 const char *server_path)
1126 HostPathData *path_data;
1128 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1129 g_return_if_fail (local_path != NULL);
1130 g_return_if_fail (server_path != NULL);
1132 server = gupnp_context_get_server (context);
1134 path_data = host_path_data_new (local_path,
1136 context->priv->default_language);
1138 soup_server_add_handler (server,
1144 context->priv->host_path_datas =
1145 g_list_append (context->priv->host_path_datas,
1150 path_compare_func (HostPathData *path_data,
1151 const char *server_path)
1153 /* ignore default language */
1154 return strcmp (path_data->server_path, server_path);
1158 * gupnp_context_host_path_for_agent:
1159 * @context: A #GUPnPContext
1160 * @local_path: Path to the local file or folder to be hosted
1161 * @server_path: Web server path already being hosted
1162 * @user_agent: The user-agent as a #GRegex.
1164 * Use this method to serve different local path to specific user-agent(s). The
1165 * path @server_path must already be hosted by @context.
1167 * Return value: %TRUE on success, %FALSE otherwise.
1170 gupnp_context_host_path_for_agent (GUPnPContext *context,
1171 const char *local_path,
1172 const char *server_path,
1177 g_return_val_if_fail (GUPNP_IS_CONTEXT (context), FALSE);
1178 g_return_val_if_fail (local_path != NULL, FALSE);
1179 g_return_val_if_fail (server_path != NULL, FALSE);
1180 g_return_val_if_fail (user_agent != NULL, FALSE);
1182 node = g_list_find_custom (context->priv->host_path_datas,
1184 (GCompareFunc) path_compare_func);
1186 HostPathData *path_data;
1189 path_data = (HostPathData *) node->data;
1190 agent = user_agent_new (local_path, user_agent);
1192 path_data->user_agents = g_list_append (path_data->user_agents,
1201 * gupnp_context_unhost_path:
1202 * @context: A #GUPnPContext
1203 * @server_path: Web server path where the file or folder is hosted
1205 * Stop hosting the file or folder at @server_path.
1208 gupnp_context_unhost_path (GUPnPContext *context,
1209 const char *server_path)
1212 HostPathData *path_data;
1215 g_return_if_fail (GUPNP_IS_CONTEXT (context));
1216 g_return_if_fail (server_path != NULL);
1218 server = gupnp_context_get_server (context);
1220 node = g_list_find_custom (context->priv->host_path_datas,
1222 (GCompareFunc) path_compare_func);
1223 g_return_if_fail (node != NULL);
1225 path_data = (HostPathData *) node->data;
1226 context->priv->host_path_datas = g_list_delete_link (
1227 context->priv->host_path_datas,
1230 soup_server_remove_handler (server, server_path);
1231 host_path_data_free (path_data);