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