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