#include "config.h" #include IFACE #include #include #include #if HAVE_GLIB /* gtk+ etc.. */ # include #endif #ifdef _WINDOWS #include #endif #if XP_UNIX #include #include #include #include # if HAVE_ATHENA /* motif etc.. */ # include # endif #endif #include "npapi.h" #define fatal(...) \ fprintf (stderr, __VA_ARGS__); \ abort (); #define DEBUG(...) do { \ fprintf (stderr, "PI: "); \ fprintf (stderr, __VA_ARGS__);\ } while (0) #define CMDLINE 8192 typedef unsigned char U8; typedef int I32; typedef unsigned int U32; typedef unsigned long U64; typedef struct { char *mimetype; char *extension; char *description; char *module; int flags; /* 1 == running */ int ref; /* refcnt */ #if HAVE_GLIB GIOChannel *gio; #endif #if HAVE_ATHENA XtInputId xid; #endif #if XP_UNIX int fd; #elif XP_PC error_no_pc errerr; #elif XP_MAC error_no_mac errerr; #endif } interp; #include "interp.h" struct instance { NPP npp; interp *h; }; #ifdef XP_UNIX char * NPP_GetMIMEDescription (void) { return MIMETYPES; } NPError NPP_GetValue (void *future, NPPVariable variable, void *value) { if (variable == NPPVpluginNameString) *((char **) value) = PLUGIN_NAME; else if (variable == NPPVpluginDescriptionString) *((char **) value) = PLUGIN_DESC; else return NPERR_GENERIC_ERROR; return NPERR_NO_ERROR; } #endif static void interp_shutdown(interp *self) { #if HAVE_GLIB if (self->gio) { g_io_channel_close (self->gio); self->gio = 0; } else #endif #if XP_UNIX { if (self->xid) { XtRemoveInput (self->xid); self->xid = 0; } close (self->fd); } #elif XP_PC fatal ("interp_shutdown: XP_PC not yet supported"); #elif XP_MAC fatal ("interp_shutdown: XP_MAC not yet supported"); #endif self->flags &= ~1; } static I32 interp_write (interp *self, void *data, U32 len) { if (self->flags & 1) return write (self->fd, data, len); else return 0; } static I32 interp_read (interp *self, void *data, U32 len) { if (self->flags & 1) { I32 got = read (self->fd, data, len); if (got == 0) interp_shutdown (self); return got; } else return 0; } #define BIAS -2147483647 static U8 cmdline[CMDLINE]; static U8 *cmdp, *rcve; static void handle_cmd(const U32 cmd); #define SND_OK (cmdp <= cmdline + CMDLINE) #define RCV_OK (cmdp <= rcve) static void snd_u8(U8 c) { if (SND_OK) *cmdp++ = c; } static void snd_u32(U32 u) { snd_u8 (u >> 24); snd_u8 (u >> 16); snd_u8 (u >> 8); snd_u8 (u >> 0); } static void snd_u64(U64 u) { snd_u32 (sizeof (U64) <= 4 ? 0 : u >> 32); snd_u32 (u & 0xffffffff); } #define snd_i32(i) snd_u32((U32)(i) - BIAS) #define snd_ptr(p) snd_u64((U64)(p)) static void snd_cmd(const U8 cmd) { cmdp = cmdline; snd_u32 (0); /* length */ snd_u32 (cmd); /* cmd */ } static void snd_blk (const U8 *data, U32 len) { if (!len && data) len = strlen (data); snd_u32 (len); while (len--) snd_u8 (*data++); } static int snd_snd(interp *self) { int len = cmdp - cmdline; if (!SND_OK) return 0; cmdp = cmdline; snd_u32 (len); return interp_write (self, cmdline, len) == len; } static int snd_dyn (interp *self, void *data, U32 length) { return interp_write (self, data, length) == length; } static U8 rcv_u8(void) { return RCV_OK ? *cmdp++ : 0; } static U32 rcv_u32(void) { return (rcv_u8 () << 24) | (rcv_u8 () << 16) | (rcv_u8 () << 8) | (rcv_u8 () << 0); } static U64 rcv_u64(void) { return sizeof (U64) <= 4 ? rcv_u32 () * 0 | rcv_u32 () : (rcv_u32 () << 32) | (rcv_u32 () << 0); } static U32 rcv_str (void **data) { U32 len = rcv_u32 (); U8 *xdata = NPN_MemAlloc (len + 1); *data = xdata; if (xdata) { U32 l = len; while (len--) *xdata++ = rcv_u8 (); *xdata = 0; } return len; } #define rcv_i32() ((I32)(rcv_u32() + BIAS)) #define rcv_ptr() ((void *)rcv_u64 ()) static int rcv_cmd(interp *self, const U8 cmd) { U32 len; U32 rcmd; do { if (interp_read (self, cmdline, 4) != 4) return 0; cmdp = cmdline; rcve = cmdp + 4; len = rcv_u32 () - 4; assert (len < CMDLINE); if (interp_read (self, cmdline, len) != len) return 0; cmdp = cmdline; rcve = cmdp + len; rcmd = rcv_u32 (); DEBUG ("got cmd %c (%c)\n", rcmd, cmd); if (rcmd >= 'a' && rcmd <= 'z') handle_cmd (rcmd); else return rcmd == cmd; } while (cmd); return 0; } static int rcv_dyn (interp *self, void *data, U32 length) { return interp_read (self, data, length) == length; } #define handle_event(self) rcv_cmd ((self), 0) #define rcv_rcv() RCV_OK #if HAVE_ATHENA static void xt_handle_event (XtPointer client_data, int *fid, XtInputId *id) { handle_event ((interp *)client_data); } #endif #if HAVE_GLIB static gboolean glib_handle_event (GIOChannel *source, GIOCondition condition, gpointer data) { handle_event ((interp *)data); } #endif static void interp_init(interp *self, NPP instance) { #if HAVE_ATHENA XtAppContext app; #endif #if XP_UNIX int fd[2]; int res; if (socketpair (AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) { perror ("socketpair"); return; } res = fork (); if (res == 0) { char init[160]; close (fd[0]); fcntl (fd[1], F_SETFD, 0); sprintf (init, "use %s (); %s->init(%d);", self->module, self->module, fd[1]); execl (PERLPATH, PERLPATH, "-e", init, NULL); _exit (255); } else if (res < 0) { perror ("fork"); return; } close (fd[1]); self->fd = fd[0]; #elif XP_PC fatal ("interp_init: XP_PC not yet supported"); #elif XP_MAC fatal ("interp_init: XP_MAC not yet supported"); #endif #if HAVE_ATHENA if (NPN_GetValue (instance, NPNVxtAppContext, &app) == NPERR_NO_ERROR) { self->xid = XtAppAddInput (app, self->fd, (XtPointer)XtInputReadMask, &xt_handle_event, (XtPointer) self); } else #endif #if HAVE_GLIB /* !xt && unix => it must be gtk */ if (1) { self->gio = g_io_channel_unix_new (self->fd); g_io_add_watch (self->gio, G_IO_IN, glib_handle_event, (gpointer) self); } else #endif { fatal ("interp_init: browser ui toolkit not supported"); close (fd[0]); return; } self->flags |= 1; } static interp * interp_find(const char *mimetype) { interp *i = perl_interp; while (strcmp (i->mimetype, mimetype)) { i++; if (!i->mimetype) return 0; } return i; } static interp * interp_get(const char *mimetype, NPP instance) { interp *i = interp_find (mimetype); if (i) { if (!i->flags & 1) { interp_init (i, instance); if (i->flags & 1) { snd_cmd ('I'); snd_u32 (1); /* version */ #if defined(XP_UNIX) snd_blk ("UNIX", 4); #elif defined (XP_PC) snd_blk ("PC", 2); #elif defined(macintosh) snd_blk ("MAC", 3); #else snd_blk ("", 1); #endif snd_blk (OSNAME, 0); snd_blk (OSVERS, 0); snd_blk (NPN_UserAgent (instance), 0); if (!snd_snd (i)) interp_shutdown (i); } } if (i->flags & 1) return i; } return 0; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_Initialize: * Provides global initialization for a plug-in, and returns an error value. * * This function is called once when a plug-in is loaded, before the first instance * is created. You should allocate any memory or resources shared by all * instances of your plug-in at this time. After the last instance has been deleted, * NPP_Shutdown will be called, where you can release any memory or * resources allocated by NPP_Initialize. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_Initialize (void) { return NPERR_NO_ERROR; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_GetJavaClass: * New in Netscape Navigator 3.0. * * NPP_GetJavaClass is called during initialization to ask your plugin * what its associated Java class is. If you don't have one, just return * NULL. Otherwise, use the javah-generated "use_" function to both * initialize your class and return it. If you can't find your class, an * error will be signalled by "use_" and will cause the Navigator to * complain to the user. +++++++++++++++++++++++++++++++++++++++++++++++++*/ jref NPP_GetJavaClass (void) { return NULL; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_Shutdown: * Provides global deinitialization for a plug-in. * * This function is called once after the last instance of your plug-in is destroyed. * Use this function to release any memory or resources shared across all * instances of your plug-in. You should be a good citizen and declare that * you're not using your java class any more. This allows java to unload * it, freeing up memory. +++++++++++++++++++++++++++++++++++++++++++++++++*/ void NPP_Shutdown (void) { /* now a nop ;) */ } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_New: * Creates a new instance of a plug-in and returns an error value. * * NPP_New creates a new instance of your plug-in with MIME type specified * by pluginType. The parameter mode is NP_EMBED if the instance was created * by an EMBED tag, or NP_FULL if the instance was created by a separate file. * You can allocate any instance-specific private data in instance->pdata at this * time. The NPP pointer is valid until the instance is destroyed. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_New (NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char *argn[], char *argv[], NPSavedData * saved) { int i; struct instance *self; interp *perl; if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; perl = interp_get (pluginType, instance); if (!perl) return NPERR_GENERIC_ERROR; self = NPN_MemAlloc (sizeof (struct instance)); if (!self) return NPERR_OUT_OF_MEMORY_ERROR; self->npp = instance; self->h = perl; snd_cmd ('+'); snd_ptr (self); snd_blk (pluginType, strlen (pluginType)); snd_u32 (mode); if (saved) snd_blk (saved->buf, saved->len); else snd_blk ("", 0); snd_u32 (argc); for (i = 0; i < argc; i++) { snd_blk (argn[i], strlen (argn[i])); snd_blk (argv[i], strlen (argv[i])); } if (!snd_snd (self->h)) { NPN_MemFree (self); return NPERR_GENERIC_ERROR; } instance->pdata = (void *) self; return NPERR_NO_ERROR; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_Destroy: * Deletes a specific instance of a plug-in and returns an error value. * NPP_Destroy is called when a plug-in instance is deleted, typically because the * user has left the page containing the instance, closed the window, or quit the * application. You should delete any private instance-specific information stored * in instance->pdata. If the instance being deleted is the last instance created * by your plug-in, NPP_Shutdown will subsequently be called, where you can * delete any data allocated in NPP_Initialize to be shared by all your plug-in's * instances. Note that you should not perform any graphics operations in * NPP_Destroy as the instance's window is no longer guaranteed to be valid. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_Destroy (NPP instance, NPSavedData **save) { struct instance *self; U32 len; if (!self) return NPERR_INVALID_INSTANCE_ERROR; self = (struct instance *)instance->pdata; snd_cmd ('-'); snd_ptr (self); if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; if (!rcv_cmd (self->h, '-')) return NPERR_GENERIC_ERROR; len = rcv_u32 (); if (!rcv_rcv ()) return NPERR_GENERIC_ERROR; if (len) { NPSavedData *buf = (NPSavedData *)NPN_MemAlloc (sizeof (NPSavedData)); buf->len = len; buf->buf = NPN_MemAlloc (len); if (!rcv_dyn (self->h, buf->buf, len)) return NPERR_GENERIC_ERROR; *save = buf; } NPN_MemFree (self); instance->pdata = NULL; return NPERR_NO_ERROR; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_SetWindow: * Sets the window in which a plug-in draws, and returns an error value. * * NPP_SetWindow informs the plug-in instance specified by instance of the * the window denoted by window in which the instance draws. This NPWindow * pointer is valid for the life of the instance, or until NPP_SetWindow is called * again with a different value. Subsequent calls to NPP_SetWindow for a given * instance typically indicate that the window has been resized. If either window * or window->window are NULL, the plug-in must not perform any additional * graphics operations on the window and should free any resources associated * with the window. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_SetWindow (NPP instance, NPWindow * window) { struct instance *self; if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; self = (struct instance *)instance->pdata; snd_cmd ('X'); snd_ptr (self); snd_ptr (window->window); snd_i32 (window->x); snd_i32 (window->y); snd_i32 (window->width); snd_i32 (window->height); #ifdef XP_UNIX { NPSetWindowCallbackStruct *ws = (NPSetWindowCallbackStruct *)window->ws_info; snd_i32 (ws->type); snd_u32 (ws->depth); } #endif /* NPRect clipRect, void *ws_info */ if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; if (!rcv_cmd (self->h, 'X')) return NPERR_GENERIC_ERROR; return rcv_u32 (); } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_NewStream: * Notifies an instance of a new data stream and returns an error value. * * NPP_NewStream notifies the instance denoted by instance of the creation of * a new stream specifed by stream. The NPStream* pointer is valid until the * stream is destroyed. The MIME type of the stream is provided by the * parameter type. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_NewStream (NPP instance, NPMIMEType type, NPStream * stream, NPBool seekable, uint16 * stype) { struct instance *self; NPError err; if (!instance) return NPERR_INVALID_INSTANCE_ERROR; self = (struct instance *)instance->pdata; snd_cmd ('N'); snd_ptr (self); snd_blk (type, 0); snd_ptr (stream); snd_blk (stream->url, 0); snd_u32 (stream->end); snd_u32 (stream->lastmodified); snd_u32 ((U32)stream->notifyData); snd_u32 (seekable); if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; if (!rcv_cmd (self->h, 'N')) return NPERR_GENERIC_ERROR; err = rcv_u32 (); if (err) return err; *stype = rcv_u32 (); return NPERR_NO_ERROR; } /* PLUGIN DEVELOPERS: * These next 2 functions are directly relevant in a plug-in which * handles the data in a streaming manner. If you want zero bytes * because no buffer space is YET available, return 0. As long as * the stream has not been written to the plugin, Navigator will * continue trying to send bytes. If the plugin doesn't want them, * just return some large number from NPP_WriteReady(), and * ignore them in NPP_Write(). For a NP_ASFILE stream, they are * still called but can safely be ignored using this strategy. */ /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_WriteReady: * Returns the maximum number of bytes that an instance is prepared to accept * from the stream. * * NPP_WriteReady determines the maximum number of bytes that the * instance will consume from the stream in a subsequent call NPP_Write. This * function allows Netscape to only send as much data to the instance as the * instance is capable of handling at a time, allowing more efficient use of * resources within both Netscape and the plug-in. +++++++++++++++++++++++++++++++++++++++++++++++++*/ int32 NPP_WriteReady (NPP instance, NPStream * stream) { if (instance) { struct instance *self = (struct instance *)instance->pdata; snd_cmd ('R'); snd_ptr (self); snd_ptr (stream); if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; if (!rcv_cmd (self->h, 'R')) return NPERR_GENERIC_ERROR; return rcv_u32 (); } return 0; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_Write: * Delivers data from a stream and returns the number of bytes written. * * NPP_Write is called after a call to NPP_NewStream in which the plug-in * requested a normal-mode stream, in which the data in the stream is delivered * progressively over a series of calls to NPP_WriteReady and NPP_Write. The * function delivers a buffer buf of len bytes of data from the stream identified * by stream to the instance. The parameter offset is the logical position of * buf from the beginning of the data in the stream. * * The function returns the number of bytes written (consumed by the instance). * A negative return value causes an error on the stream, which will * subsequently be destroyed via a call to NPP_DestroyStream. * * Note that a plug-in must consume at least as many bytes as it indicated in the * preceeding NPP_WriteReady call. All data consumed must be either processed * immediately or copied to memory allocated by the plug-in: the buf parameter * is not persistent. +++++++++++++++++++++++++++++++++++++++++++++++++*/ int32 NPP_Write (NPP instance, NPStream * stream, int32 offset, int32 len, void *buffer) { if (instance) { struct instance *self = (struct instance *)instance->pdata; snd_cmd ('W'); snd_ptr (self); snd_ptr (stream); snd_i32 (offset); snd_i32 (len); if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; if (!snd_dyn (self->h, buffer, len)) return NPERR_GENERIC_ERROR; if (!rcv_cmd (self->h, 'W')) return NPERR_GENERIC_ERROR; return rcv_u32 (); } return len; /* The number of bytes accepted */ } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_DestroyStream: * Indicates the closure and deletion of a stream, and returns an error value. * * The NPP_DestroyStream function is called when the stream identified by * stream for the plug-in instance denoted by instance will be destroyed. You * should delete any private data allocated in stream->pdata at this time. +++++++++++++++++++++++++++++++++++++++++++++++++*/ NPError NPP_DestroyStream (NPP instance, NPStream * stream, NPError reason) { struct instance *self; if (!instance) return NPERR_INVALID_INSTANCE_ERROR; self = (struct instance *)instance->pdata; snd_cmd ('D'); snd_ptr (self); snd_ptr (stream); snd_u32 (reason); if (!snd_snd (self->h)) return NPERR_GENERIC_ERROR; return NPERR_NO_ERROR; } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_StreamAsFile: * Provides a local file name for the data from a stream. * * NPP_StreamAsFile provides the instance with a full path to a local file, * identified by fname, for the stream specified by stream. NPP_StreamAsFile is * called as a result of the plug-in requesting mode NP_ASFILEONLY or * NP_ASFILE in a previous call to NPP_NewStream. If an error occurs while * retrieving the data or writing the file, fname may be NULL. +++++++++++++++++++++++++++++++++++++++++++++++++*/ void NPP_StreamAsFile (NPP instance, NPStream * stream, const char *fname) { if (instance) { struct instance *self = (struct instance *)instance->pdata; snd_cmd ('/'); snd_ptr (self); snd_ptr (stream); snd_blk (fname, 0); snd_snd (self->h); } } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_Print: +++++++++++++++++++++++++++++++++++++++++++++++++*/ void NPP_Print (NPP instance, NPPrint * printInfo) { if (printInfo == NULL) return; if (instance != NULL) { if (printInfo->mode == NP_FULL) { /* * PLUGIN DEVELOPERS: * If your plugin would like to take over * printing completely when it is in full-screen mode, * set printInfo->pluginPrinted to TRUE and print your * plugin as you see fit. If your plugin wants Netscape * to handle printing in this case, set * printInfo->pluginPrinted to FALSE (the default) and * do nothing. If you do want to handle printing * yourself, printOne is true if the print button * (as opposed to the print menu) was clicked. * On the Macintosh, platformPrint is a THPrint; on * Windows, platformPrint is a structure * (defined in npapi.h) containing the printer name, port, * etc. */ void *platformPrint = printInfo->print.fullPrint.platformPrint; NPBool printOne = printInfo->print.fullPrint.printOne; /* Do the default */ printInfo->print.fullPrint.pluginPrinted = FALSE; } else { /* If not fullscreen, we must be embedded */ /* * PLUGIN DEVELOPERS: * If your plugin is embedded, or is full-screen * but you returned false in pluginPrinted above, NPP_Print * will be called with mode == NP_EMBED. The NPWindow * in the printInfo gives the location and dimensions of * the embedded plugin on the printed page. On the * Macintosh, platformPrint is the printer port; on * Windows, platformPrint is the handle to the printing * device context. */ NPWindow *printWindow = &(printInfo->print.embedPrint.window); void *platformPrint = printInfo->print.embedPrint.platformPrint; } } } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_URLNotify: * Notifies the instance of the completion of a URL request. * * NPP_URLNotify is called when Netscape completes a NPN_GetURLNotify or * NPN_PostURLNotify request, to inform the plug-in that the request, * identified by url, has completed for the reason specified by reason. The most * common reason code is NPRES_DONE, indicating simply that the request * completed normally. Other possible reason codes are NPRES_USER_BREAK, * indicating that the request was halted due to a user action (for example, * clicking the "Stop" button), and NPRES_NETWORK_ERR, indicating that the * request could not be completed (for example, because the URL could not be * found). The complete list of reason codes is found in npapi.h. * * The parameter notifyData is the same plug-in-private value passed as an * argument to the corresponding NPN_GetURLNotify or NPN_PostURLNotify * call, and can be used by your plug-in to uniquely identify the request. +++++++++++++++++++++++++++++++++++++++++++++++++*/ void NPP_URLNotify (NPP instance, const char *url, NPReason reason, void *notifyData) { if (instance) { struct instance *self = (struct instance *)instance->pdata; snd_cmd ('U'); snd_blk (url, 0); snd_i32 ((I32) reason); snd_ptr (notifyData); snd_snd (self->h); } } /*+++++++++++++++++++++++++++++++++++++++++++++++++ * NPP_HandleEvent: * Mac-only, but stub must be present for Windows * Delivers a platform-specific event to the instance. * * On the Macintosh, event is a pointer to a standard Macintosh EventRecord. * All standard event types are passed to the instance as appropriate. In general, * return TRUE if you handle the event and FALSE if you ignore the event. +++++++++++++++++++++++++++++++++++++++++++++++++*/ #ifndef XP_UNIX int16 NPP_HandleEvent (NPP instance, void *event) { return FALSE; } #endif /*******************************************************************************/ static void handle_cmd(const U32 cmd) { struct instance *self; NPStream *stream; NPError error; U32 length; I32 result; DEBUG ("received cmd <%c>\n", cmd); switch (cmd) { case 'd': self = rcv_ptr (); stream = rcv_ptr (); error = rcv_u32 (); if (rcv_rcv ()) NPN_DestroyStream (self->npp, stream, error); break; case 'n': { void *mimetype, *target; self = rcv_ptr (); rcv_str (&mimetype); rcv_str (&target); if (rcv_rcv ()) { snd_cmd ('n'); error = NPN_NewStream (self->npp, mimetype, target, &stream); NPN_MemFree (mimetype); NPN_MemFree (target); snd_u32 (error); if (!error) snd_ptr (stream); snd_snd (self->h); } } break; case 'w': self = rcv_ptr (); stream = rcv_ptr (); length = rcv_u32 (); result = -1; if (rcv_rcv ()) { void *data = NPN_MemAlloc (length); snd_cmd ('w'); if (!data) fatal ("out of memory during write memory"); if (rcv_dyn (self->h, data, length)) result = NPN_Write (self->npp, stream, length, data); NPN_MemFree (data); snd_i32 (result); snd_snd (self->h); } break; case 'u': { void *url, *target, *notify; self = rcv_ptr (); rcv_str (&url); rcv_str (&target); notify = rcv_ptr (); snd_cmd ('u'); snd_i32 (NPN_GetURLNotify (self->npp, url, target, notify)); snd_snd (self->h); DEBUG ("NPN_GetURLNotify(*,%s,%s,%p)", url, target, notify); NPN_MemFree (url); NPN_MemFree (target); } case 'p': { void *url, *target, *notify, *buf; uint32 len; int file; self = rcv_ptr (); rcv_str (&url); rcv_str (&target); notify = rcv_ptr (); len = rcv_blk (&buf); file = rcv_u32 (); snd_cmd ('p'); snd_i32 (NPN_PostURLNotify (self->npp, url, target, len, buf, file, notify)); snd_snd (self->h); DEBUG ("NPN_PostURLNotify(*,%s,%s,[%d bytes],%p)", url, target, len, file, notify); NPN_MemFree (url); NPN_MemFree (target); NPN_MemFree (buf); } break; default: fatal ("unable to handle command %c", cmd); } }