imcplugin: Add automated tests
[nativeclient.git] / imcplugin / imcplugin.c
blob58dab945968ea03e80ac606c3efacdef86c05818
2 #include <stdio.h>
3 #include <string.h>
5 #include <fcntl.h>
6 #include <pthread.h>
7 #include <unistd.h>
9 #include <npapi.h>
10 #include <npupp.h>
11 #include <npruntime.h>
13 #include "native_client/intermodule_comm/nacl_imc_c.h"
14 #include "native_client/service_runtime/include/sys/fcntl.h"
15 #include "native_client/service_runtime/nacl_desc_base.h"
16 #include "native_client/service_runtime/nacl_desc_imc.h"
17 #include "native_client/service_runtime/nacl_desc_io.h"
18 #include "native_client/service_runtime/nrd_xfer_lib/nrd_all_modules.h"
19 #include "native_client/service_runtime/nrd_xfer_lib/nrd_xfer.h"
20 #include "native_client/service_runtime/nrd_xfer_lib/nrd_xfer_effector.h"
23 #define print(x)
26 static NPNetscapeFuncs g_funcs;
27 static NPIdentifier ident_launch;
28 static NPIdentifier ident_get_file;
29 static NPIdentifier ident_send;
30 static NPIdentifier ident_length;
32 #define NPN_CreateObject (g_funcs.createobject)
33 #define NPN_RetainObject (g_funcs.retainobject)
34 #define NPN_ReleaseObject (g_funcs.releaseobject)
35 #define NPN_ReleaseVariantValue (g_funcs.releasevariantvalue)
36 #define NPN_InvokeDefault (g_funcs.invokeDefault)
37 #define NPN_GetProperty (g_funcs.getproperty)
38 #define NPN_UTF8FromIdentifier (g_funcs.utf8fromidentifier)
39 #define NPN_GetStringIdentifier (g_funcs.getstringidentifier)
40 #define NPN_GetIntIdentifier (g_funcs.getintidentifier)
41 #define NPN_GetURLNotify (g_funcs.geturlnotify)
42 #define NPN_PluginThreadAsyncCall (g_funcs.pluginthreadasynccall)
45 static void PrintIdentifier(NPIdentifier ident)
47 char *str = NPN_UTF8FromIdentifier(ident);
48 print((" ident = \"%s\"\n", str));
49 free(str);
52 static bool
53 IsType(NPObject *npobj, NPClass *npclass)
55 /* The underscore in "_class" suggests this member is not part of
56 the public API, but it could just be to prevent a clash with the
57 C++ keyword. */
58 return npobj->_class == npclass;
62 /* Mozilla does not provide default behaviour for these NoOp_* methods
63 if NULL is provided instead in the NPClass. */
65 static bool
66 NoOp_HasMethod(NPObject* obj, NPIdentifier methodName)
68 return false;
71 static bool
72 NoOp_HasProperty(NPObject *obj, NPIdentifier propertyName)
74 print(("Obj_HasProperty\n"));
75 PrintIdentifier(propertyName);
76 return false;
79 static bool
80 NoOp_GetProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result)
82 print(("Obj_GetProperty\n"));
83 return false;
87 struct FileObj {
88 struct NPObject header;
89 struct NaClDesc *desc;
90 /* We keep a filename to pass to sel_ldr, but there's a chance the
91 file could be deleted from the cache.
92 TODO: pass FDs to sel_ldr instead. */
93 char *filename;
96 static NPObject *
97 FileObj_Allocate(NPP npp, NPClass *npclass)
99 print(("Obj_Allocate\n"));
100 struct FileObj *obj = malloc(sizeof(struct FileObj));
101 return &obj->header;
104 static void
105 FileObj_Deallocate(NPObject *npobj)
107 struct FileObj *obj = (struct FileObj *) npobj;
108 NaClDescUnref(obj->desc);
109 free(obj->filename);
110 free(obj);
111 print(("FileObj_Deallocate\n"));
114 static NPClass FileObj_Vtable = {
115 .structVersion = NP_CLASS_STRUCT_VERSION,
116 .allocate = FileObj_Allocate,
117 .deallocate = FileObj_Deallocate,
118 .invalidate = NULL,
119 .hasMethod = NoOp_HasMethod,
120 .invoke = NULL,
121 .invokeDefault = NULL,
122 .hasProperty = NoOp_HasProperty,
123 .getProperty = NoOp_GetProperty,
124 .setProperty = NULL,
125 .removeProperty = NULL,
129 struct SendObj {
130 struct NPObject header;
131 NPP plugin;
132 struct NaClDesc *desc;
135 static NPObject *
136 SendObj_Allocate(NPP npp, NPClass *npclass)
138 print(("Obj_Allocate\n"));
139 struct SendObj *obj = malloc(sizeof(struct SendObj));
140 obj->plugin = npp;
141 return &obj->header;
144 static void
145 SendObj_Deallocate(NPObject *npobj)
147 struct SendObj *obj = (struct SendObj *) npobj;
148 NaClDescUnref(obj->desc);
149 free(obj);
150 print(("SendObj_Deallocate\n"));
153 static bool
154 SendObj_HasMethod(NPObject* npobj, NPIdentifier methodName)
156 return methodName == ident_send;
159 static bool
160 ConvertDescArray(NPP plugin, NPObject *array, struct NaClImcTypedMsgHdr *header)
162 NPVariant length_np;
163 if(!NPN_GetProperty(plugin, array, ident_length, &length_np) ||
164 !NPVARIANT_IS_INT32(length_np)) {
165 NPN_ReleaseVariantValue(&length_np);
166 return false;
168 int length = NPVARIANT_TO_INT32(length_np);
169 NPN_ReleaseVariantValue(&length_np);
171 /* Firstly, there will be a limit to the number of capabilities we
172 can send in a message. Secondly, the array is not necessarily a
173 primitive Javascript array; its .length property could be much
174 larger than the amount of space Javascript is normally allowed to
175 allocate. */
176 if(length > 32)
177 return false;
179 struct NaClDesc **descs = malloc(sizeof(struct NaClDesc *) * length);
180 if(descs == NULL)
181 return false;
182 int i;
183 for(i = 0; i < length; i++) {
184 NPIdentifier index_id = NPN_GetIntIdentifier(i);
185 NPVariant element;
186 if(!NPN_GetProperty(plugin, array, index_id, &element) ||
187 !NPVARIANT_IS_OBJECT(element))
188 return false; /* TODO: clear up */
189 NPObject *file_npobj = NPVARIANT_TO_OBJECT(element);
190 if(!IsType(file_npobj, &FileObj_Vtable))
191 return false; /* TODO: clear up */
192 struct FileObj *file_obj = (struct FileObj *) file_npobj;
193 NaClDescRef(file_obj->desc);
194 descs[i] = file_obj->desc;
195 NPN_ReleaseVariantValue(&element);
197 header->ndescv = descs;
198 header->ndesc_length = length;
199 return true;
202 static bool
203 SendObj_Invoke(NPObject *npobj, NPIdentifier methodName,
204 const NPVariant *args, uint32_t argCount, NPVariant *result)
206 struct SendObj *obj = (struct SendObj *) npobj;
207 print(("Obj_Invoke %i\n", argCount));
208 PrintIdentifier(methodName);
209 if(methodName == ident_send) {
210 if(argCount == 2 &&
211 NPVARIANT_IS_STRING(args[0]) &&
212 NPVARIANT_IS_OBJECT(args[1])) {
213 NPString message_data = NPVARIANT_TO_STRING(args[0]);
214 NPObject *caps_array = NPVARIANT_TO_OBJECT(args[1]);
215 struct NaClNrdXferEffector effector;
216 if(!NaClNrdXferEffectorCtor(&effector, obj->desc))
217 return false;
218 struct NaClImcMsgIoVec iovec;
219 struct NaClImcTypedMsgHdr header;
220 if(!ConvertDescArray(obj->plugin, caps_array, &header))
221 return false;
222 /* TODO: allow sending any data, not just UTF-8 */
223 iovec.base = (char *) message_data.utf8characters;
224 iovec.length = message_data.utf8length;
225 header.iov = &iovec;
226 header.iov_length = 1;
227 header.flags = 0;
228 NaClImcSendTypedMessage(obj->desc, (struct NaClDescEffector *) &effector,
229 &header, 0);
230 int i;
231 for(i = 0; i < header.ndesc_length; i++)
232 NaClDescUnref(header.ndescv[i]);
233 free(header.ndescv);
234 effector.base.vtbl->Dtor(&effector.base);
236 VOID_TO_NPVARIANT(*result);
237 return true;
239 return false;
242 static NPClass SendObj_Vtable = {
243 .structVersion = NP_CLASS_STRUCT_VERSION,
244 .allocate = SendObj_Allocate,
245 .deallocate = SendObj_Deallocate,
246 .invalidate = NULL,
247 .hasMethod = SendObj_HasMethod,
248 .invoke = SendObj_Invoke,
249 .invokeDefault = NULL,
250 .hasProperty = NoOp_HasProperty,
251 .getProperty = NoOp_GetProperty,
252 .setProperty = NULL,
253 .removeProperty = NULL,
257 struct LauncherObj {
258 struct NPObject header;
259 NPP plugin;
262 static NPObject *
263 LauncherObj_Allocate(NPP npp, NPClass *npclass)
265 print(("Obj_Allocate\n"));
266 struct LauncherObj *obj = malloc(sizeof(struct LauncherObj));
267 obj->plugin = npp;
268 return &obj->header;
271 static bool
272 LauncherObj_HasMethod(NPObject* obj, NPIdentifier methodName)
274 print(("Obj_HasMethod %i\n", methodName == ident_launch));
275 PrintIdentifier(methodName);
276 return (methodName == ident_launch ||
277 methodName == ident_get_file);
280 struct ReceiveCallbackArgs {
281 NPP plugin;
282 NPObject *callback;
283 char buffer[1024];
284 int size;
287 static void
288 DoReceiveCallback(void *handle)
290 struct ReceiveCallbackArgs *call = handle;
291 NPVariant args[1];
292 NPVariant result;
293 /* TODO: don't assume this data is UTF-8 */
294 STRINGN_TO_NPVARIANT(call->buffer, call->size, args[0]);
295 NPN_InvokeDefault(call->plugin, call->callback, args, 1, &result);
296 NPN_ReleaseVariantValue(&result);
297 free(call);
298 /* We don't do NPN_ReleaseObject() on the callback here because we
299 couldn't do NPN_RetainObject() earlier. */
302 struct ReceiveThreadArgs {
303 NPP plugin;
304 NPObject *callback;
305 struct NaClDesc *desc;
308 static void *
309 ReceiveThread(void *handle)
311 /* We create one thread for every socket we listen on. It would be
312 more efficient to have one thread using poll(). NPAPI doesn't
313 provide a way for us to share the browser's event loop. */
314 struct ReceiveThreadArgs *args = handle;
315 struct NaClNrdXferEffector effector;
316 if(!NaClNrdXferEffectorCtor(&effector, args->desc))
317 return NULL;
318 while(true) {
319 struct ReceiveCallbackArgs *call =
320 malloc(sizeof(struct ReceiveCallbackArgs));
321 if(call == NULL)
322 break;
323 struct NaClImcMsgIoVec iovec;
324 struct NaClImcTypedMsgHdr header;
325 iovec.base = call->buffer;
326 iovec.length = sizeof(call->buffer);
327 header.iov = &iovec;
328 header.iov_length = 1;
329 header.ndescv = NULL;
330 header.ndesc_length = 0;
331 header.flags = 0;
332 int got_bytes =
333 NaClImcRecvTypedMessage(args->desc, (struct NaClDescEffector *) &effector,
334 &header, 0);
335 if(got_bytes < 0) {
336 free(call);
337 break;
339 /* We can't call NPN_RetainObject() here because it is not safe to
340 call it from this thread. We assume that
341 NPN_PluginThreadAsyncCall() schedules functions to be called in
342 order, so that the function is not freed until later. */
343 call->plugin = args->plugin;
344 call->callback = args->callback;
345 call->size = got_bytes;
346 NPN_PluginThreadAsyncCall(args->plugin, DoReceiveCallback, call);
348 effector.base.vtbl->Dtor(&effector.base);
349 NaClDescUnref(args->desc); /* Supposed to be thread safe */
350 NPN_PluginThreadAsyncCall(args->plugin, (void (*)(void *)) NPN_ReleaseObject,
351 args->callback);
352 free(args);
353 return NULL;
356 static bool
357 ConvertArgvArray(NPP plugin, NPObject *array,
358 char ***result_argv, int *result_size)
360 NPVariant length_np;
361 if(!NPN_GetProperty(plugin, array, ident_length, &length_np) ||
362 !NPVARIANT_IS_INT32(length_np)) {
363 NPN_ReleaseVariantValue(&length_np);
364 return false;
366 int length = NPVARIANT_TO_INT32(length_np);
367 NPN_ReleaseVariantValue(&length_np);
369 /* Arbitrary limit */
370 if(length > 100)
371 return false;
373 char **argv = malloc(sizeof(char *) * length);
374 if(argv == NULL)
375 return false;
376 int i;
377 for(i = 0; i < length; i++) {
378 NPIdentifier index_id = NPN_GetIntIdentifier(i);
379 NPVariant element;
380 if(!NPN_GetProperty(plugin, array, index_id, &element) ||
381 !NPVARIANT_IS_STRING(element))
382 return false; /* TODO: clear up */
383 NPString string = NPVARIANT_TO_STRING(element);
384 char *copy = malloc(string.utf8length + 1);
385 if(copy == NULL)
386 return false; /* TODO: clear up */
387 /* TODO: allow sending any data, not just UTF-8 */
388 memcpy(copy, string.utf8characters, string.utf8length);
389 copy[string.utf8length] = 0;
390 argv[i] = copy;
391 NPN_ReleaseVariantValue(&element);
393 *result_argv = argv;
394 *result_size = length;
395 return true;
398 static struct NaClDesc *
399 LaunchProcess(NPP plugin, const char *executable, char **argv, int argv_size,
400 NPObject *receive_callback)
402 int socks[2];
403 if(NaClSocketPair(socks) < 0) {
404 perror("socketpair");
405 return NULL;
407 int pid = fork();
408 if(pid < 0) {
409 NaClClose(socks[0]);
410 NaClClose(socks[1]);
411 perror("fork");
412 return NULL; /* TODO: report error */
414 if(pid == 0) {
415 NaClClose(socks[1]);
416 char fd_buffer[40];
417 snprintf(fd_buffer, sizeof(fd_buffer), "%i:%i", 3, socks[0]);
419 int args_len = 7 + argv_size;
420 const char *args[args_len];
421 int i = 0;
422 args[i++] = "sel_ldr";
423 args[i++] = "-i";
424 args[i++] = fd_buffer;
425 args[i++] = "-f";
426 args[i++] = executable;
427 args[i++] = "--";
428 int j;
429 for(j = 0; j < argv_size; j++)
430 args[i++] = argv[j];
431 args[i++] = NULL;
432 assert(i == args_len);
434 execvp("sel_ldr", (char **) args);
435 perror("exec");
436 _exit(1);
438 NaClClose(socks[0]);
439 if(fcntl(socks[1], F_SETFD, FD_CLOEXEC) < 0) {
440 perror("fcntl");
441 return NULL;
444 struct NaClDesc *desc = malloc(sizeof(struct NaClDescImcDesc));
445 if(!NaClDescImcDescCtor((struct NaClDescImcDesc *) desc, socks[1]))
446 return NULL;
448 struct ReceiveThreadArgs *args = malloc(sizeof(struct ReceiveThreadArgs));
449 if(args == NULL)
450 return NULL;
451 NaClDescRef(desc);
452 NPN_RetainObject(receive_callback);
453 args->plugin = plugin;
454 args->desc = desc;
455 args->callback = receive_callback;
456 pthread_t thread_id;
457 pthread_attr_t attr;
458 pthread_attr_init(&attr);
459 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
460 if(pthread_create(&thread_id, &attr, ReceiveThread, args) != 0)
461 return NULL;
463 return desc;
466 static bool
467 LauncherObj_Invoke(NPObject *npobj, NPIdentifier methodName,
468 const NPVariant *args, uint32_t argCount, NPVariant *result)
470 struct LauncherObj *obj = (struct LauncherObj *) npobj;
471 print(("Obj_Invoke %i\n", argCount));
472 PrintIdentifier(methodName);
473 if(methodName == ident_launch) {
474 if(argCount == 3 &&
475 NPVARIANT_IS_OBJECT(args[0]) &&
476 NPVARIANT_IS_OBJECT(args[1]) &&
477 NPVARIANT_IS_OBJECT(args[2])) {
478 NPObject *file_npobj = NPVARIANT_TO_OBJECT(args[0]);
479 NPObject *argv_obj = NPVARIANT_TO_OBJECT(args[1]);
480 NPObject *receive_callback = NPVARIANT_TO_OBJECT(args[2]);
481 char **argv;
482 int argv_size;
483 if(IsType(file_npobj, &FileObj_Vtable) &&
484 ConvertArgvArray(obj->plugin, argv_obj, &argv, &argv_size)) {
485 struct FileObj *file_obj = (struct FileObj *) file_npobj;
486 struct NaClDesc *desc = LaunchProcess(obj->plugin, file_obj->filename,
487 argv, argv_size,
488 receive_callback);
489 int i;
490 for(i = 0; i < argv_size; i++)
491 free(argv[i]);
492 free(argv);
494 if(desc != NULL) {
495 NPObject *send_npobj = NPN_CreateObject(obj->plugin, &SendObj_Vtable);
496 struct SendObj *send_obj = (struct SendObj *) send_npobj;
497 send_obj->desc = desc;
498 OBJECT_TO_NPVARIANT(send_npobj, *result);
499 return true;
503 VOID_TO_NPVARIANT(*result);
504 return true;
506 if(methodName == ident_get_file) {
507 if(argCount == 2 &&
508 NPVARIANT_IS_STRING(args[0]) &&
509 NPVARIANT_IS_OBJECT(args[1])) {
510 /* TODO: do same-origin check (assuming they are useful). */
511 NPString url = NPVARIANT_TO_STRING(args[0]);
512 NPObject *callback = NPVARIANT_TO_OBJECT(args[1]);
513 NPN_RetainObject(callback);
514 /* TODO: I don't think utf8characters is guaranteed to be
515 NULL-terminated. */
516 NPError err = NPN_GetURLNotify(obj->plugin, url.utf8characters,
517 NULL, callback);
518 print(("NPN_GetUrlNotify = %i\n", err));
520 /* TODO: handle errors */
521 VOID_TO_NPVARIANT(*result);
522 return true;
524 return false;
527 static NPClass LauncherObj_Vtable = {
528 .structVersion = NP_CLASS_STRUCT_VERSION,
529 .allocate = LauncherObj_Allocate,
530 .deallocate = NULL,
531 .invalidate = NULL,
532 .hasMethod = LauncherObj_HasMethod,
533 .invoke = LauncherObj_Invoke,
534 .invokeDefault = NULL,
535 .hasProperty = NoOp_HasProperty,
536 .getProperty = NoOp_GetProperty,
537 .setProperty = NULL,
538 .removeProperty = NULL,
542 NPError
543 NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode,
544 int16 argc, char **argn, char **argv, NPSavedData *saved)
546 return NPERR_NO_ERROR;
549 NPError
550 NPP_Destroy(NPP instance, NPSavedData **save)
552 /* TODO: kill sel_ldr subprocesses, and do waitpid() to avoid a zombie */
553 print(("NPP_Destroy\n"));
554 return NPERR_NO_ERROR;
557 NPError
558 NPP_GetValue(NPP instance, NPPVariable variable, void *result)
560 switch(variable) {
561 case NPPVpluginScriptableNPObject:
563 NPObject *obj = NPN_CreateObject(instance, &LauncherObj_Vtable);
564 *(NPObject **) result = obj;
565 break;
567 default:
568 return NPERR_GENERIC_ERROR;
570 return NPERR_NO_ERROR;
573 NPError
574 NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream,
575 NPBool seekable, uint16 *stype)
577 print(("NPP_NewStream type=%s seekable=%i data=%p url=%s\n", type, seekable,
578 stream->notifyData, stream->url));
579 *stype = NP_ASFILEONLY;
580 return NPERR_NO_ERROR;
583 void
584 NPP_StreamAsFile(NPP instance, NPStream *stream, const char *filename)
586 print(("NPP_StreamAsFile \"%s\"\n", filename));
588 /* TODO: this function calls LOG_FATAL on error; not good */
589 struct NaClDesc *desc = (struct NaClDesc *)
590 NaClDescIoDescOpen((char *) filename, NACL_ABI_O_RDONLY, 0);
591 if(desc == NULL)
592 return;
594 NPObject *callback = stream->notifyData;
595 NPObject *file_npobj = NPN_CreateObject(instance, &FileObj_Vtable);
596 struct FileObj *file_obj = (struct FileObj *) file_npobj;
597 file_obj->desc = desc;
598 file_obj->filename = strdup(filename);
599 if(file_obj->filename == NULL)
600 return;
601 NPVariant result;
602 NPVariant args[1];
603 OBJECT_TO_NPVARIANT(file_npobj, args[0]);
604 bool success = NPN_InvokeDefault(instance, callback, args, 1, &result);
605 print(("NPN_InvokeDefault %i\n", success));
606 NPN_ReleaseVariantValue(&result);
607 NPN_ReleaseObject(callback);
608 NPN_ReleaseObject(file_npobj);
611 void
612 NPP_URLNotify(NPP instance, const char *url, NPReason reason, void *notifyData)
614 /* TODO: handle file not found errors */
615 print(("NPP_URLNotify \"%s\"\n", url));
619 NPError
620 NP_Initialize(NPNetscapeFuncs *funcs, NPPluginFuncs *callbacks)
622 print(("NP_Initialize\n"));
623 g_funcs = *funcs;
624 callbacks->newp = NPP_New;
625 callbacks->destroy = NPP_Destroy;
626 callbacks->getvalue = NPP_GetValue;
627 callbacks->newstream = NPP_NewStream;
628 callbacks->asfile = NPP_StreamAsFile;
629 callbacks->urlnotify = NPP_URLNotify;
631 ident_launch = NPN_GetStringIdentifier("launch");
632 ident_get_file = NPN_GetStringIdentifier("get_file");
633 ident_send = NPN_GetStringIdentifier("send");
634 ident_length = NPN_GetStringIdentifier("length");
636 NaClNrdAllModulesInit();
638 return NPERR_NO_ERROR;
641 char *
642 NP_GetMIMEDescription(void)
644 return "application/x-nacl-imc::Native Client IMC plugin";