Imported Upstream version 0.20.12
[profile/ivi/GUPnP.git] / libgupnp / gupnp-service.c
1 /*
2  * Copyright (C) 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gupnp-service
24  * @short_description: Class for service implementations.
25  *
26  * #GUPnPService allows for handling incoming actions and state variable
27  * notification. #GUPnPService implements the #GUPnPServiceInfo interface.
28  */
29
30 #include <gobject/gvaluecollector.h>
31 #include <gmodule.h>
32 #include <libsoup/soup-date.h>
33 #include <string.h>
34
35 #include "gupnp-service.h"
36 #include "gupnp-root-device.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-marshal.h"
39 #include "gupnp-error.h"
40 #include "gupnp-acl.h"
41 #include "http-headers.h"
42 #include "gena-protocol.h"
43 #include "xml-util.h"
44 #include "gvalue-util.h"
45
46 #ifdef G_OS_WIN32
47 #include <rpc.h>
48 #else
49 #include <uuid/uuid.h>
50 #endif
51
52 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
53
54 G_DEFINE_TYPE (GUPnPService,
55                gupnp_service,
56                GUPNP_TYPE_SERVICE_INFO);
57
58 struct _GUPnPServicePrivate {
59         GUPnPRootDevice *root_device;
60
61         SoupSession     *session;
62
63         guint            notify_available_id;
64
65         GHashTable      *subscriptions;
66
67         GList           *state_variables;
68
69         GQueue          *notify_queue;
70
71         gboolean         notify_frozen;
72 };
73
74 enum {
75         PROP_0,
76         PROP_ROOT_DEVICE
77 };
78
79 enum {
80         ACTION_INVOKED,
81         QUERY_VARIABLE,
82         NOTIFY_FAILED,
83         LAST_SIGNAL
84 };
85
86 static guint signals[LAST_SIGNAL];
87
88 static char *
89 create_property_set (GQueue *queue);
90
91 static void
92 notify_subscriber   (gpointer key,
93                      gpointer value,
94                      gpointer user_data);
95
96 typedef struct {
97         GUPnPService *service;
98
99         GList        *callbacks;
100         char         *sid;
101
102         int           seq;
103
104         GSource      *timeout_src;
105
106         GList        *pending_messages; /* Pending SoupMessages from this
107                                            subscription */
108         gboolean      initial_state_sent;
109         gboolean      to_delete;
110 } SubscriptionData;
111
112 static gboolean
113 subscription_data_can_delete (SubscriptionData *data) {
114     return data->initial_state_sent && data->to_delete;
115 }
116
117 static void
118 send_initial_state (SubscriptionData *data);
119
120 static SoupSession *
121 gupnp_service_get_session (GUPnPService *service)
122 {
123         if (! service->priv->session) {
124                 /* Create a dedicated session for this service to
125                  * ensure that notifications are sent in the proper
126                  * order. The session from GUPnPContext may use
127                  * multiple connections.
128                  */
129                 service->priv->session = soup_session_async_new_with_options
130                   (SOUP_SESSION_IDLE_TIMEOUT, 60,
131                    SOUP_SESSION_ASYNC_CONTEXT,
132                    g_main_context_get_thread_default (),
133                    SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
134                    NULL);
135
136                 if (g_getenv ("GUPNP_DEBUG")) {
137                         SoupLogger *logger;
138                         logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
139                         soup_session_add_feature (
140                                         service->priv->session,
141                                         SOUP_SESSION_FEATURE (logger));
142                 }
143         }
144
145         return service->priv->session;
146 }
147
148 static void
149 subscription_data_free (SubscriptionData *data)
150 {
151         SoupSession *session;
152
153         session = gupnp_service_get_session (data->service);
154
155         /* Cancel pending messages */
156         while (data->pending_messages) {
157                 SoupMessage *msg;
158
159                 msg = data->pending_messages->data;
160
161                 soup_session_cancel_message (session,
162                                              msg,
163                                              SOUP_STATUS_CANCELLED);
164
165                 data->pending_messages =
166                         g_list_delete_link (data->pending_messages,
167                                             data->pending_messages);
168         }
169        
170         /* Further cleanup */
171         g_list_free_full (data->callbacks, g_free);
172
173         g_free (data->sid);
174
175         if (data->timeout_src)
176                 g_source_destroy (data->timeout_src);
177
178         g_slice_free (SubscriptionData, data);
179 }
180
181 typedef struct {
182         char  *variable;
183         GValue value;
184 } NotifyData;
185
186 static void
187 notify_data_free (NotifyData *data)
188 {
189         g_free (data->variable);
190         g_value_unset (&data->value);
191
192         g_slice_free (NotifyData, data);
193 }
194
195 struct _GUPnPServiceAction {
196         volatile gint ref_count;
197
198         GUPnPContext *context;
199
200         char         *name;
201
202         SoupMessage  *msg;
203         gboolean      accept_gzip;
204
205         GUPnPXMLDoc  *doc;
206         xmlNode      *node;
207
208         GString      *response_str;
209
210         guint         argument_count;
211 };
212
213 GUPnPServiceAction *
214 gupnp_service_action_ref (GUPnPServiceAction *action)
215 {
216         g_return_val_if_fail (action, NULL);
217         g_return_val_if_fail (action->ref_count > 0, NULL);
218
219         g_atomic_int_inc (&action->ref_count);
220
221         return action;
222 }
223
224 void
225 gupnp_service_action_unref (GUPnPServiceAction *action)
226 {
227         g_return_if_fail (action);
228         g_return_if_fail (action->ref_count > 0);
229
230         if (g_atomic_int_dec_and_test (&action->ref_count)) {
231                 g_free (action->name);
232                 g_object_unref (action->msg);
233                 g_object_unref (action->context);
234                 g_object_unref (action->doc);
235
236                 g_slice_free (GUPnPServiceAction, action);
237         }
238 }
239
240 /**
241  * gupnp_service_action_get_type:
242  *
243  * Get the gtype for GUPnPServiceActon
244  *
245  * Return value: The gtype of GUPnPServiceAction
246  **/
247 GType
248 gupnp_service_action_get_type (void)
249 {
250         static GType our_type = 0;
251
252         if (our_type == 0)
253                 our_type = g_boxed_type_register_static
254                         ("GUPnPServiceAction",
255                          (GBoxedCopyFunc) gupnp_service_action_ref,
256                          (GBoxedFreeFunc) gupnp_service_action_unref);
257
258         return our_type;
259 }
260
261 static void
262 finalize_action (GUPnPServiceAction *action)
263 {
264         SoupServer *server;
265
266         /* Embed action->response_str in a SOAP document */
267         g_string_prepend (action->response_str,
268                           "<?xml version=\"1.0\"?>"
269                           "<s:Envelope xmlns:s="
270                                 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
271                           "s:encodingStyle="
272                                 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
273                           "<s:Body>");
274
275         if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
276                 g_string_append (action->response_str, "</u:");
277                 g_string_append (action->response_str, action->name);
278                 g_string_append (action->response_str, "Response>");
279         }
280
281         g_string_append (action->response_str,
282                          "</s:Body>"
283                          "</s:Envelope>");
284
285         soup_message_headers_replace (action->msg->response_headers,
286                                       "Content-Type",
287                                       "text/xml; charset=\"utf-8\"");
288
289         if (action->accept_gzip && action->response_str->len > 1024) {
290                 http_response_set_body_gzip (action->msg,
291                                              action->response_str->str,
292                                              action->response_str->len);
293                 g_string_free (action->response_str, TRUE);
294         } else {
295                 soup_message_body_append (action->msg->response_body,
296                                           SOUP_MEMORY_TAKE,
297                                           action->response_str->str,
298                                           action->response_str->len);
299                 g_string_free (action->response_str, FALSE);
300         }
301
302         soup_message_headers_append (action->msg->response_headers, "Ext", "");
303
304         /* Server header on response */
305         soup_message_headers_append
306                         (action->msg->response_headers,
307                          "Server",
308                          gssdp_client_get_server_id
309                                 (GSSDP_CLIENT (action->context)));
310
311         /* Tell soup server that response is now ready */
312         server = gupnp_context_get_server (action->context);
313         soup_server_unpause_message (server, action->msg);
314
315         /* Cleanup */
316         gupnp_service_action_unref (action);
317 }
318
319 /**
320  * gupnp_service_action_get_name:
321  * @action: A #GUPnPServiceAction
322  *
323  * Get the name of @action.
324  *
325  * Return value: The name of @action
326  **/
327 const char *
328 gupnp_service_action_get_name (GUPnPServiceAction *action)
329 {
330         g_return_val_if_fail (action != NULL, NULL);
331
332         return action->name;
333 }
334
335 /**
336  * gupnp_service_action_get_locales:
337  * @action: A #GUPnPServiceAction
338  *
339  * Get an ordered (preferred first) #GList of locales preferred by
340  * the client. Free list and elements after use.
341  *
342  * Return value: (element-type utf8) (transfer full): A #GList of #char*
343  * locale names.
344  **/
345 GList *
346 gupnp_service_action_get_locales (GUPnPServiceAction *action)
347 {
348         g_return_val_if_fail (action != NULL, NULL);
349
350         return http_request_get_accept_locales (action->msg);
351 }
352
353 /**
354  * gupnp_service_action_get:
355  * @action: A #GUPnPServiceAction
356  * @...: tuples of argument name, argument type, and argument value
357  * location, terminated with %NULL.
358  *
359  * Retrieves the specified action arguments.
360  **/
361 void
362 gupnp_service_action_get (GUPnPServiceAction *action,
363                           ...)
364 {
365         va_list var_args;
366
367         g_return_if_fail (action != NULL);
368
369         va_start (var_args, action);
370         gupnp_service_action_get_valist (action, var_args);
371         va_end (var_args);
372 }
373
374 /**
375  * gupnp_service_action_get_valist:
376  * @action: A #GUPnPServiceAction
377  * @var_args: va_list of tuples of argument name, argument type, and argument
378  * value location.
379  *
380  * See gupnp_service_action_get(); this version takes a va_list for
381  * use by language bindings.
382  **/
383 void
384 gupnp_service_action_get_valist (GUPnPServiceAction *action,
385                                  va_list             var_args)
386 {
387         const char *arg_name;
388         GType arg_type;
389         GValue value = {0, };
390         char *copy_error;
391
392         g_return_if_fail (action != NULL);
393
394         copy_error = NULL;
395
396         arg_name = va_arg (var_args, const char *);
397         while (arg_name) {
398                 arg_type = va_arg (var_args, GType);
399                 g_value_init (&value, arg_type);
400
401                 gupnp_service_action_get_value (action, arg_name, &value);
402
403                 G_VALUE_LCOPY (&value, var_args, 0, &copy_error);
404
405                 g_value_unset (&value);
406
407                 if (copy_error) {
408                         g_warning ("Error lcopying value: %s\n", copy_error);
409
410                         g_free (copy_error);
411                 }
412
413                 arg_name = va_arg (var_args, const char *);
414         }
415 }
416
417 /**
418  * gupnp_service_action_get_values:
419  * @action: A #GUPnPServiceAction
420  * @arg_names: (element-type utf8) : A #GList of argument names as string
421  * @arg_types: (element-type GType): A #GList of argument types as #GType
422  *
423  * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
424  *
425  * Return value: (element-type GValue) (transfer full): The values as #GList of
426  * #GValue. g_list_free() the returned list and g_value_unset() and g_slice_free()
427  * each element.
428  *
429  * Since: 0.13.3
430  **/
431 GList *
432 gupnp_service_action_get_values (GUPnPServiceAction *action,
433                                  GList              *arg_names,
434                                  GList              *arg_types)
435 {
436         GList *arg_values;
437         guint i;
438
439         g_return_val_if_fail (action != NULL, NULL);
440
441         arg_values = NULL;
442
443         for (i = 0; i < g_list_length (arg_names); i++) {
444                 const char *arg_name;
445                 GType arg_type;
446                 GValue *arg_value;
447
448                 arg_name = (const char *) g_list_nth_data (arg_names, i);
449                 arg_type = (GType) g_list_nth_data (arg_types, i);
450
451                 arg_value = g_slice_new0 (GValue);
452                 g_value_init (arg_value, arg_type);
453
454                 gupnp_service_action_get_value (action, arg_name, arg_value);
455
456                 arg_values = g_list_append (arg_values, arg_value);
457         }
458
459         return arg_values;
460 }
461
462 /**
463  * gupnp_service_action_get_value: (skip)
464  * @action: A #GUPnPServiceAction
465  * @argument: The name of the argument to retrieve
466  * @value: (inout):The #GValue to store the value of the argument, initialized
467  * to the correct type.
468  *
469  * Retrieves the value of @argument into @value.
470  **/
471 void
472 gupnp_service_action_get_value (GUPnPServiceAction *action,
473                                 const char         *argument,
474                                 GValue             *value)
475 {
476         xmlNode *node;
477         gboolean found;
478
479         g_return_if_fail (action != NULL);
480         g_return_if_fail (argument != NULL);
481         g_return_if_fail (value != NULL);
482
483         found = FALSE;
484         for (node = action->node->children; node; node = node->next) {
485                 if (strcmp ((char *) node->name, argument) != 0)
486                         continue;
487
488                 found = gvalue_util_set_value_from_xml_node (value, node);
489
490                 break;
491         }
492
493         if (!found)
494                 g_warning ("Failed to retrieve '%s' argument of '%s' action",
495                            argument,
496                            action->name);
497 }
498
499 /**
500  * gupnp_service_action_get_gvalue: (rename-to gupnp_service_action_get_value)
501  * @action: A #GUPnPServiceAction
502  * @argument: The name of the argument to retrieve
503  * @type: The type of argument to retrieve
504  *
505  * Retrieves the value of @argument into a GValue of type @type and returns it.
506  * The method exists only and only to satify PyGI, please use
507  * gupnp_service_action_get_value() and ignore this if possible.
508  *
509  * Return value: (transfer full): Value as #GValue associated with @action.
510  * g_value_unset() and g_slice_free() it after usage.
511  *
512  * Since: 0.13.3
513  **/
514 GValue *
515 gupnp_service_action_get_gvalue (GUPnPServiceAction *action,
516                                        const char         *argument,
517                                        GType               type)
518 {
519         GValue *val;
520
521         val = g_slice_new0 (GValue);
522         g_value_init (val, type);
523
524         gupnp_service_action_get_value (action, argument, val);
525
526         return val;
527 }
528
529 /**
530  * gupnp_service_action_get_argument_count:
531  * @action: A #GUPnPServiceAction
532  *
533  * Get the number of IN arguments from the @action and return it.
534  *
535  * Return value: The number of IN arguments from the @action.
536  *
537  * Since: 0.17.0
538  */
539 guint
540 gupnp_service_action_get_argument_count (GUPnPServiceAction *action)
541 {
542     return action->argument_count;
543 }
544
545 /**
546  * gupnp_service_action_set:
547  * @action: A #GUPnPServiceAction
548  * @...: tuples of return value name, return value type, and
549  * actual return value, terminated with %NULL.
550  *
551  * Sets the specified action return values.
552  **/
553 void
554 gupnp_service_action_set (GUPnPServiceAction *action,
555                           ...)
556 {
557         va_list var_args;
558
559         g_return_if_fail (action != NULL);
560
561         va_start (var_args, action);
562         gupnp_service_action_set_valist (action, var_args);
563         va_end (var_args);
564 }
565
566 /**
567  * gupnp_service_action_set_valist:
568  * @action: A #GUPnPServiceAction
569  * @var_args: va_list of tuples of return value name, return value type, and
570  * actual return value.
571  *
572  * See gupnp_service_action_set(); this version takes a va_list for
573  * use by language bindings.
574  **/
575 void
576 gupnp_service_action_set_valist (GUPnPServiceAction *action,
577                                  va_list             var_args)
578 {
579         const char *arg_name;
580         GType arg_type;
581         GValue value = {0, };
582         char *collect_error;
583
584         g_return_if_fail (action != NULL);
585
586         collect_error = NULL;
587
588         arg_name = va_arg (var_args, const char *);
589         while (arg_name) {
590                 arg_type = va_arg (var_args, GType);
591                 g_value_init (&value, arg_type);
592
593                 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
594                                  &collect_error);
595                 if (!collect_error) {
596                         gupnp_service_action_set_value (action,
597                                                         arg_name, &value);
598
599                         g_value_unset (&value);
600
601                 } else {
602                         g_warning ("Error collecting value: %s\n",
603                                    collect_error);
604
605                         g_free (collect_error);
606                 }
607
608                 arg_name = va_arg (var_args, const char *);
609         }
610 }
611
612 /**
613  * gupnp_service_action_set_values:
614  * @action: A #GUPnPServiceAction
615  * @arg_names: (element-type utf8) (transfer none): A #GList of argument names
616  * @arg_values: (element-type GValue) (transfer none): The #GList of values (as
617  * #GValues) that line up with @arg_names.
618  *
619  * Sets the specified action return values.
620  *
621  * Since: 0.13.3
622  **/
623 void
624 gupnp_service_action_set_values (GUPnPServiceAction *action,
625                                  GList              *arg_names,
626                                  GList              *arg_values)
627 {
628         g_return_if_fail (action != NULL);
629         g_return_if_fail (arg_names != NULL);
630         g_return_if_fail (arg_values != NULL);
631         g_return_if_fail (g_list_length (arg_names) ==
632                           g_list_length (arg_values));
633
634         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
635                 g_warning ("Calling gupnp_service_action_set_value() after "
636                            "having called gupnp_service_action_return_error() "
637                            "is not allowed.");
638
639                 return;
640         }
641
642         /* Append to response */
643         for (; arg_names; arg_names = arg_names->next) {
644                 const char *arg_name;
645                 GValue *value;
646
647                 arg_name = arg_names->data;
648                 value = arg_values->data;
649
650                 xml_util_start_element (action->response_str, arg_name);
651                 gvalue_util_value_append_to_xml_string (value,
652                                                         action->response_str);
653                 xml_util_end_element (action->response_str, arg_name);
654
655                 arg_values = arg_values->next;
656         }
657 }
658
659 /**
660  * gupnp_service_action_set_value:
661  * @action: A #GUPnPServiceAction
662  * @argument: The name of the return value to retrieve
663  * @value: The #GValue to store the return value
664  *
665  * Sets the value of @argument to @value.
666  **/
667 void
668 gupnp_service_action_set_value (GUPnPServiceAction *action,
669                                 const char         *argument,
670                                 const GValue       *value)
671 {
672         g_return_if_fail (action != NULL);
673         g_return_if_fail (argument != NULL);
674         g_return_if_fail (value != NULL);
675
676         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
677                 g_warning ("Calling gupnp_service_action_set_value() after "
678                            "having called gupnp_service_action_return_error() "
679                            "is not allowed.");
680
681                 return;
682         }
683
684         /* Append to response */
685         xml_util_start_element (action->response_str, argument);
686         gvalue_util_value_append_to_xml_string (value, action->response_str);
687         xml_util_end_element (action->response_str, argument);
688 }
689
690 /**
691  * gupnp_service_action_return:
692  * @action: A #GUPnPServiceAction
693  *
694  * Return succesfully.
695  **/
696 void
697 gupnp_service_action_return (GUPnPServiceAction *action)
698 {
699         g_return_if_fail (action != NULL);
700
701         soup_message_set_status (action->msg, SOUP_STATUS_OK);
702
703         finalize_action (action);
704 }
705
706 /**
707  * gupnp_service_action_return_error:
708  * @action: A #GUPnPServiceAction
709  * @error_code: The error code
710  * @error_description: The error description, or %NULL if @error_code is
711  * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
712  * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
713  * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
714  * provided automatically.
715  *
716  * Return @error_code.
717  **/
718 void
719 gupnp_service_action_return_error (GUPnPServiceAction *action,
720                                    guint               error_code,
721                                    const char         *error_description)
722 {
723         g_return_if_fail (action != NULL);
724
725         switch (error_code) {
726         case GUPNP_CONTROL_ERROR_INVALID_ACTION:
727                 if (error_description == NULL)
728                         error_description = "Invalid Action";
729
730                 break;
731         case GUPNP_CONTROL_ERROR_INVALID_ARGS:
732                 if (error_description == NULL)
733                         error_description = "Invalid Args";
734
735                 break;
736         case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
737                 if (error_description == NULL)
738                         error_description = "Out of Sync";
739
740                 break;
741         case GUPNP_CONTROL_ERROR_ACTION_FAILED:
742                 if (error_description == NULL)
743                         error_description = "Action Failed";
744
745                 break;
746         default:
747                 g_return_if_fail (error_description != NULL);
748                 break;
749         }
750
751         /* Replace response_str with a SOAP Fault */
752         g_string_erase (action->response_str, 0, -1);
753
754         xml_util_start_element (action->response_str, "s:Fault");
755
756         xml_util_start_element (action->response_str, "faultcode");
757         g_string_append (action->response_str, "s:Client");
758         xml_util_end_element (action->response_str, "faultcode");
759
760         xml_util_start_element (action->response_str, "faultstring");
761         g_string_append (action->response_str, "UPnPError");
762         xml_util_end_element (action->response_str, "faultstring");
763
764         xml_util_start_element (action->response_str, "detail");
765
766         xml_util_start_element (action->response_str,
767                                 "UPnPError "
768                                 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
769
770         xml_util_start_element (action->response_str, "errorCode");
771         g_string_append_printf (action->response_str, "%u", error_code);
772         xml_util_end_element (action->response_str, "errorCode");
773
774         xml_util_start_element (action->response_str, "errorDescription");
775         xml_util_add_content (action->response_str, error_description);
776         xml_util_end_element (action->response_str, "errorDescription");
777
778         xml_util_end_element (action->response_str, "UPnPError");
779         xml_util_end_element (action->response_str, "detail");
780
781         xml_util_end_element (action->response_str, "s:Fault");
782
783         soup_message_set_status (action->msg,
784                                  SOUP_STATUS_INTERNAL_SERVER_ERROR);
785
786         finalize_action (action);
787 }
788
789 /**
790  * gupnp_service_action_get_message:
791  * @action: A #GUPnPServiceAction
792  *
793  * Get the #SoupMessage associated with @action. Mainly intended for
794  * applications to be able to read HTTP headers received from clients.
795  *
796  * Return value: (transfer full): #SoupMessage associated with @action. Unref
797  * after using it.
798  *
799  * Since: 0.13.0
800  **/
801 SoupMessage *
802 gupnp_service_action_get_message (GUPnPServiceAction *action)
803 {
804         return g_object_ref (action->msg);
805 }
806
807 static void
808 gupnp_service_init (GUPnPService *service)
809 {
810         service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
811                                                      GUPNP_TYPE_SERVICE,
812                                                      GUPnPServicePrivate);
813
814         service->priv->subscriptions =
815                 g_hash_table_new_full (g_str_hash,
816                                        g_str_equal,
817                                        NULL,
818                                        (GDestroyNotify) subscription_data_free);
819
820         service->priv->notify_queue = g_queue_new ();
821 }
822
823 /* Generate a new action response node for @action_name */
824 static GString *
825 new_action_response_str (const char   *action_name,
826                          const char   *service_type)
827 {
828         GString *str;
829
830         str = xml_util_new_string ();
831
832         g_string_append (str, "<u:");
833         g_string_append (str, action_name);
834         g_string_append (str, "Response xmlns:u=");
835
836         if (service_type != NULL) {
837                 g_string_append (str, service_type);
838                 g_string_append_c (str, '"');
839         } else {
840                 g_warning ("No serviceType defined. Control may not work "
841                            "correctly.");
842         }
843
844         g_string_append_c (str, '>');
845
846         return str;
847 }
848
849 /* Handle QueryStateVariable action */
850 static void
851 query_state_variable (GUPnPService       *service,
852                       GUPnPServiceAction *action)
853 {
854         xmlNode *node;
855
856         /* Iterate requested variables */
857         for (node = action->node->children; node; node = node->next) {
858                 xmlChar *var_name;
859                 GValue value = {0,};
860
861                 if (strcmp ((char *) node->name, "varName") != 0)
862                         continue;
863
864                 /* varName */
865                 var_name = xmlNodeGetContent (node);
866                 if (!var_name) {
867                         gupnp_service_action_return_error (action,
868                                                            402,
869                                                            "Invalid Args");
870
871                         return;
872                 }
873
874                 /* Query variable */
875                 g_signal_emit (service,
876                                signals[QUERY_VARIABLE],
877                                g_quark_from_string ((char *) var_name),
878                                (char *) var_name,
879                                &value);
880
881                 if (!G_IS_VALUE (&value)) {
882                         gupnp_service_action_return_error (action,
883                                                            402,
884                                                            "Invalid Args");
885
886                         xmlFree (var_name);
887
888                         return;
889                 }
890
891                 /* Add variable to response */
892                 gupnp_service_action_set_value (action,
893                                                 (char *) var_name,
894                                                 &value);
895
896                 /* Cleanup */
897                 g_value_unset (&value);
898
899                 xmlFree (var_name);
900         }
901
902         gupnp_service_action_return (action);
903 }
904
905 /* controlURL handler */
906 static void
907 control_server_handler (SoupServer                      *server,
908                         SoupMessage                     *msg,
909                         G_GNUC_UNUSED const char        *server_path,
910                         G_GNUC_UNUSED GHashTable        *query,
911                         G_GNUC_UNUSED SoupClientContext *soup_client,
912                         gpointer                         user_data)
913 {
914         GUPnPService *service;
915         GUPnPContext *context;
916         xmlDoc *doc;
917         xmlNode *action_node, *node;
918         const char *soap_action;
919         const char *accept_encoding;
920         char *action_name;
921         char *end;
922         GUPnPServiceAction *action;
923
924         service = GUPNP_SERVICE (user_data);
925
926         if (msg->method != SOUP_METHOD_POST) {
927                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
928
929                 return;
930         }
931
932         if (msg->request_body->length == 0) {
933                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
934
935                 return;
936         }
937
938         /* DLNA 7.2.5.6: Always use HTTP 1.1 */
939         if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
940                 soup_message_set_http_version (msg, SOUP_HTTP_1_1);
941                 soup_message_headers_append (msg->response_headers,
942                                              "Connection",
943                                              "close");
944         }
945
946         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
947
948         /* Get action name */
949         soap_action = soup_message_headers_get_one (msg->request_headers,
950                                                     "SOAPAction");
951         if (!soap_action) {
952                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
953                 return;
954         }
955
956         action_name = strchr (soap_action, '#');
957         if (!action_name) {
958                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
959
960                 return;
961         }
962
963         /* This memory is libsoup-owned so we can do this */
964         *action_name = '\0';
965         action_name += 1;
966
967         /* This memory is libsoup-owned so we can do this */
968         end = strrchr (action_name, '"');
969         if (end)
970                 *end = '\0';
971
972         /* Parse action_node */
973         doc = xmlRecoverMemory (msg->request_body->data,
974                                 msg->request_body->length);
975         if (doc == NULL) {
976                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
977
978                 return;
979         }
980
981         action_node = xml_util_get_element ((xmlNode *) doc,
982                                             "Envelope",
983                                             "Body",
984                                             action_name,
985                                             NULL);
986         if (!action_node) {
987                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
988
989                 return;
990         }
991
992         /* Create action structure */
993         action = g_slice_new0 (GUPnPServiceAction);
994
995         action->ref_count      = 1;
996         action->name           = g_strdup (action_name);
997         action->msg            = g_object_ref (msg);
998         action->doc            = gupnp_xml_doc_new(doc);
999         action->node           = action_node;
1000         action->response_str   = new_action_response_str (action_name,
1001                                                         soap_action);
1002         action->context        = g_object_ref (context);
1003         action->argument_count = 0;
1004
1005         for (node = action->node->children; node; node = node->next)
1006                 if (node->type == XML_ELEMENT_NODE)
1007                         action->argument_count++;
1008
1009         /* Get accepted encodings */
1010         accept_encoding = soup_message_headers_get_list (msg->request_headers,
1011                                                          "Accept-Encoding");
1012
1013         if (accept_encoding) {
1014                 GSList *codings;
1015
1016                 codings = soup_header_parse_quality_list (accept_encoding,
1017                                                           NULL);
1018                 if (codings &&
1019                     g_slist_find_custom (codings, "gzip",
1020                                          (GCompareFunc) g_ascii_strcasecmp)) {
1021                        action->accept_gzip = TRUE;
1022                 }
1023
1024                 soup_header_free_list (codings);
1025         }
1026
1027         /* Tell soup server that response is not ready yet */
1028         soup_server_pause_message (server, msg);
1029
1030         /* QueryStateVariable? */
1031         if (strcmp (action_name, "QueryStateVariable") == 0)
1032                 query_state_variable (service, action);
1033         else {
1034                 GQuark action_name_quark;
1035
1036                 action_name_quark = g_quark_from_string (action_name);
1037                 if (g_signal_has_handler_pending (service,
1038                                                   signals[ACTION_INVOKED],
1039                                                   action_name_quark,
1040                                                   FALSE)) {
1041                         /* Emit signal. Handler parses request and fills in
1042                          * response. */
1043                         g_signal_emit (service,
1044                                        signals[ACTION_INVOKED],
1045                                        action_name_quark,
1046                                        action);
1047                 } else {
1048                         /* No handlers attached. */
1049                         gupnp_service_action_return_error (action,
1050                                                            401,
1051                                                            "Invalid Action");
1052                 }
1053         }
1054 }
1055
1056 /* Generates a standard (re)subscription response */
1057 static void
1058 subscription_response (GUPnPService *service,
1059                        SoupMessage  *msg,
1060                        const char   *sid,
1061                        int           timeout)
1062 {
1063         GUPnPContext *context;
1064         GSSDPClient *client;
1065         char *tmp;
1066
1067         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1068         client = GSSDP_CLIENT (context);
1069
1070         /* Server header on response */
1071         soup_message_headers_append (msg->response_headers,
1072                                      "Server",
1073                                      gssdp_client_get_server_id (client));
1074
1075         /* SID header */
1076         soup_message_headers_append (msg->response_headers,
1077                                      "SID",
1078                                      sid);
1079
1080         /* Timeout header */
1081         if (timeout > 0)
1082                 tmp = g_strdup_printf ("Second-%d", timeout);
1083         else
1084                 tmp = g_strdup ("infinite");
1085
1086         soup_message_headers_append (msg->response_headers,
1087                                      "Timeout",
1088                                      tmp);
1089         g_free (tmp);
1090
1091         /* 200 OK */
1092         soup_message_set_status (msg, SOUP_STATUS_OK);
1093 }
1094
1095 /* Generates a new SID */
1096 static char *
1097 generate_sid (void)
1098 {
1099 #ifdef G_OS_WIN32
1100         char *ret = NULL;
1101         UUID uuid;
1102         RPC_STATUS stat;
1103         stat = UuidCreate (&uuid);
1104         if (stat == RPC_S_OK) {
1105                 unsigned char* uuidStr = NULL;
1106                 stat = UuidToString (&uuid, &uuidStr);
1107                 if (stat == RPC_S_OK) {
1108                         ret = g_strdup_printf ("uuid:%s", uuidStr);
1109                         RpcStringFree (&uuidStr);
1110                 }
1111         }
1112
1113         return ret;
1114 #else
1115         uuid_t id;
1116         char out[39];
1117
1118         uuid_generate (id);
1119         uuid_unparse (id, out);
1120
1121         return g_strdup_printf ("uuid:%s", out);
1122 #endif
1123 }
1124
1125 /* Subscription expired */
1126 static gboolean
1127 subscription_timeout (gpointer user_data)
1128 {
1129         SubscriptionData *data;
1130
1131         data = user_data;
1132
1133         g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1134
1135         return FALSE;
1136 }
1137
1138 static void
1139 send_initial_state (SubscriptionData *data)
1140 {
1141         GQueue *queue;
1142         char *mem;
1143         GList *l;
1144
1145         /* Send initial event message */
1146         queue = g_queue_new ();
1147
1148         for (l = data->service->priv->state_variables; l; l = l->next) {
1149                 NotifyData *ndata;
1150
1151                 ndata = g_slice_new0 (NotifyData);
1152
1153                 g_signal_emit (data->service,
1154                                signals[QUERY_VARIABLE],
1155                                g_quark_from_string (l->data),
1156                                l->data,
1157                                &ndata->value);
1158
1159                 if (!G_IS_VALUE (&ndata->value)) {
1160                         g_slice_free (NotifyData, ndata);
1161
1162                         continue;
1163                 }
1164
1165                 ndata->variable = g_strdup (l->data);
1166
1167                 g_queue_push_tail (queue, ndata);
1168         }
1169
1170         mem = create_property_set (queue);
1171         notify_subscriber (data->sid, data, mem);
1172
1173         /* Cleanup */
1174         g_queue_free (queue);
1175
1176         g_free (mem);
1177 }
1178
1179
1180 /* Subscription request */
1181 static void
1182 subscribe (GUPnPService *service,
1183            SoupMessage  *msg,
1184            const char   *callback)
1185 {
1186         SubscriptionData *data;
1187         char *start, *end, *uri;
1188
1189         data = g_slice_new0 (SubscriptionData);
1190
1191         /* Parse callback list */
1192         start = (char *) callback;
1193         while ((start = strchr (start, '<'))) {
1194                 start += 1;
1195                 if (!start || !*start)
1196                         break;
1197
1198                 end = strchr (start, '>');
1199                 if (!end || !*end)
1200                         break;
1201
1202                 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1203                         uri = g_strndup (start, end - start);
1204                         data->callbacks = g_list_append (data->callbacks, uri);
1205                 }
1206
1207                 start = end;
1208         }
1209
1210         if (!data->callbacks) {
1211                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1212
1213                 g_slice_free (SubscriptionData, data);
1214
1215                 return;
1216         }
1217
1218         /* Add service and SID */
1219         data->service = service;
1220         data->sid     = generate_sid ();
1221
1222         /* Add timeout */
1223         data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1224         g_source_set_callback (data->timeout_src,
1225                                subscription_timeout,
1226                                data,
1227                                NULL);
1228
1229         g_source_attach (data->timeout_src,
1230                          g_main_context_get_thread_default ());
1231
1232         g_source_unref (data->timeout_src);
1233
1234         /* Add to hash */
1235         g_hash_table_insert (service->priv->subscriptions,
1236                              data->sid,
1237                              data);
1238
1239         /* Respond */
1240         subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1241
1242         send_initial_state (data);
1243 }
1244
1245 /* Resubscription request */
1246 static void
1247 resubscribe (GUPnPService *service,
1248              SoupMessage  *msg,
1249              const char   *sid)
1250 {
1251         SubscriptionData *data;
1252
1253         data = g_hash_table_lookup (service->priv->subscriptions, sid);
1254         if (!data) {
1255                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1256
1257                 return;
1258         }
1259
1260         /* Update timeout */
1261         if (data->timeout_src) {
1262                 g_source_destroy (data->timeout_src);
1263                 data->timeout_src = NULL;
1264         }
1265
1266         data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1267         g_source_set_callback (data->timeout_src,
1268                                subscription_timeout,
1269                                data,
1270                                NULL);
1271
1272         g_source_attach (data->timeout_src,
1273                          g_main_context_get_thread_default ());
1274
1275         g_source_unref (data->timeout_src);
1276
1277         /* Respond */
1278         subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1279 }
1280
1281 /* Unsubscription request */
1282 static void
1283 unsubscribe (GUPnPService *service,
1284              SoupMessage  *msg,
1285              const char   *sid)
1286 {
1287         SubscriptionData *data;
1288
1289         data = g_hash_table_lookup (service->priv->subscriptions, sid);
1290         if (data) {
1291                 if (data->initial_state_sent)
1292                         g_hash_table_remove (service->priv->subscriptions,
1293                                              sid);
1294                 else
1295                         data->to_delete = TRUE;
1296                 soup_message_set_status (msg, SOUP_STATUS_OK);
1297         } else
1298                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1299 }
1300
1301 /* eventSubscriptionURL handler */
1302 static void
1303 subscription_server_handler (G_GNUC_UNUSED SoupServer        *server,
1304                              SoupMessage                     *msg,
1305                              G_GNUC_UNUSED const char        *server_path,
1306                              G_GNUC_UNUSED GHashTable        *query,
1307                              G_GNUC_UNUSED SoupClientContext *soup_client,
1308                              gpointer                         user_data)
1309 {
1310         GUPnPService *service;
1311         GUPnPContext *context;
1312         GUPnPAcl *acl;
1313         const char *callback, *nt, *sid;
1314
1315         service = GUPNP_SERVICE (user_data);
1316
1317         callback = soup_message_headers_get_one (msg->request_headers,
1318                                                  "Callback");
1319         nt       = soup_message_headers_get_one (msg->request_headers, "NT");
1320         sid      = soup_message_headers_get_one (msg->request_headers, "SID");
1321
1322         /* Choose appropriate handler */
1323         if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1324                 if (callback) {
1325                         if (sid) {
1326                                 soup_message_set_status
1327                                         (msg, SOUP_STATUS_BAD_REQUEST);
1328
1329                         } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1330                                 soup_message_set_status
1331                                         (msg, SOUP_STATUS_PRECONDITION_FAILED);
1332
1333                         } else {
1334                                 subscribe (service, msg, callback);
1335
1336                         }
1337
1338                 } else if (sid) {
1339                         if (nt) {
1340                                 soup_message_set_status
1341                                         (msg, SOUP_STATUS_BAD_REQUEST);
1342
1343                         } else {
1344                                 resubscribe (service, msg, sid);
1345
1346                         }
1347
1348                 } else {
1349                         soup_message_set_status
1350                                 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1351
1352                 }
1353
1354         } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1355                 if (sid) {
1356                         if (nt || callback) {
1357                                 soup_message_set_status
1358                                         (msg, SOUP_STATUS_BAD_REQUEST);
1359
1360                         } else {
1361                                 unsubscribe (service, msg, sid);
1362
1363                         }
1364
1365                 } else {
1366                         soup_message_set_status
1367                                 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1368
1369                 }
1370
1371         } else {
1372                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1373
1374         }
1375 }
1376
1377 static void
1378 got_introspection (GUPnPServiceInfo          *info,
1379                    GUPnPServiceIntrospection *introspection,
1380                    const GError              *error,
1381                    G_GNUC_UNUSED gpointer     user_data)
1382 {
1383         GUPnPService *service = GUPNP_SERVICE (info);
1384         const GList *state_variables, *l;
1385         GHashTableIter iter;
1386         gpointer data;
1387
1388         if (introspection) {
1389                 state_variables =
1390                         gupnp_service_introspection_list_state_variables
1391                                 (introspection);
1392
1393                 for (l = state_variables; l; l = l->next) {
1394                         GUPnPServiceStateVariableInfo *variable;
1395
1396                         variable = l->data;
1397
1398                         if (!variable->send_events)
1399                                 continue;
1400
1401                         service->priv->state_variables =
1402                                 g_list_prepend (service->priv->state_variables,
1403                                                 g_strdup (variable->name));
1404                 }
1405
1406                 g_object_unref (introspection);
1407         } else
1408                 g_warning ("Failed to get SCPD: %s\n"
1409                            "The initial event message will not be sent.",
1410                            error ? error->message : "No error");
1411
1412         g_hash_table_iter_init (&iter, service->priv->subscriptions);
1413
1414         while (g_hash_table_iter_next (&iter, NULL, &data)) {
1415                 send_initial_state ((SubscriptionData *) data);
1416                 if (subscription_data_can_delete ((SubscriptionData *) data))
1417                         g_hash_table_iter_remove (&iter);
1418         }
1419 }
1420
1421 static char *
1422 path_from_url (const char *url)
1423 {
1424         SoupURI *uri;
1425         gchar   *path;
1426
1427         uri = soup_uri_new (url);
1428         path = soup_uri_to_string (uri, TRUE);
1429         soup_uri_free (uri);
1430
1431         return path;
1432 }
1433
1434 static GObject *
1435 gupnp_service_constructor (GType                  type,
1436                            guint                  n_construct_params,
1437                            GObjectConstructParam *construct_params)
1438 {
1439         GObjectClass *object_class;
1440         GObject *object;
1441         GUPnPServiceInfo *info;
1442         GUPnPContext *context;
1443         AclServerHandler *handler;
1444         char *url;
1445         char *path;
1446
1447         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1448
1449         /* Construct */
1450         object = object_class->constructor (type,
1451                                             n_construct_params,
1452                                             construct_params);
1453
1454         info = GUPNP_SERVICE_INFO (object);
1455
1456         /* Get introspection and save state variable names */
1457         gupnp_service_info_get_introspection_async (info,
1458                                                     got_introspection,
1459                                                     NULL);
1460
1461         /* Get server */
1462         context = gupnp_service_info_get_context (info);
1463
1464         /* Run listener on controlURL */
1465         url = gupnp_service_info_get_control_url (info);
1466         path = path_from_url (url);
1467         handler = acl_server_handler_new (GUPNP_SERVICE (object),
1468                                           context,
1469                                           control_server_handler,
1470                                           g_object_ref (object),
1471                                           g_object_unref);
1472         _gupnp_context_add_server_handler_with_data (context, path, handler);
1473         g_free (path);
1474         g_free (url);
1475
1476         /* Run listener on eventSubscriptionURL */
1477         url = gupnp_service_info_get_event_subscription_url (info);
1478         path = path_from_url (url);
1479         handler = acl_server_handler_new (GUPNP_SERVICE (object),
1480                                           context,
1481                                           subscription_server_handler,
1482                                           g_object_ref (object),
1483                                           g_object_unref);
1484         _gupnp_context_add_server_handler_with_data (context, path, handler);
1485         g_free (path);
1486         g_free (url);
1487
1488         return object;
1489 }
1490
1491 /* Root device availability changed. */
1492 static void
1493 notify_available_cb (GObject                  *object,
1494                      G_GNUC_UNUSED GParamSpec *pspec,
1495                      gpointer                  user_data)
1496 {
1497         GUPnPService *service;
1498
1499         service = GUPNP_SERVICE (user_data);
1500
1501         if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1502                 /* Root device now unavailable: Purge subscriptions */
1503                 g_hash_table_remove_all (service->priv->subscriptions);
1504         }
1505 }
1506
1507 static void
1508 gupnp_service_set_property (GObject      *object,
1509                             guint         property_id,
1510                             const GValue *value,
1511                             GParamSpec   *pspec)
1512 {
1513         GUPnPService *service;
1514
1515         service = GUPNP_SERVICE (object);
1516
1517         switch (property_id) {
1518         case PROP_ROOT_DEVICE: {
1519                 GUPnPRootDevice **dev;
1520
1521                 service->priv->root_device = g_value_get_object (value);
1522                 dev = &(service->priv->root_device);
1523
1524                 g_object_add_weak_pointer
1525                         (G_OBJECT (service->priv->root_device),
1526                          (gpointer *) dev);
1527
1528                 service->priv->notify_available_id =
1529                         g_signal_connect_object (service->priv->root_device,
1530                                                  "notify::available",
1531                                                  G_CALLBACK
1532                                                         (notify_available_cb),
1533                                                  object,
1534                                                  0);
1535
1536                 break;
1537         }
1538         default:
1539                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1540                 break;
1541         }
1542 }
1543
1544 static void
1545 gupnp_service_get_property (GObject    *object,
1546                             guint       property_id,
1547                             GValue     *value,
1548                             GParamSpec *pspec)
1549 {
1550         GUPnPService *service;
1551
1552         service = GUPNP_SERVICE (object);
1553
1554         switch (property_id) {
1555         case PROP_ROOT_DEVICE:
1556                 g_value_set_object (value, service->priv->root_device);
1557                 break;
1558         default:
1559                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1560                 break;
1561         }
1562 }
1563
1564 static void
1565 gupnp_service_dispose (GObject *object)
1566 {
1567         GUPnPService *service;
1568         GObjectClass *object_class;
1569         GUPnPServiceInfo *info;
1570         GUPnPContext *context;
1571         char *url;
1572         char *path;
1573
1574         service = GUPNP_SERVICE (object);
1575
1576         /* Get server */
1577         info = GUPNP_SERVICE_INFO (service);
1578         context = gupnp_service_info_get_context (info);
1579
1580         /* Remove listener on controlURL */
1581         url = gupnp_service_info_get_control_url (info);
1582         path = path_from_url (url);
1583         gupnp_context_remove_server_handler (context, path);
1584         g_free (path);
1585         g_free (url);
1586
1587         /* Remove listener on eventSubscriptionURL */
1588         url = gupnp_service_info_get_event_subscription_url (info);
1589         path = path_from_url (url);
1590         gupnp_context_remove_server_handler (context, path);
1591         g_free (path);
1592         g_free (url);
1593
1594         if (service->priv->root_device) {
1595                 GUPnPRootDevice **dev = &(service->priv->root_device);
1596
1597                 if (g_signal_handler_is_connected
1598                         (service->priv->root_device,
1599                          service->priv->notify_available_id)) {
1600                         g_signal_handler_disconnect
1601                                 (service->priv->root_device,
1602                                  service->priv->notify_available_id);
1603                 }
1604
1605                 g_object_remove_weak_pointer
1606                         (G_OBJECT (service->priv->root_device),
1607                          (gpointer *) dev);
1608
1609                 service->priv->root_device = NULL;
1610         }
1611
1612         /* Cancel pending messages */
1613         g_hash_table_remove_all (service->priv->subscriptions);
1614
1615         /* Call super */
1616         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1617         object_class->dispose (object);
1618 }
1619
1620 static void
1621 gupnp_service_finalize (GObject *object)
1622 {
1623         GUPnPService *service;
1624         GObjectClass *object_class;
1625         NotifyData *data;
1626
1627         service = GUPNP_SERVICE (object);
1628
1629         /* Free subscription hash */
1630         g_hash_table_destroy (service->priv->subscriptions);
1631
1632         /* Free state variable list */
1633         g_list_free_full (service->priv->state_variables, g_free);
1634
1635         /* Free notify queue */
1636         while ((data = g_queue_pop_head (service->priv->notify_queue)))
1637                 notify_data_free (data);
1638
1639         g_queue_free (service->priv->notify_queue);
1640
1641         if (service->priv->session) {
1642                 g_object_unref (service->priv->session);
1643                 service->priv->session = NULL;
1644         }
1645
1646         /* Call super */
1647         object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1648         object_class->finalize (object);
1649 }
1650
1651 static void
1652 gupnp_service_class_init (GUPnPServiceClass *klass)
1653 {
1654         GObjectClass *object_class;
1655
1656         object_class = G_OBJECT_CLASS (klass);
1657
1658         object_class->set_property = gupnp_service_set_property;
1659         object_class->get_property = gupnp_service_get_property;
1660         object_class->constructor  = gupnp_service_constructor;
1661         object_class->dispose      = gupnp_service_dispose;
1662         object_class->finalize     = gupnp_service_finalize;
1663
1664         g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1665
1666         /**
1667          * GUPnPService:root-device:
1668          *
1669          * The containing #GUPnPRootDevice.
1670          **/
1671         g_object_class_install_property
1672                 (object_class,
1673                  PROP_ROOT_DEVICE,
1674                  g_param_spec_object ("root-device",
1675                                       "Root device",
1676                                       "The GUPnPRootDevice",
1677                                       GUPNP_TYPE_ROOT_DEVICE,
1678                                       G_PARAM_READWRITE |
1679                                       G_PARAM_CONSTRUCT_ONLY |
1680                                       G_PARAM_STATIC_NAME |
1681                                       G_PARAM_STATIC_NICK |
1682                                       G_PARAM_STATIC_BLURB));
1683
1684         /**
1685          * GUPnPService::action-invoked:
1686          * @service: The #GUPnPService that received the signal
1687          * @action: The invoked #GUPnPServiceAction
1688          *
1689          * Emitted whenever an action is invoked. Handler should process
1690          * @action and must call either gupnp_service_action_return() or
1691          * gupnp_service_action_return_error().
1692          **/
1693         signals[ACTION_INVOKED] =
1694                 g_signal_new ("action-invoked",
1695                               GUPNP_TYPE_SERVICE,
1696                               G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1697                               G_STRUCT_OFFSET (GUPnPServiceClass,
1698                                                action_invoked),
1699                               NULL,
1700                               NULL,
1701                               g_cclosure_marshal_VOID__BOXED,
1702                               G_TYPE_NONE,
1703                               1,
1704                               GUPNP_TYPE_SERVICE_ACTION);
1705
1706         /**
1707          * GUPnPService::query-variable:
1708          * @service: The #GUPnPService that received the signal
1709          * @variable: The variable that is being queried
1710          * @value: (type GValue)(inout):The location of the #GValue of the variable
1711          *
1712          * Emitted whenever @service needs to know the value of @variable.
1713          * Handler should fill @value with the value of @variable.
1714          **/
1715         signals[QUERY_VARIABLE] =
1716                 g_signal_new ("query-variable",
1717                               GUPNP_TYPE_SERVICE,
1718                               G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1719                               G_STRUCT_OFFSET (GUPnPServiceClass,
1720                                                query_variable),
1721                               NULL,
1722                               NULL,
1723                               gupnp_marshal_VOID__STRING_POINTER,
1724                               G_TYPE_NONE,
1725                               2,
1726                               G_TYPE_STRING,
1727                               G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1728                                                 is an outward argument! */);
1729
1730         /**
1731          * GUPnPService::notify-failed:
1732          * @service: The #GUPnPService that received the signal
1733          * @callback_url: (type GList)(element-type SoupURI):A #GList of callback URLs
1734          * @reason: (type GError): A pointer to a #GError describing why the notify failed
1735          *
1736          * Emitted whenever notification of a client fails.
1737          **/
1738         signals[NOTIFY_FAILED] =
1739                 g_signal_new ("notify-failed",
1740                               GUPNP_TYPE_SERVICE,
1741                               G_SIGNAL_RUN_LAST,
1742                               G_STRUCT_OFFSET (GUPnPServiceClass,
1743                                                notify_failed),
1744                               NULL,
1745                               NULL,
1746                               gupnp_marshal_VOID__POINTER_POINTER,
1747                               G_TYPE_NONE,
1748                               2,
1749                               G_TYPE_POINTER,
1750                               G_TYPE_POINTER);
1751 }
1752
1753 /**
1754  * gupnp_service_notify:
1755  * @service: A #GUPnPService
1756  * @...: Tuples of variable name, variable type, and variable value,
1757  * terminated with %NULL.
1758  *
1759  * Notifies listening clients that the properties listed in @Varargs
1760  * have changed to the specified values.
1761  **/
1762 void
1763 gupnp_service_notify (GUPnPService *service,
1764                       ...)
1765 {
1766         va_list var_args;
1767
1768         g_return_if_fail (GUPNP_IS_SERVICE (service));
1769
1770         va_start (var_args, service);
1771         gupnp_service_notify_valist (service, var_args);
1772         va_end (var_args);
1773 }
1774
1775 /**
1776  * gupnp_service_notify_valist:
1777  * @service: A #GUPnPService
1778  * @var_args: A va_list of tuples of variable name, variable type, and variable
1779  * value, terminated with %NULL.
1780  *
1781  * See gupnp_service_notify(); this version takes a va_list for
1782  * use by language bindings.
1783  **/
1784 void
1785 gupnp_service_notify_valist (GUPnPService *service,
1786                              va_list       var_args)
1787 {
1788         const char *var_name;
1789         GType var_type;
1790         GValue value = {0, };
1791         char *collect_error;
1792
1793         g_return_if_fail (GUPNP_IS_SERVICE (service));
1794
1795         collect_error = NULL;
1796
1797         var_name = va_arg (var_args, const char *);
1798         while (var_name) {
1799                 var_type = va_arg (var_args, GType);
1800                 g_value_init (&value, var_type);
1801
1802                 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1803                                  &collect_error);
1804                 if (!collect_error) {
1805                         gupnp_service_notify_value (service, var_name, &value);
1806
1807                         g_value_unset (&value);
1808
1809                 } else {
1810                         g_warning ("Error collecting value: %s\n",
1811                                    collect_error);
1812
1813                         g_free (collect_error);
1814                 }
1815
1816                 var_name = va_arg (var_args, const char *);
1817         }
1818 }
1819
1820 /* Received notify response. */
1821 static void
1822 notify_got_response (G_GNUC_UNUSED SoupSession *session,
1823                      SoupMessage               *msg,
1824                      gpointer                   user_data)
1825 {
1826         SubscriptionData *data;
1827
1828         /* Cancelled? */
1829         if (msg->status_code == SOUP_STATUS_CANCELLED)
1830                 return;
1831
1832         data = user_data;
1833
1834         /* Remove from pending messages list */
1835         data->pending_messages = g_list_remove (data->pending_messages, msg);
1836
1837         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1838                 data->initial_state_sent = TRUE;
1839
1840                 /* Success: reset callbacks pointer */
1841                 data->callbacks = g_list_first (data->callbacks);
1842
1843         } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1844                 /* Precondition failed: Cancel subscription */
1845                 g_hash_table_remove (data->service->priv->subscriptions,
1846                                      data->sid);
1847
1848         } else {
1849                 /* Other failure: Try next callback or signal failure. */
1850                 if (data->callbacks->next) {
1851                         SoupBuffer *buffer;
1852                         guint8 *property_set;
1853                         gsize length;
1854
1855                         /* Call next callback */
1856                         data->callbacks = data->callbacks->next;
1857
1858                         /* Get property-set from old message */
1859                         buffer = soup_message_body_flatten (msg->request_body);
1860                         soup_buffer_get_data (buffer,
1861                                               (const guint8 **) &property_set,
1862                                               &length);
1863                         notify_subscriber (NULL, data, property_set);
1864                         soup_buffer_free (buffer);
1865                 } else {
1866                         /* Emit 'notify-failed' signal */
1867                         GError *error;
1868
1869                         error = g_error_new_literal
1870                                         (GUPNP_EVENTING_ERROR,
1871                                          GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1872                                          msg->reason_phrase);
1873
1874                         g_signal_emit (data->service,
1875                                        signals[NOTIFY_FAILED],
1876                                        0,
1877                                        data->callbacks,
1878                                        error);
1879
1880                         g_error_free (error);
1881
1882                         /* Reset callbacks pointer */
1883                         data->callbacks = g_list_first (data->callbacks);
1884                 }
1885         }
1886 }
1887
1888 /* Send notification @user_data to subscriber @value */
1889 static void
1890 notify_subscriber (G_GNUC_UNUSED gpointer key,
1891                    gpointer               value,
1892                    gpointer               user_data)
1893 {
1894         SubscriptionData *data;
1895         const char *property_set;
1896         char *tmp;
1897         SoupMessage *msg;
1898         SoupSession *session;
1899
1900         data = value;
1901         property_set = user_data;
1902
1903         /* Subscriber called unsubscribe */
1904         if (subscription_data_can_delete (data))
1905                 return;
1906
1907         /* Create message */
1908         msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1909         if (!msg) {
1910                 g_warning ("Invalid callback URL: %s",
1911                            (char *) data->callbacks->data);
1912
1913                 return;
1914         }
1915
1916         soup_message_headers_append (msg->request_headers,
1917                                      "NT",
1918                                      "upnp:event");
1919
1920         soup_message_headers_append (msg->request_headers,
1921                                      "NTS",
1922                                      "upnp:propchange");
1923
1924         soup_message_headers_append (msg->request_headers,
1925                                      "SID",
1926                                      data->sid);
1927
1928         tmp = g_strdup_printf ("%d", data->seq);
1929         soup_message_headers_append (msg->request_headers,
1930                                      "SEQ",
1931                                      tmp);
1932         g_free (tmp);
1933
1934         /* Handle overflow */
1935         if (data->seq < G_MAXINT32)
1936                 data->seq++;
1937         else
1938                 data->seq = 1;
1939
1940         /* Add body */
1941         soup_message_set_request (msg,
1942                                   "text/xml; charset=\"utf-8\"",
1943                                   SOUP_MEMORY_TAKE,
1944                                   g_strdup (property_set),
1945                                   strlen (property_set));
1946
1947         /* Queue */
1948         data->pending_messages = g_list_prepend (data->pending_messages, msg);
1949         soup_message_headers_append (msg->request_headers,
1950                                      "Connection", "close");
1951
1952         session = gupnp_service_get_session (data->service);
1953
1954         soup_session_queue_message (session,
1955                                     msg,
1956                                     notify_got_response,
1957                                     data);
1958 }
1959
1960 /* Create a property set from @queue */
1961 static char *
1962 create_property_set (GQueue *queue)
1963 {
1964         NotifyData *data;
1965         GString *str;
1966
1967         /* Compose property set */
1968         str = xml_util_new_string ();
1969
1970         g_string_append (str,
1971                          "<?xml version=\"1.0\"?>"
1972                          "<e:propertyset xmlns:e="
1973                                 "\"urn:schemas-upnp-org:event-1-0\">");
1974
1975         /* Add variables */
1976         while ((data = g_queue_pop_head (queue))) {
1977                 xml_util_start_element (str, "e:property");
1978                 xml_util_start_element (str, data->variable);
1979                 gvalue_util_value_append_to_xml_string (&data->value, str);
1980                 xml_util_end_element (str, data->variable);
1981                 xml_util_end_element (str, "e:property");
1982
1983                 /* Cleanup */
1984                 notify_data_free (data);
1985         }
1986
1987         g_string_append (str, "</e:propertyset>");
1988
1989         /* Cleanup & return */
1990         return g_string_free (str, FALSE);
1991 }
1992
1993 /* Flush all queued notifications */
1994 static void
1995 flush_notifications (GUPnPService *service)
1996 {
1997         char *mem;
1998
1999         /* Create property set */
2000         mem = create_property_set (service->priv->notify_queue);
2001
2002         /* And send it off */
2003         g_hash_table_foreach (service->priv->subscriptions,
2004                               notify_subscriber,
2005                               mem);
2006
2007         /* Cleanup */
2008         g_free (mem);
2009 }
2010
2011 /**
2012  * gupnp_service_notify_value:
2013  * @service: A #GUPnPService
2014  * @variable: The name of the variable to notify
2015  * @value: The value of the variable
2016  *
2017  * Notifies listening clients that @variable has changed to @value.
2018  **/
2019 void
2020 gupnp_service_notify_value (GUPnPService *service,
2021                             const char   *variable,
2022                             const GValue *value)
2023 {
2024         NotifyData *data;
2025
2026         g_return_if_fail (GUPNP_IS_SERVICE (service));
2027         g_return_if_fail (variable != NULL);
2028         g_return_if_fail (G_IS_VALUE (value));
2029
2030         /* Queue */
2031         data = g_slice_new0 (NotifyData);
2032
2033         data->variable = g_strdup (variable);
2034
2035         g_value_init (&data->value, G_VALUE_TYPE (value));
2036         g_value_copy (value, &data->value);
2037
2038         g_queue_push_tail (service->priv->notify_queue, data);
2039
2040         /* And flush, if not frozen */
2041         if (!service->priv->notify_frozen)
2042                 flush_notifications (service);
2043 }
2044
2045 /**
2046  * gupnp_service_freeze_notify:
2047  * @service: A #GUPnPService
2048  *
2049  * Causes new notifications to be queued up until gupnp_service_thaw_notify()
2050  * is called.
2051  **/
2052 void
2053 gupnp_service_freeze_notify (GUPnPService *service)
2054 {
2055         g_return_if_fail (GUPNP_IS_SERVICE (service));
2056
2057         service->priv->notify_frozen = TRUE;
2058 }
2059
2060 /**
2061  * gupnp_service_thaw_notify:
2062  * @service: A #GUPnPService
2063  *
2064  * Sends out any pending notifications, and stops queuing of new ones.
2065  **/
2066 void
2067 gupnp_service_thaw_notify (GUPnPService *service)
2068 {
2069         g_return_if_fail (GUPNP_IS_SERVICE (service));
2070
2071         service->priv->notify_frozen = FALSE;
2072
2073         if (g_queue_get_length (service->priv->notify_queue) == 0)
2074                 return; /* Empty notify queue */
2075
2076         flush_notifications (service);
2077 }
2078
2079 /* Convert a CamelCase string to a lowercase string with underscores */
2080 static char *
2081 strip_camel_case (char *camel_str)
2082 {
2083         char *stripped;
2084         unsigned int i, j;
2085
2086         /* Keep enough space for underscores */
2087         stripped = g_malloc (strlen (camel_str) * 2);
2088
2089         for (i = 0, j = 0; i <= strlen (camel_str); i++) {
2090                 /* Convert every upper case letter to lower case and unless
2091                  * it's the first character, the last charachter, in the
2092                  * middle of an abbreviation or there is already an underscore
2093                  * before it, add an underscore before it */
2094                 if (g_ascii_isupper (camel_str[i])) {
2095                         if (i != 0 &&
2096                             camel_str[i + 1] != '\0' &&
2097                             camel_str[i - 1] != '_' &&
2098                             !g_ascii_isupper (camel_str[i - 1])) {
2099                                 stripped[j++] = '_';
2100                         }
2101                         stripped[j++] = g_ascii_tolower (camel_str[i]);
2102                 } else
2103                         stripped[j++] = camel_str[i];
2104         }
2105
2106         return stripped;
2107 }
2108
2109 static GCallback
2110 find_callback_by_name (GModule    *module,
2111                        const char *name)
2112 {
2113         GCallback callback;
2114         char *full_name;
2115
2116         /* First try with 'on_' prefix */
2117         full_name = g_strjoin ("_",
2118                                "on",
2119                                name,
2120                                NULL);
2121
2122         if (!g_module_symbol (module,
2123                               full_name,
2124                               (gpointer) &callback)) {
2125                 g_free (full_name);
2126
2127                 /* Now try with '_cb' postfix */
2128                 full_name = g_strjoin ("_",
2129                                        name,
2130                                        "cb",
2131                                        NULL);
2132
2133                 if (!g_module_symbol (module,
2134                                       full_name,
2135                                       (gpointer) &callback))
2136                         callback = NULL;
2137         }
2138
2139         g_free (full_name);
2140
2141         return callback;
2142 }
2143
2144 /* Use the strings from @name_list as details to @signal_name, and connect
2145  * callbacks with names based on these same strings to @signal_name::string. */
2146 static void
2147 connect_names_to_signal_handlers (GUPnPService *service,
2148                                   GModule      *module,
2149                                   const GList  *name_list,
2150                                   const char   *signal_name,
2151                                   const char   *callback_prefix,
2152                                   gpointer      user_data)
2153 {
2154         const GList *name_node;
2155
2156         for (name_node = name_list;
2157              name_node;
2158              name_node = name_node->next) {
2159                 GCallback callback;
2160                 char     *callback_name;
2161                 char     *signal_detail;
2162
2163                 signal_detail = (char *) name_node->data;
2164                 callback_name = strip_camel_case (signal_detail);
2165
2166                 if (callback_prefix) {
2167                         char *tmp;
2168
2169                         tmp = g_strjoin ("_",
2170                                          callback_prefix,
2171                                          callback_name,
2172                                          NULL);
2173
2174                         g_free (callback_name);
2175                         callback_name = tmp;
2176                 }
2177
2178                 callback = find_callback_by_name (module, callback_name);
2179                 g_free (callback_name);
2180
2181                 if (callback == NULL)
2182                         continue;
2183
2184                 signal_detail = g_strjoin ("::",
2185                                            signal_name,
2186                                            signal_detail,
2187                                            NULL);
2188
2189                 g_signal_connect (service,
2190                                   signal_detail,
2191                                   callback,
2192                                   user_data);
2193
2194                 g_free (signal_detail);
2195         }
2196 }
2197
2198 /**
2199  * gupnp_service_signals_autoconnect:
2200  * @service: A #GUPnPService
2201  * @user_data: the data to pass to each of the callbacks
2202  * @error: return location for a #GError, or %NULL
2203  *
2204  * A convenience function that attempts to connect all possible
2205  * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2206  * appropriate callbacks for the service @service. It uses service introspection
2207  * and #GModule<!-- -->'s introspective features. It is very simillar to
2208  * gtk_builder_connect_signals() except that it attempts to guess the names of
2209  * the signal handlers on its own.
2210  *
2211  * For this function to do its magic, the application must name the callback
2212  * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2213  * off the action names and either prepend "on_" or append "_cb" to them. Same
2214  * goes for #GUPnPService::query-variable signals, except that "query_" should
2215  * be prepended to the variable name. For example, callback function for
2216  * <varname>GetSystemUpdateID</varname> action should be either named as
2217  * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2218  * for the query of "SystemUpdateID" state variable should be named
2219  * <function>query_system_update_id_cb</function> or
2220  * <function>on_query_system_update_id</function>.
2221  *
2222  * <note>This function will not work correctly if #GModule is not supported
2223  * on the platform or introspection is not available for @service.</note>
2224  *
2225  * <warning>This function can not and therefore does not guarantee that the
2226  * resulting signal connections will be correct as it depends heavily on a
2227  * particular naming schemes described above.</warning>
2228  **/
2229 void
2230 gupnp_service_signals_autoconnect (GUPnPService *service,
2231                                    gpointer      user_data,
2232                                    GError      **error)
2233 {
2234         GUPnPServiceIntrospection *introspection;
2235         const GList               *names;
2236         GModule                   *module;
2237
2238         g_return_if_fail (GUPNP_IS_SERVICE (service));
2239
2240         introspection = gupnp_service_info_get_introspection
2241                                 (GUPNP_SERVICE_INFO (service),
2242                                  error);
2243         if (!introspection)
2244                 return;
2245
2246         /* Get a handle on the main executable -- use this to find symbols */
2247         module = g_module_open (NULL, 0);
2248         if (module == NULL) {
2249                 g_error ("Failed to open module: %s", g_module_error ());
2250
2251                 g_object_unref (introspection);
2252
2253                 return;
2254         }
2255
2256         names = gupnp_service_introspection_list_action_names (introspection);
2257         connect_names_to_signal_handlers (service,
2258                                           module,
2259                                           names,
2260                                           "action-invoked",
2261                                           NULL,
2262                                           user_data);
2263
2264         names = gupnp_service_introspection_list_state_variable_names
2265                         (introspection);
2266         connect_names_to_signal_handlers (service,
2267                                           module,
2268                                           names,
2269                                           "query-variable",
2270                                           "query",
2271                                           user_data);
2272
2273         g_module_close (module);
2274         g_object_unref (introspection);
2275 }