[PATCH 14/16] e_dbus: Don't crash when method return NULL
[framework/uifw/edbus.git] / src / lib / dbus / e_dbus_object.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #include <string.h>
6
7 #include "e_dbus_private.h"
8
9 static E_DBus_Interface *introspectable_interface = NULL;
10 static E_DBus_Interface *properties_interface = NULL;
11
12 typedef struct E_DBus_Method E_DBus_Method;
13 typedef struct E_DBus_Signal E_DBus_Signal;
14
15 Eina_Strbuf * e_dbus_object_introspect(E_DBus_Object *obj);
16
17 static void e_dbus_object_unregister(DBusConnection *conn, void *user_data);
18 static DBusHandlerResult e_dbus_object_handler(DBusConnection *conn, DBusMessage *message, void *user_data);
19
20 static void e_dbus_interface_free(E_DBus_Interface *iface);
21
22 static E_DBus_Method *e_dbus_method_new(const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func);
23 static void e_dbus_object_method_free(E_DBus_Method *m);
24
25 static E_DBus_Signal *e_dbus_signal_new(const char *name, const char *signature);
26 static void e_dbus_object_signal_free(E_DBus_Signal *s);
27
28 static void _introspect_indent_append(Eina_Strbuf *buf, int level);
29 static void _introspect_interface_append(Eina_Strbuf *buf, E_DBus_Interface *iface, int level);
30 static void _introspect_method_append(Eina_Strbuf *buf, E_DBus_Method *method, int level);
31 static void _introspect_signal_append(Eina_Strbuf *buf, E_DBus_Signal *signal, int level);
32 static void _introspect_arg_append(Eina_Strbuf *buf, const char *type, const char *direction, int level);
33
34
35 //static Eina_List *standard_methods = NULL;
36
37
38 static DBusObjectPathVTable vtable = {
39   e_dbus_object_unregister,
40   e_dbus_object_handler,
41   NULL,
42   NULL,
43   NULL,
44   NULL
45 };
46
47 struct E_DBus_Object
48 {
49   E_DBus_Connection *conn;
50   char *path;
51   Eina_List *interfaces;
52   char *introspection_data;
53   int introspection_dirty;
54
55   E_DBus_Object_Property_Get_Cb cb_property_get;
56   E_DBus_Object_Property_Set_Cb cb_property_set;
57
58   void *data;
59 };
60
61 struct E_DBus_Interface
62 {
63   char *name;
64   Eina_List *methods;
65   Eina_List *signals;
66   int refcount;
67 };
68
69 struct E_DBus_Method
70 {
71   char *member;
72   char *signature;
73   char *reply_signature;
74   E_DBus_Method_Cb func;
75 };
76
77 struct E_DBus_Signal
78 {
79   char *name;
80   char *signature;
81 };
82
83 static DBusMessage *
84 cb_introspect(E_DBus_Object *obj, DBusMessage *msg)
85 {
86   DBusMessage *ret;
87   Eina_Strbuf *buf;
88
89   if (obj->introspection_dirty || !obj->introspection_data)
90   {
91     buf = e_dbus_object_introspect(obj);
92     if (!buf)
93     {
94       ret = dbus_message_new_error(msg, "org.enlightenment.NotIntrospectable", "This object does not provide introspection data");
95       return ret;
96     }
97
98     if (obj->introspection_data) free(obj->introspection_data);
99     obj->introspection_data = strdup(eina_strbuf_string_get(buf));
100     eina_strbuf_free(buf);
101   }
102   //printf("XML: \n\n%s\n\n", obj->introspection_data);
103   ret = dbus_message_new_method_return(msg);
104   dbus_message_append_args(ret, DBUS_TYPE_STRING, &(obj->introspection_data), DBUS_TYPE_INVALID);
105
106   return ret;
107 }
108
109 static DBusMessage *
110 cb_properties_get(E_DBus_Object *obj, DBusMessage *msg)
111 {
112   DBusMessage *reply;
113   DBusMessageIter iter, sub;
114   DBusError err;
115   int type;
116   void *value;
117   char *property;
118
119   dbus_error_init(&err);
120   dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
121
122   if (dbus_error_is_set(&err))
123   {
124     return dbus_message_new_error(msg, err.name, err.message);
125   }
126
127   obj->cb_property_get(obj, property, &type, &value);
128   if (type == DBUS_TYPE_INVALID)
129   {
130     return dbus_message_new_error_printf(msg, "org.enlightenment.DBus.InvalidProperty", "The property '%s' does not exist on this object.", property);
131   }
132
133   if (dbus_type_is_basic(type))
134   {
135     reply = dbus_message_new_method_return(msg);
136     dbus_message_iter_init_append(msg, &iter);
137     dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, e_dbus_basic_type_as_string(type), &sub);
138     dbus_message_iter_append_basic(&sub, type, &value);
139     dbus_message_iter_close_container(&iter, &sub);
140     return reply;
141   }
142   else
143   {
144     return dbus_message_new_error(msg, "org.enlightenment.DBus.UnsupportedType", "E_DBus currently only supports properties of a basic type.");
145   }
146 }
147
148 static DBusMessage *
149 cb_properties_set(E_DBus_Object *obj, DBusMessage *msg)
150 {
151   DBusMessageIter iter, sub;
152   int type;
153   void *value;
154   char *property;
155
156   dbus_message_iter_init(msg, &iter);
157   dbus_message_iter_get_basic(&iter, &property);
158   dbus_message_iter_recurse(&iter, &sub);
159   type = dbus_message_iter_get_arg_type(&sub);
160   if (dbus_type_is_basic(type))
161   {
162     dbus_message_iter_get_basic(&sub, &value);
163     if (obj->cb_property_set(obj, property, type, value))
164     {
165       return dbus_message_new_method_return(msg);
166     }
167     else
168     {
169       return dbus_message_new_error_printf(msg, "org.enlightenment.DBus.InvalidProperty", "The property '%s' does not exist on this object.", property);
170     }
171   }
172   else
173   {
174     return dbus_message_new_error(msg, "org.enlightenment.DBus.UnsupportedType", "E_DBus currently only supports properties of a basic type.");
175   }
176
177 }
178
179 int
180 e_dbus_object_init(void)
181 {
182   introspectable_interface = e_dbus_interface_new("org.freedesktop.DBus.Introspectable");
183   properties_interface = e_dbus_interface_new("org.freedesktop.DBus.Properties");
184   if (!introspectable_interface || !properties_interface)
185   {
186     if (introspectable_interface) e_dbus_interface_unref(introspectable_interface);
187     introspectable_interface = NULL;
188     if (properties_interface) e_dbus_interface_unref(properties_interface);
189     properties_interface = NULL;
190     return 0;
191   }
192
193   e_dbus_interface_method_add(introspectable_interface, "Introspect", "", "s", cb_introspect);
194   e_dbus_interface_method_add(properties_interface, "Get", "s", "v", cb_properties_get);
195   e_dbus_interface_method_add(properties_interface, "Set", "sv", "", cb_properties_set);
196   return 1;
197 }
198
199 void
200 e_dbus_object_shutdown(void)
201 {
202   e_dbus_interface_unref(introspectable_interface);
203   introspectable_interface = NULL;
204
205   e_dbus_interface_unref(properties_interface);
206   properties_interface = NULL;
207 }
208
209 /**
210  * Add a dbus object.
211  *
212  * @param conn the connection on with the object should listen
213  * @param object_path a unique string identifying an object (e.g. org/enlightenment/WindowManager
214  * @param data custom data to set on the object (retrievable via
215  *             e_dbus_object_data_get())
216  */
217 EAPI E_DBus_Object *
218 e_dbus_object_add(E_DBus_Connection *conn, const char *object_path, void *data)
219 {
220   E_DBus_Object *obj;
221
222   obj = calloc(1, sizeof(E_DBus_Object));
223   if (!obj) return NULL;
224
225   if (!dbus_connection_register_object_path(conn->conn, object_path, &vtable, obj))
226   {
227     free(obj);
228     return NULL;
229   }
230
231   obj->conn = conn;
232   e_dbus_connection_ref(conn);
233   obj->path = strdup(object_path);
234   obj->data = data;
235   obj->interfaces = NULL;
236
237   e_dbus_object_interface_attach(obj, introspectable_interface);
238
239   return obj;
240 }
241
242 /**
243  * Free a dbus object
244  *
245  * @param obj the object to free
246  */
247 EAPI void
248 e_dbus_object_free(E_DBus_Object *obj)
249 {
250   E_DBus_Interface *iface;
251
252   if (!obj) return;
253
254   DBG("e_dbus_object_free (%s)", obj->path);
255   dbus_connection_unregister_object_path(obj->conn->conn, obj->path);
256   e_dbus_connection_close(obj->conn);
257
258   if (obj->path) free(obj->path);
259   EINA_LIST_FREE(obj->interfaces, iface)
260     e_dbus_interface_unref(iface);
261
262   if (obj->introspection_data) free(obj->introspection_data);
263
264   free(obj);
265 }
266
267 /**
268  * @brief Fetch the data pointer for a dbus object
269  * @param obj the dbus object
270  */
271 EAPI void *
272 e_dbus_object_data_get(E_DBus_Object *obj)
273 {
274   return obj->data;
275 }
276
277 /**
278  * @brief Get the dbus connection of a dbus object
279  * @param obj the dbus object
280  */
281 EAPI E_DBus_Connection *
282 e_dbus_object_conn_get(E_DBus_Object *obj)
283 {
284   return obj->conn;
285 }
286
287 /**
288  * @brief Get the path of a dbus object
289  * @param obj the dbus object
290  */
291 EAPI const char *
292 e_dbus_object_path_get(E_DBus_Object *obj)
293 {
294   return obj->path;
295 }
296
297 /**
298  * @brief Get the interfaces of a dbus object
299  * @param obj the dbus object
300  */
301 EAPI const Eina_List *
302 e_dbus_object_interfaces_get(E_DBus_Object *obj)
303 {
304   return obj->interfaces;
305 }
306
307 /**
308  * @brief Sets the callback to fetch properties from an object
309  * @param obj the object
310  * @param func the callback
311  */
312 EAPI void
313 e_dbus_object_property_get_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Get_Cb func)
314 {
315   obj->cb_property_get = func;
316 }
317
318 /**
319  * @brief Sets the callback to set properties on an object
320  * @param obj the object
321  * @param func the callback
322  */
323 EAPI void
324 e_dbus_object_property_set_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Set_Cb func)
325 {
326   obj->cb_property_set = func;
327 }
328
329 EAPI void
330 e_dbus_object_interface_attach(E_DBus_Object *obj, E_DBus_Interface *iface)
331 {
332   e_dbus_interface_ref(iface);
333   obj->interfaces = eina_list_append(obj->interfaces, iface);
334   obj->introspection_dirty = 1;
335   DBG("e_dbus_object_interface_attach (%s, %s) ", obj->path, iface->name);
336 }
337
338 EAPI void
339 e_dbus_object_interface_detach(E_DBus_Object *obj, E_DBus_Interface *iface)
340 {
341   E_DBus_Interface *found;
342
343   DBG("e_dbus_object_interface_detach (%s, %s) ", obj->path, iface->name);
344   found = eina_list_data_find(obj->interfaces, iface);
345   if (found == NULL) return;
346
347   obj->interfaces = eina_list_remove(obj->interfaces, iface);
348   obj->introspection_dirty = 1;
349   e_dbus_interface_unref(iface);
350 }
351
352 EAPI void
353 e_dbus_interface_ref(E_DBus_Interface *iface)
354 {
355   iface->refcount++;
356   DBG("e_dbus_interface_ref (%s) = %d", iface->name, iface->refcount);
357 }
358
359 EAPI void
360 e_dbus_interface_unref(E_DBus_Interface *iface)
361 {
362   DBG("e_dbus_interface_unref (%s) = %d", iface->name, iface->refcount - 1);
363   if (--(iface->refcount) == 0)
364     e_dbus_interface_free(iface);
365 }
366
367 static void
368 e_dbus_interface_free(E_DBus_Interface *iface)
369 {
370   E_DBus_Method *m;
371   E_DBus_Signal *s;
372
373   if (iface->name) free(iface->name);
374   EINA_LIST_FREE(iface->methods, m)
375     e_dbus_object_method_free(m);
376   EINA_LIST_FREE(iface->signals, s)
377     e_dbus_object_signal_free(s);
378   free(iface);
379 }
380
381
382 /**
383  * Add a method to an object
384  *
385  * @param iface the E_DBus_Interface to which this method belongs
386  * @param member the name of the method
387  * @param signature  an optional message signature. if provided, then messages
388  *                   with invalid signatures will be automatically rejected 
389  *                   (an Error response will be sent) and introspection data
390  *                   will be available.
391  *
392  * @return 1 if successful, 0 if failed (e.g. no memory)
393  */
394 EAPI int
395 e_dbus_interface_method_add(E_DBus_Interface *iface, const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
396 {
397   E_DBus_Method *m;
398
399   m = e_dbus_method_new(member, signature, reply_signature, func);
400   DBG("E-dbus: Add method %s: %p", member, m);
401   if (!m) return 0;
402
403   iface->methods = eina_list_append(iface->methods, m);
404   return 1;
405 }
406
407 /**
408  * Add a signal to an object
409  *
410  * @param iface the E_DBus_Interface to which this signal belongs
411  * @param name  the name of the signal
412  * @param signature  an optional message signature.
413  *
414  * @return 1 if successful, 0 if failed (e.g. no memory)
415  */
416 EAPI int
417 e_dbus_interface_signal_add(E_DBus_Interface *iface, const char *name, const char *signature)
418 {
419   E_DBus_Signal *s;
420
421   s = e_dbus_signal_new(name, signature);
422   DBG("E-dbus: Add signal %s: %p", name, s);
423   if (!s) return 0;
424
425   iface->signals = eina_list_append(iface->signals, s);
426   return 1;
427 }
428
429 EAPI E_DBus_Interface *
430 e_dbus_interface_new(const char *interface)
431 {
432   E_DBus_Interface *iface;
433
434   if (!interface) return NULL;
435
436   iface = calloc(1, sizeof(E_DBus_Interface));
437   if (!iface) return NULL;
438
439   iface->refcount = 1;
440   iface->name = strdup(interface);
441   iface->methods = NULL;
442   iface->signals = NULL;
443
444   return iface;
445 }
446
447 static E_DBus_Method *
448 e_dbus_method_new(const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
449 {
450   E_DBus_Method *m;
451
452   if (!member || !func) return NULL;
453
454   if (signature && !dbus_signature_validate(signature, NULL)) return NULL;
455   if (reply_signature && !dbus_signature_validate(reply_signature, NULL)) return NULL;
456   m = calloc(1, sizeof(E_DBus_Method));
457   if (!m) return NULL;
458
459   m->member = strdup(member);
460   if (signature)
461     m->signature = strdup(signature);
462   if (reply_signature)
463     m->reply_signature = strdup(reply_signature);
464   m->func = func;
465
466   return m;
467 }
468
469 static void
470 e_dbus_object_method_free(E_DBus_Method *m)
471 {
472   if (!m) return;
473   if (m->member) free(m->member);
474   if (m->signature) free(m->signature);
475   if (m->reply_signature) free(m->reply_signature);
476
477   free(m);
478 }
479
480 static E_DBus_Signal *
481 e_dbus_signal_new(const char *name, const char *signature)
482 {
483   E_DBus_Signal *s;
484
485   if (!name) return NULL;
486
487   if (signature && !dbus_signature_validate(signature, NULL)) return NULL;
488   s = calloc(1, sizeof(E_DBus_Signal));
489   if (!s) return NULL;
490
491   s->name = strdup(name);
492   if (signature)
493     s->signature = strdup(signature);
494
495   return s;
496 }
497
498 static void
499 e_dbus_object_signal_free(E_DBus_Signal *s)
500 {
501   if (!s) return;
502   if (s->name) free(s->name);
503   if (s->signature) free(s->signature);
504   free(s);
505 }
506
507 static E_DBus_Method *
508 e_dbus_object_method_find(E_DBus_Object *obj, const char *interface, const char *member)
509 {
510   E_DBus_Method *m;
511   E_DBus_Interface *iface;
512   Eina_List *l, *ll;
513
514   if (!obj || !member) return NULL;
515
516   EINA_LIST_FOREACH(obj->interfaces, l, iface)
517   {
518     if (strcmp(interface, iface->name)) continue;
519     EINA_LIST_FOREACH(iface->methods, ll, m)
520     {
521       if (!strcmp(member, m->member))
522         return m;
523     }
524   }
525   return NULL;
526 }
527
528 static DBusHandlerResult
529 e_dbus_object_handler(DBusConnection *conn, DBusMessage *message, void *user_data) 
530 {
531   E_DBus_Object *obj;
532   E_DBus_Method *m;
533   DBusMessage *reply;
534   dbus_uint32_t serial;
535
536   obj = user_data;
537   if (!obj)
538     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
539
540   m = e_dbus_object_method_find(obj, dbus_message_get_interface(message), dbus_message_get_member(message));
541
542   /* XXX should this send an 'invalid method' error instead? */
543   if (!m) 
544     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
545
546   if (m->signature && !dbus_message_has_signature(message, m->signature))
547     reply = dbus_message_new_error_printf(message, "org.enlightenment.InvalidSignature", "Expected signature: %s", m->signature);
548   else
549     reply = m->func(obj, message);
550
551   /* user can choose reply later */
552   if (!reply)
553     return DBUS_HANDLER_RESULT_HANDLED;
554
555   dbus_connection_send(conn, reply, &serial);
556   dbus_message_unref(reply);
557
558   return DBUS_HANDLER_RESULT_HANDLED;
559 }
560
561 static void
562 e_dbus_object_unregister(DBusConnection *conn __UNUSED__, void *user_data __UNUSED__)
563 {
564   /* free up the object struct? */
565 }
566
567 Eina_Strbuf *
568 e_dbus_object_introspect(E_DBus_Object *obj)
569 {
570   Eina_Strbuf *buf;
571   int level = 0;
572   E_DBus_Interface *iface;
573   Eina_List *l;
574
575   buf = eina_strbuf_new();
576
577   /* Doctype */
578   eina_strbuf_append(buf, "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n");
579
580   eina_strbuf_append(buf, "<node name=\"");
581   eina_strbuf_append(buf, obj->path);
582   eina_strbuf_append(buf, "\">\n");
583   level++;
584
585   EINA_LIST_FOREACH(obj->interfaces, l, iface)
586     _introspect_interface_append(buf, iface, level);
587
588   eina_strbuf_append(buf, "</node>\n");
589   return buf;
590 }
591
592 static void
593 _introspect_indent_append(Eina_Strbuf *buf, int level)
594 {
595   /* XXX optimize this? */
596   int i = level * 2;
597   while (i-- > 0)
598     eina_strbuf_append_char(buf, ' ');
599 }
600 static void
601 _introspect_interface_append(Eina_Strbuf *buf, E_DBus_Interface *iface, int level)
602 {
603   E_DBus_Method *method;
604   E_DBus_Signal *signal;
605   Eina_List *l;
606
607   _introspect_indent_append(buf, level);
608   eina_strbuf_append(buf, "<interface name=\"");
609   eina_strbuf_append(buf, iface->name);
610   eina_strbuf_append(buf, "\">\n");
611   level++;
612
613   DBG("introspect iface: %s", iface->name);
614   EINA_LIST_FOREACH(iface->methods, l, method)
615     _introspect_method_append(buf, method, level);
616   EINA_LIST_FOREACH(iface->signals, l, signal)
617     _introspect_signal_append(buf, signal, level);
618
619   level--;
620   _introspect_indent_append(buf, level);
621   eina_strbuf_append(buf, "</interface>\n");
622 }
623 static void
624 _introspect_method_append(Eina_Strbuf *buf, E_DBus_Method *method, int level)
625 {
626   DBusSignatureIter iter;
627   char *type;
628
629   _introspect_indent_append(buf, level);
630   DBG("introspect method: %s\n", method->member);
631   eina_strbuf_append(buf, "<method name=\"");
632   eina_strbuf_append(buf, method->member);
633   eina_strbuf_append(buf, "\">\n");
634   level++;
635
636   /* append args */
637   if (method->signature && 
638       method->signature[0] &&
639       dbus_signature_validate(method->signature, NULL))
640   {
641     dbus_signature_iter_init(&iter, method->signature);
642     while ((type = dbus_signature_iter_get_signature(&iter)))
643     {
644       _introspect_arg_append(buf, type, "in", level);
645
646       dbus_free(type);
647       if (!dbus_signature_iter_next(&iter)) break;
648     }
649   }
650
651   /* append reply args */
652   if (method->reply_signature &&
653       method->reply_signature[0] &&
654       dbus_signature_validate(method->reply_signature, NULL))
655   {
656     dbus_signature_iter_init(&iter, method->reply_signature);
657     while ((type = dbus_signature_iter_get_signature(&iter)))
658     {
659       _introspect_arg_append(buf, type, "out", level);
660
661       dbus_free(type);
662       if (!dbus_signature_iter_next(&iter)) break;
663     }
664   }
665
666   level--;
667   _introspect_indent_append(buf, level);
668   eina_strbuf_append(buf, "</method>\n");
669 }
670
671 static void
672 _introspect_signal_append(Eina_Strbuf *buf, E_DBus_Signal *signal, int level)
673 {
674   DBusSignatureIter iter;
675   char *type;
676
677   _introspect_indent_append(buf, level);
678   DBG("introspect signal: %s", signal->name);
679   eina_strbuf_append(buf, "<signal name=\"");
680   eina_strbuf_append(buf, signal->name);
681   eina_strbuf_append(buf, "\">\n");
682   level++;
683
684   /* append args */
685   if (signal->signature &&
686       signal->signature[0] &&
687       dbus_signature_validate(signal->signature, NULL))
688   {
689     dbus_signature_iter_init(&iter, signal->signature);
690     while ((type = dbus_signature_iter_get_signature(&iter)))
691     {
692       _introspect_arg_append(buf, type, NULL, level);
693
694       dbus_free(type);
695       if (!dbus_signature_iter_next(&iter)) break;
696     }
697   }
698
699   level--;
700   _introspect_indent_append(buf, level);
701   eina_strbuf_append(buf, "</signal>\n");
702 }
703
704 static void
705 _introspect_arg_append(Eina_Strbuf *buf, const char *type, const char *direction, int level)
706 {
707   _introspect_indent_append(buf, level);
708   eina_strbuf_append(buf, "<arg type=\"");
709   eina_strbuf_append(buf, type);
710   if (direction)
711   {
712     eina_strbuf_append(buf, "\" direction=\"");
713     eina_strbuf_append(buf, direction);
714   }
715   eina_strbuf_append(buf, "\"/>\n");
716 }
717