[jscorebus] Fix memory leaks in D-Bus message signature handling
[browser-dbus-bridge.git] / jscorebus / jscorebus-marshal.c
1 /**
2  * Browser D-Bus Bridge, JavaScriptCore version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include <string.h>
24
25 #include <glib.h>
26 #include <dbus/dbus.h>
27 #include <JavaScriptCore/JavaScript.h>
28
29 #include "jscorebus-classfactory.h"
30 #include "jscorebus-marshal.h"
31
32 static gboolean
33 jsvalue_append_basic(JSContextRef context,
34                      JSValueRef jsvalue,
35                      int type,
36                      DBusMessageIter *iter);
37
38
39 gboolean jsvalue_array_append_to_message_iter (JSContextRef context,
40                                                const JSValueRef jsvalues[],
41                                                int n_values,
42                                                DBusMessageIter *iter,
43                                                const char *signature)
44 {
45   DBusSignatureIter siter;
46   DBusError error;
47   char *sig = (char*) signature; /* This is a bit silly, I know */
48   int i = 0;
49
50   /* If there is no signature, we need to do some autodetection */
51   if (signature == NULL)
52   {
53     char **parts;
54     parts = g_new0(char*, n_values + 1);
55     for (i = 0; i < n_values; i++)
56     {
57       parts[i] = jsvalue_to_signature(context, jsvalues[i]);
58     }
59     sig = g_strjoinv(NULL, parts);
60     g_strfreev(parts);
61   }
62
63   /* If there *still* is no signature, or it is empty, we bork.
64    * Empty messages have no business going through this code path.
65    */
66   if (sig == NULL || strlen(sig) == 0)
67   {
68     g_warning("Could not autodetect signature for message arguments!");
69     g_free(sig);
70     return FALSE;
71   }
72
73   /* First of all, we need to validate the signature */
74   dbus_error_init(&error);
75   if (!dbus_signature_validate(sig, &error))
76   {
77     g_warning(error.message);
78     if (sig != signature)
79       g_free(sig);
80     return FALSE;
81   }
82   
83   dbus_signature_iter_init(&siter, sig);
84   i = 0;
85   do {
86     char *arg_sig = dbus_signature_iter_get_signature(&siter);
87     if (!jsvalue_append_to_message_iter(context, jsvalues[i++], iter, arg_sig))
88     {
89       g_warning("Appending '%s' to message failed!", arg_sig);
90       dbus_free(arg_sig);
91       if (sig != signature)
92         g_free(sig);
93       return FALSE;
94     }
95     dbus_free(arg_sig);
96   } while (dbus_signature_iter_next(&siter));
97   
98   if (sig != signature)
99     g_free(sig);
100
101   return TRUE;
102 }
103
104 gboolean jsvalue_append_to_message_iter(JSContextRef context,
105                                         JSValueRef jsvalue,
106                                         DBusMessageIter *iter,
107                                         const char *signature)
108 {
109   DBusSignatureIter siter;
110
111   dbus_signature_iter_init(&siter, signature);
112
113   switch (dbus_signature_iter_get_current_type(&siter))
114   {
115     /* JSValueToBoolean follows the JS rules of what's true and false so we can
116      * simply take the value without checking the type of it
117      */ 
118     case DBUS_TYPE_BOOLEAN:
119       {
120         dbus_bool_t value = JSValueToBoolean(context, jsvalue);
121         if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value))
122         {
123           g_warning("Could not append a boolean to message iter");
124           return FALSE;
125         }
126         break;
127       }
128     /* Basic types */
129     case DBUS_TYPE_INT16:
130     case DBUS_TYPE_INT32:
131     case DBUS_TYPE_INT64:
132     case DBUS_TYPE_UINT16:
133     case DBUS_TYPE_UINT32:
134     case DBUS_TYPE_UINT64:
135     case DBUS_TYPE_BYTE:
136     case DBUS_TYPE_STRING:
137     case DBUS_TYPE_OBJECT_PATH:
138     case DBUS_TYPE_SIGNATURE:
139       {
140         int type = dbus_signature_iter_get_current_type(&siter);
141         if (!jsvalue_append_basic(context, jsvalue, type, iter))
142         {
143           g_warning("Could not append a '%c' to message iter", type);
144           return FALSE;
145         }
146         break;
147       }
148     case DBUS_TYPE_DOUBLE:
149       {
150         /* Conversions between dbus_uint64_t and double seem to loose precision,
151          * that's why doubles are special-cased
152          */
153         double value = JSValueToNumber(context, jsvalue, NULL);
154         if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value))
155         {
156           g_warning("Could not append a double to message iter");
157           return FALSE;
158         }
159         break;
160       }
161     case DBUS_TYPE_ARRAY:
162       {
163         /* Dicts are implemented as arrays of entries in D-Bus */
164         if (dbus_signature_iter_get_element_type(&siter) == DBUS_TYPE_DICT_ENTRY)
165         {
166           int i, props;
167           JSPropertyNameArrayRef propnames;
168           char *dict_signature;
169           DBusMessageIter subiter;
170           DBusSignatureIter dictsiter;
171           DBusSignatureIter dictsubsiter;
172
173           dbus_signature_iter_recurse(&siter, &dictsiter);
174           dict_signature = dbus_signature_iter_get_signature(&dictsiter);
175
176           if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
177                                                 dict_signature, &subiter))
178           {
179             dbus_free(dict_signature);
180             g_warning("Memory exhausted!");
181             return FALSE;
182           }
183           dbus_free(dict_signature);
184           propnames = JSObjectCopyPropertyNames(context, (JSObjectRef)jsvalue);
185           props = JSPropertyNameArrayGetCount(propnames);
186
187           /* Move the signature iter to the value part of the signature
188            * We only support string->value dicts currently, though we coud do
189            * numbers too...
190            */
191           dbus_signature_iter_recurse(&dictsiter, &dictsubsiter); /* Now at key */
192           dbus_signature_iter_next(&dictsubsiter); /* Now at value */
193
194           for (i = 0; i < props; i++)
195           {
196             JSStringRef jsstr;
197             DBusMessageIter dictiter;
198             char *cstr;
199             if (!dbus_message_iter_open_container(&subiter, DBUS_TYPE_DICT_ENTRY,
200                                                   NULL, &dictiter))
201             {
202               g_warning("Memory exhausted!");
203               return FALSE;
204             }
205             jsstr = JSPropertyNameArrayGetNameAtIndex(propnames, i);
206             cstr = string_from_jsstring(context, jsstr);
207             dbus_message_iter_append_basic(&dictiter, DBUS_TYPE_STRING, &cstr);
208             g_free(cstr);
209             cstr = dbus_signature_iter_get_signature(&dictsubsiter);
210             jsvalue_append_to_message_iter(context,
211               JSObjectGetProperty(context, (JSObjectRef)jsvalue, jsstr, NULL),
212               &dictiter, cstr);
213             dbus_free(cstr);
214             dbus_message_iter_close_container(&subiter, &dictiter);
215           }
216           dbus_message_iter_close_container(iter, &subiter);
217           break;
218         } else {
219           int i, props;
220           JSPropertyNameArrayRef propnames;
221           JSValueRef *jsvalues;
222           DBusMessageIter subiter;
223           DBusSignatureIter arraysiter;
224           char *array_signature = NULL;
225           if (!jsvalue_instanceof(context, jsvalue, "Array"))
226           {
227             g_warning("Expected JavaScript Array type, got %i",
228                       JSValueGetType(context, jsvalue));
229             return FALSE;
230           }
231           
232           dbus_signature_iter_recurse(&siter, &arraysiter);
233           array_signature = dbus_signature_iter_get_signature(&arraysiter);
234           
235           propnames = JSObjectCopyPropertyNames(context, (JSObjectRef)jsvalue);
236           if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
237                                                 array_signature, &subiter))
238           {
239             g_warning("Memory exhausted!");
240             JSPropertyNameArrayRelease(propnames);
241             g_free(array_signature);
242             return FALSE;
243           }
244
245           props = JSPropertyNameArrayGetCount(propnames);
246
247           for (i = 0; i < props; i++)
248           {
249             JSValueRef value = JSObjectGetPropertyAtIndex(context,
250                                                           (JSObjectRef)jsvalue,
251                                                           i,
252                                                           NULL);
253             jsvalue_append_to_message_iter(context, value,
254                                            &subiter, array_signature);
255           }
256           dbus_message_iter_close_container(iter, &subiter);
257           g_free(array_signature);
258           JSPropertyNameArrayRelease(propnames);
259           break;
260         }
261       }
262     case DBUS_TYPE_VARIANT:
263       {
264         DBusMessageIter subiter;
265         DBusSignatureIter vsiter;
266         char *vsignature;
267         JSValueRef value = NULL;
268
269         if (jsvalue_typeof(context, jsvalue, "DBusVariant"))
270         {
271           value = (JSValueRef)JSObjectGetPrivate((JSObjectRef)jsvalue);
272         } else {
273           value = jsvalue;
274         }
275         
276         dbus_signature_iter_recurse(&siter, &vsiter);
277         vsignature = jsvalue_to_signature(context, value);
278         
279         if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
280                                               vsignature, &subiter))
281         {
282           g_warning("Memory exhausted!");
283           g_free(vsignature);
284           return FALSE;
285         }
286
287         if (!jsvalue_append_to_message_iter(context, value,
288                                             &subiter, vsignature))
289         {
290           g_warning("Failed to append variant contents with signature %s",
291                     vsignature);
292           g_free(vsignature);
293           return FALSE;
294         }
295         
296         g_free(vsignature);
297         dbus_message_iter_close_container(iter, &subiter);
298         break;
299       }
300     case DBUS_TYPE_STRUCT:
301       {
302         int i, props;
303         JSPropertyNameArrayRef propnames;
304         DBusMessageIter subiter;
305         DBusSignatureIter stsiter;
306         char *stsignature;
307         JSValueRef value = NULL;
308
309         if (jsvalue_typeof(context, jsvalue, "DBusStruct"))
310         {
311           value = (JSValueRef)JSObjectGetPrivate((JSObjectRef)jsvalue);
312         } else {
313           value = jsvalue;
314         }
315
316         propnames = JSObjectCopyPropertyNames(context, (JSObjectRef)value);
317         props = JSPropertyNameArrayGetCount(propnames);
318
319         if (props == 0)
320         {
321           g_warning("Empty struct not allowed");
322           return FALSE;
323         }
324         
325         if (!dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
326                                               NULL, &subiter))
327         {
328           g_warning("Memory exhausted!");
329           return FALSE;
330         }
331
332         dbus_signature_iter_recurse(&siter, &stsiter);
333
334         for (i = 0; i < props; i++)
335         {
336           const char *sig = dbus_signature_iter_get_signature(&stsiter);
337           JSValueRef child_value = JSObjectGetProperty(context,
338             (JSObjectRef)value,
339             JSPropertyNameArrayGetNameAtIndex(propnames, i),
340             NULL);
341
342           if (!jsvalue_append_to_message_iter(context, child_value,
343                                               &subiter, sig))
344           {
345             g_warning("Failed to append struct contents with signature %s",
346                       sig);
347             return FALSE;
348           }
349
350           if (!dbus_signature_iter_next(&stsiter))
351           {
352             break;
353           }
354         }
355         JSPropertyNameArrayRelease(propnames);
356         dbus_message_iter_close_container(iter, &subiter);
357         break;
358       }
359     default:
360       g_warning("Tried to append invalid or unsupported argument '%s' "
361                 "(base type '%c') to a message", signature,
362                 dbus_signature_iter_get_current_type(&siter));
363       break;
364   }
365
366   return TRUE;
367 }
368
369 static gboolean
370 jsvalue_append_basic(JSContextRef context,
371                      JSValueRef jsvalue,
372                      int type,
373                      DBusMessageIter *iter)
374 {
375   dbus_uint64_t v = 0;
376   dbus_uint64_t *value = NULL;
377   char *strvalue = NULL;
378   switch (JSValueGetType(context, jsvalue))
379   {
380     case kJSTypeNumber:
381       {
382         v = (dbus_uint64_t)JSValueToNumber(context, jsvalue, NULL);
383         value = &v;
384         break;
385       }
386     case kJSTypeString:
387       {
388         strvalue = string_from_jsvalue(context, jsvalue);
389         break;
390       }
391     case kJSTypeUndefined:
392     case kJSTypeNull:
393       {
394         g_warning("Tried to pass undefined or null as basic type");
395         break;
396       }
397     case kJSTypeObject:
398       {
399         int i;
400         for (i = 0; i < JSCOREBUS_N_NUMBER_CLASSES; i++)
401         {
402           if (jscorebus_number_class_types[i] == type
403            && jsvalue_typeof(context, jsvalue, jscorebus_number_class_names[i]))
404           {
405             value = (dbus_uint64_t *)JSObjectGetPrivate((JSObjectRef)jsvalue);
406             break;
407           }
408         }
409
410         if (jsvalue_typeof(context, jsvalue, "DBusObjectPath"))
411         {
412           strvalue = string_from_jsvalue(context,
413                                          JSObjectGetPrivate((JSObjectRef)jsvalue));
414           break;
415         }
416
417         if (jsvalue_typeof(context, jsvalue, "DBusSignature"))
418         {
419           strvalue = string_from_jsvalue(context,
420                                          JSObjectGetPrivate((JSObjectRef)jsvalue));
421           break;
422         }
423
424         /* Intentionally falls through to default */
425       }
426     default:
427       g_warning("JSValue wasn't a '%c' (it was %i), or it is not supported",
428                 type, JSValueGetType(context, jsvalue));
429       break;
430   }
431   
432   if (value != NULL && dbus_message_iter_append_basic(iter, type, value))
433   {
434     return TRUE;
435   } else if (strvalue != NULL
436           && dbus_message_iter_append_basic(iter, type, &strvalue)) {
437     g_free(strvalue);
438     return TRUE;
439   }
440   
441   g_free(strvalue);
442   return FALSE;
443 }
444
445 #define NUMERIC_VALUE(NAME) \
446   DBUS_TYPE_## NAME: \
447     { \
448       dbus_uint64_t value = 0; \
449       dbus_message_iter_get_basic(iter, &value); \
450       jsvalue = JSValueMakeNumber(context, (double)value); \
451       break; \
452     }
453
454 JSValueRef jsvalue_from_message_iter(JSContextRef context,
455                                      DBusMessageIter *iter)
456 {
457   JSValueRef jsvalue;
458   
459   switch (dbus_message_iter_get_arg_type (iter))
460   {
461     case DBUS_TYPE_BOOLEAN:
462       {
463         gboolean value;
464         dbus_message_iter_get_basic(iter, &value);
465         jsvalue = JSValueMakeBoolean(context, value);
466         break;
467       }
468     case NUMERIC_VALUE(BYTE)
469     case NUMERIC_VALUE(INT16)
470     case NUMERIC_VALUE(UINT16)
471     case NUMERIC_VALUE(INT32)
472     case NUMERIC_VALUE(UINT32)
473     case NUMERIC_VALUE(INT64)
474     case NUMERIC_VALUE(UINT64)
475     case DBUS_TYPE_DOUBLE:
476     {
477       double value = 0;
478       dbus_message_iter_get_basic(iter, &value);
479       jsvalue = JSValueMakeNumber(context, value);
480       break;
481     }
482     case DBUS_TYPE_OBJECT_PATH:
483     case DBUS_TYPE_SIGNATURE:
484     case DBUS_TYPE_STRING:
485       {
486         const char *value;
487         JSStringRef jsstr;
488         dbus_message_iter_get_basic(iter, &value);
489         jsstr = JSStringCreateWithUTF8CString(value);
490         jsvalue = JSValueMakeString(context, jsstr);
491         JSStringRelease(jsstr);
492         break;
493       }
494     case DBUS_TYPE_ARRAY:
495       {
496         JSStringRef arrayProperty;
497         JSObjectRef arrayConstructor;
498         DBusMessageIter child_iter;
499         int i;
500         
501         arrayProperty = JSStringCreateWithUTF8CString("Array");
502         arrayConstructor = JSValueToObject(context,
503                             JSObjectGetProperty(context,
504                               JSContextGetGlobalObject(context),
505                               arrayProperty, NULL),
506                             NULL);
507         JSStringRelease(arrayProperty);
508
509         jsvalue = JSObjectCallAsConstructor(context, arrayConstructor,
510                                             0, NULL, NULL);
511
512         dbus_message_iter_recurse(iter, &child_iter);
513         
514         i = 0;
515         do
516         {
517           if (dbus_message_iter_get_arg_type(&child_iter) == DBUS_TYPE_DICT_ENTRY)
518           {
519             JSValueRef key;
520             JSStringRef key_str;
521             JSValueRef value;
522             DBusMessageIter dictiter;
523             
524             dbus_message_iter_recurse(&child_iter, &dictiter);
525             key = jsvalue_from_message_iter(context, &dictiter);
526             key_str = JSValueToStringCopy(context, key, NULL);
527             dbus_message_iter_next(&dictiter);
528             value = jsvalue_from_message_iter(context, &dictiter);
529
530             JSObjectSetProperty(context, (JSObjectRef)jsvalue,
531                                 key_str, value, 0, NULL);
532             JSStringRelease(key_str);
533           } else {
534             JSObjectSetPropertyAtIndex(context, (JSObjectRef)jsvalue, i++, 
535               jsvalue_from_message_iter(context, &child_iter), NULL);
536           }
537         } while (dbus_message_iter_next(&child_iter));
538         
539         break;
540       }
541     case DBUS_TYPE_VARIANT:
542       {
543         DBusMessageIter child_iter;
544         dbus_message_iter_recurse(iter, &child_iter);
545         jsvalue = jsvalue_from_message_iter(context, &child_iter);
546         break;
547       }
548     case DBUS_TYPE_INVALID:
549         /* Convert invalid to undefined */
550         jsvalue = JSValueMakeUndefined(context);
551         break;
552     case DBUS_TYPE_DICT_ENTRY:
553         /* Dict entries should always be handled in the array branch */
554         g_assert_not_reached();
555     default:
556       g_warning("Could not convert value from type %c (%i)",
557                 dbus_message_iter_get_arg_type (iter),
558                 dbus_message_iter_get_arg_type (iter));
559       jsvalue = JSValueMakeUndefined(context);
560       break;
561   }
562
563   return jsvalue;
564 }
565
566
567 char *string_from_jsstring(JSContextRef context, JSStringRef jsstr)
568 {
569   size_t len;
570   char *cstr;
571
572   len = JSStringGetMaximumUTF8CStringSize(jsstr);
573   cstr = g_new(char, len);
574   JSStringGetUTF8CString(jsstr, cstr, len);
575
576   return cstr;
577 }
578
579 char *string_from_jsvalue(JSContextRef context, JSValueRef jsvalue)
580 {
581   JSStringRef jsstr;
582   char *cstr;
583
584   if (!JSValueIsString(context, jsvalue))
585   {
586     return NULL;
587   }
588
589   jsstr = JSValueToStringCopy(context, jsvalue, NULL);
590   cstr = string_from_jsstring(context, jsstr);
591   JSStringRelease(jsstr);
592   
593   return cstr;
594 }
595
596 JSObjectRef function_from_jsvalue(JSContextRef context,
597                                   JSValueRef value,
598                                   JSValueRef* exception)
599 {
600   JSObjectRef function;
601
602   if (JSValueIsNull(context, value))
603   {
604     return NULL;
605   }
606
607   if (!JSValueIsObject(context, value) )
608   {
609     /* TODO: set exception */
610     g_warning("%s: Value wasn't an object", G_STRFUNC);
611     return NULL;
612   }
613
614   function = JSValueToObject(context, value, exception);
615   if (!JSObjectIsFunction(context, function))
616   {
617     /* TODO: set exception */
618     g_warning("%s: Value wasn't a function", G_STRFUNC);
619     return NULL;
620   }
621
622   return function;
623 }
624
625 void call_function_with_message_args(JSContextRef context,
626                                      JSObjectRef thisObject,
627                                      JSObjectRef function,
628                                      DBusMessage *message)
629 {
630   size_t argumentCount;
631   JSValueRef *args;
632   int arg_type;
633   DBusMessageIter iter;
634
635   /**
636    * Iterate over the message arguments and append them to args
637    */
638   dbus_message_iter_init (message, &iter);
639   argumentCount = 0;
640   args = NULL;
641
642   /* Error messages should have the error name as the first param */
643   if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR)
644   {
645     const char *error_name = dbus_message_get_error_name(message);
646     
647     if (error_name != NULL)
648     {
649       JSValueRef *tmp;
650       JSStringRef jsstr;
651       
652       jsstr = JSStringCreateWithUTF8CString(error_name);
653       argumentCount++;
654       tmp = g_renew(JSValueRef, args, argumentCount);
655       args = tmp;
656       args[argumentCount-1] = JSValueMakeString(context, jsstr);;
657       JSStringRelease(jsstr);
658      }
659   }
660
661   while ((arg_type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID)
662   {
663     JSValueRef *tmp;
664     
665     argumentCount++;
666     tmp = g_renew(JSValueRef, args, argumentCount);
667     args = tmp;
668     args[argumentCount-1] = (JSValueRef)jsvalue_from_message_iter(context, &iter);
669     if (args[argumentCount-1] == NULL) {
670       g_warning("Couldn't get argument from argument type %c", arg_type);
671       args[argumentCount-1] = JSValueMakeUndefined(context);
672     }
673     dbus_message_iter_next (&iter);
674   }
675
676   JSObjectCallAsFunction(context, function, thisObject,
677                          argumentCount, args, NULL);
678   g_free(args);
679 }
680