1 /* gcjwebplugin.cc -- web browser plugin to execute Java applets
2    Copyright (C) 2003, 2004, 2006  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 // System includes.
39 #include <errno.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <unistd.h>
45 
46 // Netscape plugin API includes.
47 #include <npapi.h>
48 #if (((NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR) < 20)
49 #include <npupp.h>
50 #else
51 #include <npfunctions.h>
52 #endif
53 
54 // GLib includes.
55 #include <glib.h>
56 #include <glib/gstdio.h>
57 
58 // GTK includes.
59 #include <gtk/gtk.h>
60 
61 // gcjwebplugin includes.
62 #include "config.h"
63 
64 // Documentbase retrieval includes.
65 #include <nsIPluginInstance.h>
66 #include <nsIPluginInstancePeer.h>
67 #include <nsIPluginTagInfo2.h>
68 
69 // Debugging macros.
70 #define PLUGIN_DEBUG(message)                                           \
71   g_print ("GCJ PLUGIN: thread %p: %s\n", g_thread_self (), message)
72 
73 #define PLUGIN_DEBUG_TWO(first, second)                                 \
74   g_print ("GCJ PLUGIN: thread %p: %s %s\n", g_thread_self (),          \
75            first, second)
76 
77 // Error reporting macros.
78 #define PLUGIN_ERROR(message)                                       \
79   g_printerr ("%s:%d: thread %p: Error: %s\n", __FILE__, __LINE__,  \
80               g_thread_self (), message)
81 
82 #define PLUGIN_ERROR_TWO(first, second)                                 \
83   g_printerr ("%s:%d: thread %p: Error: %s: %s\n", __FILE__, __LINE__,  \
84               g_thread_self (), first, second)
85 
86 #define PLUGIN_ERROR_THREE(first, second, third)                        \
87   g_printerr ("%s:%d: thread %p: Error: %s: %s: %s\n", __FILE__,        \
88               __LINE__, g_thread_self (), first, second, third)
89 
90 // Plugin information passed to about:plugins.
91 #define PLUGIN_NAME "GCJ Web Browser Plugin"
92 #define PLUGIN_DESC "The " PLUGIN_NAME " executes Java applets."
93 #define PLUGIN_MIME_DESC                                           \
94   "application/x-java-vm:class,jar:GCJ;"                           \
95   "application/x-java-applet:class,jar:GCJ;"                       \
96   "application/x-java-applet;version=1.1:class,jar:GCJ;"           \
97   "application/x-java-applet;version=1.1.1:class,jar:GCJ;"         \
98   "application/x-java-applet;version=1.1.2:class,jar:GCJ;"         \
99   "application/x-java-applet;version=1.1.3:class,jar:GCJ;"         \
100   "application/x-java-applet;version=1.2:class,jar:GCJ;"           \
101   "application/x-java-applet;version=1.2.1:class,jar:GCJ;"         \
102   "application/x-java-applet;version=1.2.2:class,jar:GCJ;"         \
103   "application/x-java-applet;version=1.3:class,jar:GCJ;"           \
104   "application/x-java-applet;version=1.3.1:class,jar:GCJ;"         \
105   "application/x-java-applet;version=1.4:class,jar:GCJ;"           \
106   "application/x-java-applet;version=1.4.1:class,jar:GCJ;"         \
107   "application/x-java-applet;version=1.4.2:class,jar:GCJ;"         \
108   "application/x-java-applet;jpi-version=1.4.2_01:class,jar:GCJ;"  \
109   "application/x-java-bean:class,jar:GCJ;"                         \
110   "application/x-java-bean;version=1.1:class,jar:GCJ;"             \
111   "application/x-java-bean;version=1.1.1:class,jar:GCJ;"           \
112   "application/x-java-bean;version=1.1.2:class,jar:GCJ;"           \
113   "application/x-java-bean;version=1.1.3:class,jar:GCJ;"           \
114   "application/x-java-bean;version=1.2:class,jar:GCJ;"             \
115   "application/x-java-bean;version=1.2.1:class,jar:GCJ;"           \
116   "application/x-java-bean;version=1.2.2:class,jar:GCJ;"           \
117   "application/x-java-bean;version=1.3:class,jar:GCJ;"             \
118   "application/x-java-bean;version=1.3.1:class,jar:GCJ;"           \
119   "application/x-java-bean;version=1.4:class,jar:GCJ;"             \
120   "application/x-java-bean;version=1.4.1:class,jar:GCJ;"           \
121   "application/x-java-bean;version=1.4.2:class,jar:GCJ;"           \
122   "application/x-java-bean;jpi-version=1.4.2_01:class,jar:GCJ;"
123 #define PLUGIN_URL NS_INLINE_PLUGIN_CONTRACTID_PREFIX NS_JVM_MIME_TYPE
124 #define PLUGIN_MIME_TYPE "application/x-java-vm"
125 #define PLUGIN_FILE_EXTS "class,jar,zip"
126 #define PLUGIN_MIME_COUNT 1
127 
128 // Security dialog messages.
129 #define RESPONSE_TRUST_APPLET "Trust Applet"
130 #define RESPONSE_TRUST_APPLET_ADD_TO_LIST "Trust Applet and Add to Whitelist"
131 #define SECURITY_WARNING                                        \
132   "%s wants to load an applet.\n"                               \
133   "GNU Classpath's security implementation is not complete.\n"  \
134   "HOSTILE APPLETS WILL STEAL AND/OR DESTROY YOUR DATA!\n"
135 #define SECURITY_DESCRIPTION                                            \
136   "Click \"Cancel\" if you do not trust the source of this applet.\n"   \
137   "Click \"Trust Applet\" to load and run this applet now.\n"           \
138   "Click \"Trust Applet and Add To Whitelist\" to always load"          \
139   " and run this applet from now on, without asking.\n"                 \
140   "The whitelist is a list of the URLs from which you trust"            \
141   " applets.\n"                                                         \
142   "Your whitelist file is \" %s \"."
143 #define FAILURE_MESSAGE                                                 \
144   "This page wants to load an applet.\n"                                \
145   "The appletviewer is missing or not installed properly in \""         \
146   APPLETVIEWER_EXECUTABLE "\"."
147 
148 // Documentbase retrieval required definition.
149 static NS_DEFINE_IID (kIPluginTagInfo2IID, NS_IPLUGINTAGINFO2_IID);
150 
151 // Browser function table.
152 static NPNetscapeFuncs browserFunctions;
153 
154 // Data directory for plugin.
155 static gchar* data_directory;
156 
157 // Whitelist filename
158 static gchar* whitelist_filename;
159 
160 // Keeps track of initialization. NP_Initialize should only be
161 // called once.
162 gboolean initialized = false;
163 
164 // GCJPluginData stores all the data associated with a single plugin
165 // instance.  A separate plugin instance is created for each <APPLET>
166 // tag.  For now, each plugin instance spawns its own applet viewer
167 // process but this may need to change if we find pages containing
168 // multiple applets that expect to be running in the same VM.
169 struct GCJPluginData
170 {
171   // A unique identifier for this plugin window.
172   gchar* instance_string;
173   // Applet viewer input pipe name.
174   gchar* in_pipe_name;
175   // Applet viewer input channel.
176   GIOChannel* in_from_appletviewer;
177   // Applet viewer input watch source.
178   gint in_watch_source;
179   // Applet viewer output pipe name.
180   gchar* out_pipe_name;
181   // Applet viewer output channel.
182   GIOChannel* out_to_appletviewer;
183   // Applet viewer output watch source.
184   gint out_watch_source;
185   // Mutex to protect appletviewer_alive.
186   GMutex* appletviewer_mutex;
187   // Back-pointer to the plugin instance to which this data belongs.
188   // This should not be freed but instead simply set to NULL.
189   NPP owner;
190   // FALSE if the applet viewer process has died.  All code
191   // communicating with the applet viewer should check this flag
192   // before attempting to read from/write to the applet viewer pipes.
193   gboolean appletviewer_alive;
194   // The address of the plugin window.  This should not be freed but
195   // instead simply set to NULL.
196   gpointer window_handle;
197   // The last plugin window width sent to us by the browser.
198   guint32 window_width;
199   // The last plugin window height sent to us by the browser.
200   guint32 window_height;
201 };
202 
203 // Documentbase retrieval type-punning union.
204 typedef union
205 {
206   void** void_field;
207   nsIPluginTagInfo2** info_field;
208 } info_union;
209 
210 // Static instance helper functions.
211 // Have the browser allocate a new GCJPluginData structure.
212 static void plugin_data_new (GCJPluginData** data);
213 // Documentbase retrieval.
214 static gchar* plugin_get_documentbase (NPP instance);
215 // plugin failure handling.
216 static bool plugin_failed ();
217 // Whitelist handling.
218 static bool plugin_user_trusts_documentbase (char* documentbase);
219 static bool plugin_ask_user_about_documentbase (char* documentbase);
220 static void plugin_add_documentbase_to_whitelist (char* documentbase);
221 // Callback used to monitor input pipe status.
222 static gboolean plugin_in_pipe_callback (GIOChannel* source,
223                                          GIOCondition condition,
224                                          gpointer plugin_data);
225 // Callback used to monitor output pipe status.
226 static gboolean plugin_out_pipe_callback (GIOChannel* source,
227                                           GIOCondition condition,
228                                           gpointer plugin_data);
229 static NPError plugin_start_appletviewer (GCJPluginData* data);
230 static gchar* plugin_create_applet_tag (int16 argc, char* argn[],
231                                         char* argv[]);
232 static void plugin_send_message_to_appletviewer (GCJPluginData* data,
233                                                  gchar const* message);
234 static void plugin_stop_appletviewer (GCJPluginData* data);
235 // Uninitialize GCJPluginData structure and delete pipes.
236 static void plugin_data_destroy (GCJPluginData** data);
237 
238 // Global instance counter.
239 // Mutex to protect plugin_instance_counter.
240 static GMutex* plugin_instance_mutex = NULL;
241 // A counter used to create uniquely named pipes.
242 static gulong plugin_instance_counter = 0;
243 // The user's documentbase whitelist.
244 static GIOChannel* whitelist_file = NULL;
245 // A global variable for reporting GLib errors.  This must be free'd
246 // and set to NULL after each use.
247 static GError* channel_error = NULL;
248 
249 // Functions prefixed by GCJ_ are instance functions.  They are called
250 // by the browser and operate on instances of GCJPluginData.
251 // Functions prefixed by plugin_ are static helper functions.
252 // Functions prefixed by NP_ are factory functions.  They are called
253 // by the browser and provide functionality needed to create plugin
254 // instances.
255 
256 // INSTANCE FUNCTIONS
257 
258 // Creates a new gcjwebplugin instance.  This function creates a
259 // GCJPluginData* and stores it in instance->pdata.  The following
260 // GCJPluginData fiels are initialized: instance_string, in_pipe_name,
261 // in_from_appletviewer, in_watch_source, out_pipe_name,
262 // out_to_appletviewer, out_watch_source, appletviewer_mutex, owner,
263 // appletviewer_alive.  In addition two pipe files are created.  All
264 // of those fields must be properly destroyed, and the pipes deleted,
265 // by GCJ_Destroy.  If an error occurs during initialization then this
266 // function will free anything that's been allocated so far, set
267 // instance->pdata to NULL and return an error code.
268 NPError
GCJ_New(NPMIMEType pluginType,NPP instance,uint16 mode,int16 argc,char * argn[],char * argv[],NPSavedData * saved)269 GCJ_New (NPMIMEType pluginType, NPP instance, uint16 mode,
270          int16 argc, char* argn[], char* argv[],
271          NPSavedData* saved)
272 {
273   PLUGIN_DEBUG ("GCJ_New");
274 
275   NPError np_error = NPERR_NO_ERROR;
276   GCJPluginData* data = NULL;
277 
278   gchar* documentbase = NULL;
279   gchar* read_message = NULL;
280   gchar* applet_tag = NULL;
281   gchar* tag_message = NULL;
282 
283   if (!instance)
284     {
285       PLUGIN_ERROR ("Browser-provided instance pointer is NULL.");
286       np_error = NPERR_INVALID_INSTANCE_ERROR;
287       goto cleanup_done;
288     }
289 
290   // data
291   plugin_data_new (&data);
292   if (data == NULL)
293     {
294       PLUGIN_ERROR ("Failed to allocate plugin data.");
295       np_error = NPERR_OUT_OF_MEMORY_ERROR;
296       goto cleanup_done;
297     }
298 
299   // Initialize data->instance_string.
300   //
301   // instance_string should be unique for this process so we use a
302   // combination of getpid and plugin_instance_counter.
303   //
304   // Critical region.  Reference and increment plugin_instance_counter
305   // global.
306   g_mutex_lock (plugin_instance_mutex);
307 
308   // data->instance_string
309   data->instance_string = g_strdup_printf ("instance-%d-%ld",
310                                            getpid (),
311                                            plugin_instance_counter++);
312 
313   g_mutex_unlock (plugin_instance_mutex);
314 
315   // data->appletviewer_mutex
316   data->appletviewer_mutex = g_mutex_new ();
317 
318   // Documentbase retrieval.
319   documentbase = plugin_get_documentbase (instance);
320   if (!documentbase)
321     {
322       PLUGIN_ERROR ("Documentbase retrieval failed."
323                     " Browser not Mozilla-based?");
324       goto cleanup_appletviewer_mutex;
325     }
326 
327   // Open the user's documentbase whitelist.
328   whitelist_file = g_io_channel_new_file (whitelist_filename,
329                                           "a+", &channel_error);
330   if (!whitelist_file)
331     {
332       if (channel_error)
333         {
334           PLUGIN_ERROR_THREE ("Failed to open whitelist file",
335                               whitelist_filename,
336                               channel_error->message);
337           g_error_free (channel_error);
338           channel_error = NULL;
339         }
340       else
341         PLUGIN_ERROR_TWO ("Failed to open whitelist file",
342                           whitelist_filename);
343 
344       return NPERR_GENERIC_ERROR;
345     }
346 
347   if (!plugin_user_trusts_documentbase (documentbase))
348     {
349       PLUGIN_ERROR ("User does not trust applet.");
350       np_error = NPERR_GENERIC_ERROR;
351       goto cleanup_appletviewer_mutex;
352     }
353 
354   // Create appletviewer-to-plugin pipe which we refer to as the input
355   // pipe.
356 
357   // data->in_pipe_name
358   data->in_pipe_name = g_strdup_printf ("%s/gcj-%s-appletviewer-to-plugin",
359                                          data_directory, data->instance_string);
360   if (!data->in_pipe_name)
361     {
362       PLUGIN_ERROR ("Failed to create input pipe name.");
363       np_error = NPERR_OUT_OF_MEMORY_ERROR;
364       // If data->in_pipe_name is NULL then the g_free at
365       // cleanup_in_pipe_name will simply return.
366       goto cleanup_in_pipe_name;
367     }
368 
369   if (mkfifo (data->in_pipe_name, 0700) == -1 && errno != EEXIST)
370     {
371       PLUGIN_ERROR_TWO ("Failed to create input pipe", strerror (errno));
372       np_error = NPERR_GENERIC_ERROR;
373       goto cleanup_in_pipe_name;
374     }
375 
376   // Create plugin-to-appletviewer pipe which we refer to as the
377   // output pipe.
378 
379   // data->out_pipe_name
380   data->out_pipe_name = g_strdup_printf ("%s/gcj-%s-plugin-to-appletviewer",
381                                          data_directory, data->instance_string);
382 
383   if (!data->out_pipe_name)
384     {
385       PLUGIN_ERROR ("Failed to create output pipe name.");
386       np_error = NPERR_OUT_OF_MEMORY_ERROR;
387       goto cleanup_out_pipe_name;
388     }
389 
390   if (mkfifo (data->out_pipe_name, 0700) == -1 && errno != EEXIST)
391     {
392       PLUGIN_ERROR_TWO ("Failed to create output pipe", strerror (errno));
393       np_error = NPERR_GENERIC_ERROR;
394       goto cleanup_out_pipe_name;
395     }
396 
397   // Start a separate appletviewer process for each applet, even if
398   // there are multiple applets in the same page.  We may need to
399   // change this behaviour if we find pages with multiple applets that
400   // rely on being run in the same VM.
401 
402   // Critical region.  Hold appletviewer_mutex while we start the
403   // appletviewer, create the IO channels and install the channel
404   // watch callbacks.
405   g_mutex_lock (data->appletviewer_mutex);
406 
407   np_error = plugin_start_appletviewer (data);
408 
409   // If the appletviewer is not installed, then a dialog box will
410   // show up and the plugin will be killed.
411   if (np_error != NPERR_NO_ERROR)
412     {
413       if (plugin_failed ())
414         goto cleanup_applet_failure;
415   	}
416 
417   // Create plugin-to-appletviewer channel.  The default encoding for
418   // the file is UTF-8.
419   // data->out_to_appletviewer
420   data->out_to_appletviewer = g_io_channel_new_file (data->out_pipe_name,
421                                                      "w", &channel_error);
422   if (!data->out_to_appletviewer)
423     {
424       if (channel_error)
425         {
426           PLUGIN_ERROR_TWO ("Failed to create output channel",
427                             channel_error->message);
428           g_error_free (channel_error);
429           channel_error = NULL;
430         }
431       else
432         PLUGIN_ERROR ("Failed to create output channel");
433 
434       np_error = NPERR_GENERIC_ERROR;
435       goto cleanup_out_to_appletviewer;
436     }
437 
438   // Watch for hangup and error signals on the output pipe.
439   data->out_watch_source =
440     g_io_add_watch (data->out_to_appletviewer,
441                     (GIOCondition) (G_IO_ERR | G_IO_HUP),
442                     plugin_out_pipe_callback, (gpointer) data);
443 
444   // Create appletviewer-to-plugin channel.  The default encoding for
445   // the file is UTF-8.
446   // data->in_from_appletviewer
447   data->in_from_appletviewer = g_io_channel_new_file (data->in_pipe_name,
448                                                       "r", &channel_error);
449   if (!data->in_from_appletviewer)
450     {
451       if (channel_error)
452         {
453           PLUGIN_ERROR_TWO ("Failed to create input channel",
454                             channel_error->message);
455           g_error_free (channel_error);
456           channel_error = NULL;
457         }
458       else
459         PLUGIN_ERROR ("Failed to create input channel");
460 
461       np_error = NPERR_GENERIC_ERROR;
462       goto cleanup_in_from_appletviewer;
463     }
464 
465   // Watch for hangup and error signals on the input pipe.
466   data->in_watch_source =
467     g_io_add_watch (data->in_from_appletviewer,
468                     (GIOCondition) (G_IO_IN | G_IO_ERR | G_IO_HUP),
469                     plugin_in_pipe_callback, (gpointer) data);
470 
471   // Wait until we receive confirmation that the appletviewer has
472   // started.
473   if (g_io_channel_read_line (data->in_from_appletviewer,
474                               &read_message, NULL, NULL,
475                               &channel_error)
476       != G_IO_STATUS_NORMAL)
477     {
478       if (channel_error)
479         {
480           PLUGIN_ERROR_TWO ("Receiving confirmation from appletviewer failed",
481                             channel_error->message);
482           g_error_free (channel_error);
483           channel_error = NULL;
484         }
485       else
486         PLUGIN_ERROR ("Receiving confirmation from appletviewer failed");
487 
488       np_error = NPERR_GENERIC_ERROR;
489       goto cleanup_in_watch_source;
490     }
491 
492   PLUGIN_DEBUG ("GCJ_New: got confirmation that appletviewer is running.");
493   data->appletviewer_alive = TRUE;
494 
495   // Send applet tag message to appletviewer.
496   applet_tag = plugin_create_applet_tag (argc, argn, argv);
497   tag_message = g_strconcat ("tag ", documentbase, " ", applet_tag, NULL);
498 
499   plugin_send_message_to_appletviewer (data, data->instance_string);
500   plugin_send_message_to_appletviewer (data, tag_message);
501 
502   g_mutex_unlock (data->appletviewer_mutex);
503 
504   // If initialization succeeded entirely then we store the plugin
505   // data in the instance structure and return.  Otherwise we free the
506   // data we've allocated so far and set instance->pdata to NULL.
507 
508   // Set back-pointer to owner instance.
509   data->owner = instance;
510   instance->pdata = data;
511   goto cleanup_done;
512 
513   // An error occurred while initializing the plugin data or spawning
514   // the appletviewer so we free the data we've already allocated.
515 
516  cleanup_in_watch_source:
517   // Removing a source is harmless if it fails since it just means the
518   // source has already been removed.
519   g_source_remove (data->in_watch_source);
520   data->in_watch_source = 0;
521 
522  cleanup_in_from_appletviewer:
523   if (data->in_from_appletviewer)
524     g_io_channel_unref (data->in_from_appletviewer);
525   data->in_from_appletviewer = NULL;
526 
527   // cleanup_out_watch_source:
528   g_source_remove (data->out_watch_source);
529   data->out_watch_source = 0;
530 
531  cleanup_out_to_appletviewer:
532   if (data->out_to_appletviewer)
533     g_io_channel_unref (data->out_to_appletviewer);
534   data->out_to_appletviewer = NULL;
535 
536   // cleanup_out_pipe:
537   // Delete output pipe.
538   unlink (data->out_pipe_name);
539 
540  cleanup_applet_failure:
541  cleanup_out_pipe_name:
542   g_free (data->out_pipe_name);
543   data->out_pipe_name = NULL;
544 
545   // cleanup_in_pipe:
546   // Delete input pipe.
547   unlink (data->in_pipe_name);
548 
549  cleanup_in_pipe_name:
550   g_free (data->in_pipe_name);
551   data->in_pipe_name = NULL;
552 
553  cleanup_appletviewer_mutex:
554   g_free (data->appletviewer_mutex);
555   data->appletviewer_mutex = NULL;
556 
557   // cleanup_instance_string:
558   g_free (data->instance_string);
559   data->instance_string = NULL;
560 
561   // cleanup_data:
562   // Eliminate back-pointer to plugin instance.
563   data->owner = NULL;
564   (*browserFunctions.memfree) (data);
565   data = NULL;
566 
567   // Initialization failed so return a NULL pointer for the browser
568   // data.
569   instance->pdata = NULL;
570 
571  cleanup_done:
572   g_free (tag_message);
573   tag_message = NULL;
574   g_free (applet_tag);
575   applet_tag = NULL;
576   g_free (read_message);
577   read_message = NULL;
578   g_free (documentbase);
579   documentbase = NULL;
580 
581   PLUGIN_DEBUG ("GCJ_New return");
582 
583   return np_error;
584 }
585 
586 NPError
GCJ_GetValue(NPP instance,NPPVariable variable,void * value)587 GCJ_GetValue (NPP instance, NPPVariable variable, void* value)
588 {
589   PLUGIN_DEBUG ("GCJ_GetValue");
590 
591   NPError np_error = NPERR_NO_ERROR;
592 
593   switch (variable)
594     {
595     // This plugin needs XEmbed support.
596     case NPPVpluginNeedsXEmbed:
597       {
598         PLUGIN_DEBUG ("GCJ_GetValue: returning TRUE for NeedsXEmbed.");
599         PRBool* bool_value = (PRBool*) value;
600         *bool_value = PR_TRUE;
601       }
602       break;
603 
604     default:
605       PLUGIN_ERROR ("Unknown plugin value requested.");
606       np_error = NPERR_GENERIC_ERROR;
607       break;
608     }
609 
610   PLUGIN_DEBUG ("GCJ_GetValue return");
611 
612   return np_error;
613 }
614 
615 NPError
GCJ_Destroy(NPP instance,NPSavedData ** save)616 GCJ_Destroy (NPP instance, NPSavedData** save)
617 {
618   PLUGIN_DEBUG ("GCJ_Destroy");
619 
620   GCJPluginData* data = (GCJPluginData*) instance->pdata;
621 
622   if (data)
623     {
624       // Critical region.  Stop the appletviewer.
625       g_mutex_lock (data->appletviewer_mutex);
626 
627       // Tell the appletviewer to destroy its embedded plugin window.
628       plugin_send_message_to_appletviewer (data, "destroy");
629       // Shut down the appletviewer.
630       plugin_stop_appletviewer (data);
631 
632       g_mutex_unlock (data->appletviewer_mutex);
633 
634       // Free plugin data.
635       plugin_data_destroy (&data);
636     }
637 
638   PLUGIN_DEBUG ("GCJ_Destroy return");
639 
640   return NPERR_NO_ERROR;
641 }
642 
643 NPError
GCJ_SetWindow(NPP instance,NPWindow * window)644 GCJ_SetWindow (NPP instance, NPWindow* window)
645 {
646   PLUGIN_DEBUG ("GCJ_SetWindow");
647 
648   if (instance == NULL)
649     {
650       PLUGIN_ERROR ("Invalid instance.");
651 
652       return NPERR_INVALID_INSTANCE_ERROR;
653     }
654 
655   GCJPluginData* data = (GCJPluginData*) instance->pdata;
656 
657   // Simply return if we receive a NULL window.
658   if ((window == NULL) || (window->window == NULL))
659     {
660       PLUGIN_DEBUG ("GCJ_SetWindow: got NULL window.");
661 
662       return NPERR_NO_ERROR;
663     }
664 
665   if (data->window_handle)
666     {
667       // The window already exists.
668       if (data->window_handle == window->window)
669 	{
670           // The parent window is the same as in previous calls.
671           PLUGIN_DEBUG ("GCJ_SetWindow: window already exists.");
672 
673           // Critical region.  Read data->appletviewer_mutex and send
674           // a message to the appletviewer.
675           g_mutex_lock (data->appletviewer_mutex);
676 
677 	  if (data->appletviewer_alive)
678 	    {
679 	      // The window is the same as it was for the last
680 	      // SetWindow call.
681 	      if (window->width != data->window_width)
682 		{
683                   PLUGIN_DEBUG ("GCJ_SetWindow: window width changed.");
684 		  // The width of the plugin window has changed.
685 
686                   // Send the new width to the appletviewer.
687 		  plugin_send_message_to_appletviewer (data,
688                                                        data->instance_string);
689                   gchar* width_message = g_strdup_printf ("width %d",
690                                                           window->width);
691 		  plugin_send_message_to_appletviewer (data, width_message);
692                   g_free (width_message);
693                   width_message = NULL;
694 
695                   // Store the new width.
696                   data->window_width = window->width;
697 		}
698 
699 	      if (window->height != data->window_height)
700 		{
701                   PLUGIN_DEBUG ("GCJ_SetWindow: window height changed.");
702 		  // The height of the plugin window has changed.
703 
704                   // Send the new height to the appletviewer.
705 		  plugin_send_message_to_appletviewer (data,
706                                                        data->instance_string);
707                   gchar* height_message = g_strdup_printf ("height %d",
708                                                            window->height);
709 		  plugin_send_message_to_appletviewer (data, height_message);
710                   g_free (height_message);
711                   height_message = NULL;
712 
713                   // Store the new height.
714                   data->window_height = window->height;
715 		}
716 	    }
717 	  else
718 	    {
719               // The appletviewer is not running.
720 	      PLUGIN_DEBUG ("GCJ_SetWindow: appletviewer is not running.");
721 	    }
722 
723           g_mutex_unlock (data->appletviewer_mutex);
724 	}
725       else
726 	{
727 	  // The parent window has changed.  This branch does run but
728 	  // doing nothing in response seems to be sufficient.
729 	  PLUGIN_DEBUG ("GCJ_SetWindow: parent window changed.");
730 	}
731     }
732   else
733     {
734       PLUGIN_DEBUG ("GCJ_SetWindow: setting window.");
735 
736       // Critical region.  Send messages to appletviewer.
737       g_mutex_lock (data->appletviewer_mutex);
738 
739       plugin_send_message_to_appletviewer (data, data->instance_string);
740       gchar *window_message = g_strdup_printf ("handle %ld",
741                                                (gulong) window->window);
742       plugin_send_message_to_appletviewer (data, window_message);
743       g_free (window_message);
744       window_message = NULL;
745 
746       g_mutex_unlock (data->appletviewer_mutex);
747 
748       // Store the window handle.
749       data->window_handle = window->window;
750     }
751 
752   PLUGIN_DEBUG ("GCJ_SetWindow return");
753 
754   return NPERR_NO_ERROR;
755 }
756 
757 NPError
GCJ_NewStream(NPP instance,NPMIMEType type,NPStream * stream,NPBool seekable,uint16 * stype)758 GCJ_NewStream (NPP instance, NPMIMEType type, NPStream* stream,
759                NPBool seekable, uint16* stype)
760 {
761   PLUGIN_DEBUG ("GCJ_NewStream");
762 
763   PLUGIN_DEBUG ("GCJ_NewStream return");
764 
765   return NPERR_NO_ERROR;
766 }
767 
768 void
GCJ_StreamAsFile(NPP instance,NPStream * stream,const char * filename)769 GCJ_StreamAsFile (NPP instance, NPStream* stream, const char* filename)
770 {
771   PLUGIN_DEBUG ("GCJ_StreamAsFile");
772 
773   PLUGIN_DEBUG ("GCJ_StreamAsFile return");
774 }
775 
776 NPError
GCJ_DestroyStream(NPP instance,NPStream * stream,NPReason reason)777 GCJ_DestroyStream (NPP instance, NPStream* stream, NPReason reason)
778 {
779   PLUGIN_DEBUG ("GCJ_DestroyStream");
780 
781   PLUGIN_DEBUG ("GCJ_DestroyStream return");
782 
783   return NPERR_NO_ERROR;
784 }
785 
786 int32
GCJ_WriteReady(NPP instance,NPStream * stream)787 GCJ_WriteReady (NPP instance, NPStream* stream)
788 {
789   PLUGIN_DEBUG ("GCJ_WriteReady");
790 
791   PLUGIN_DEBUG ("GCJ_WriteReady return");
792 
793   return 0;
794 }
795 
796 int32
GCJ_Write(NPP instance,NPStream * stream,int32 offset,int32 len,void * buffer)797 GCJ_Write (NPP instance, NPStream* stream, int32 offset, int32 len,
798            void* buffer)
799 {
800   PLUGIN_DEBUG ("GCJ_Write");
801 
802   PLUGIN_DEBUG ("GCJ_Write return");
803 
804   return 0;
805 }
806 
807 void
GCJ_Print(NPP instance,NPPrint * platformPrint)808 GCJ_Print (NPP instance, NPPrint* platformPrint)
809 {
810   PLUGIN_DEBUG ("GCJ_Print");
811 
812   PLUGIN_DEBUG ("GCJ_Print return");
813 }
814 
815 int16
GCJ_HandleEvent(NPP instance,void * event)816 GCJ_HandleEvent (NPP instance, void* event)
817 {
818   PLUGIN_DEBUG ("GCJ_HandleEvent");
819 
820   PLUGIN_DEBUG ("GCJ_HandleEvent return");
821 
822   return 0;
823 }
824 
825 void
GCJ_URLNotify(NPP instance,const char * url,NPReason reason,void * notifyData)826 GCJ_URLNotify (NPP instance, const char* url, NPReason reason,
827                void* notifyData)
828 {
829   PLUGIN_DEBUG ("GCJ_URLNotify");
830 
831   PLUGIN_DEBUG ("GCJ_URLNotify return");
832 }
833 
834 #if (((NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR) < 20)
835 jref
836 #else
837 void*
838 #endif
GCJ_GetJavaClass(void)839 GCJ_GetJavaClass (void)
840 {
841   PLUGIN_DEBUG ("GCJ_GetJavaClass");
842 
843   PLUGIN_DEBUG ("GCJ_GetJavaClass return");
844 
845   return 0;
846 }
847 
848 // HELPER FUNCTIONS
849 
850 static void
plugin_data_new(GCJPluginData ** data)851 plugin_data_new (GCJPluginData** data)
852 {
853   PLUGIN_DEBUG ("plugin_data_new");
854 
855   *data = (GCJPluginData*)
856     (*browserFunctions.memalloc) (sizeof (struct GCJPluginData));
857 
858   // appletviewer_alive is false until the applet viewer is spawned.
859   if (*data)
860     memset (*data, 0, sizeof (struct GCJPluginData));
861 
862   PLUGIN_DEBUG ("plugin_data_new return");
863 }
864 
865 // Documentbase retrieval.  This function gets the current document's
866 // documentbase.  This function relies on browser-private data so it
867 // will only work when the plugin is loaded in a Mozilla-based
868 // browser.  We could not find a way to retrieve the documentbase
869 // using the original Netscape plugin API so we use the XPCOM API
870 // instead.
871 static gchar*
plugin_get_documentbase(NPP instance)872 plugin_get_documentbase (NPP instance)
873 {
874   PLUGIN_DEBUG ("plugin_get_documentbase");
875 
876   nsIPluginInstance* xpcom_instance = NULL;
877   nsIPluginInstancePeer* peer = NULL;
878   nsresult result = 0;
879   nsIPluginTagInfo2* pluginTagInfo2 = NULL;
880   info_union u = { NULL };
881   char const* documentbase = NULL;
882   gchar* documentbase_copy = NULL;
883 
884   xpcom_instance = (nsIPluginInstance*) (instance->ndata);
885   if (!xpcom_instance)
886     {
887       PLUGIN_ERROR ("xpcom_instance is NULL.");
888       goto cleanup_done;
889     }
890 
891   xpcom_instance->GetPeer (&peer);
892   if (!peer)
893     {
894       PLUGIN_ERROR ("peer is NULL.");
895       goto cleanup_done;
896     }
897 
898   u.info_field = &pluginTagInfo2;
899 
900   result = peer->QueryInterface (kIPluginTagInfo2IID,
901                                  u.void_field);
902   if (result || !pluginTagInfo2)
903     {
904       PLUGIN_ERROR ("pluginTagInfo2 retrieval failed.");
905       goto cleanup_peer;
906     }
907 
908   pluginTagInfo2->GetDocumentBase (&documentbase);
909 
910   if (!documentbase)
911     {
912       PLUGIN_ERROR ("documentbase is NULL.");
913       goto cleanup_plugintaginfo2;
914     }
915 
916   documentbase_copy = g_strdup (documentbase);
917 
918   // Release references.
919  cleanup_plugintaginfo2:
920   NS_RELEASE (pluginTagInfo2);
921 
922  cleanup_peer:
923   NS_RELEASE (peer);
924 
925  cleanup_done:
926   PLUGIN_DEBUG ("plugin_get_documentbase return");
927 
928   return documentbase_copy;
929 }
930 
931 // This function shows a error message if the appletviewer has
932 // not been installed. It returns true, if the user presses the
933 // ok button.
934 static bool
plugin_failed()935 plugin_failed ()
936 {
937   GtkWidget* dialog = NULL;
938   GtkWidget* ok_button = NULL;
939 
940   dialog = gtk_message_dialog_new (NULL,
941                                    GTK_DIALOG_MODAL,
942                                    GTK_MESSAGE_WARNING,
943                                    GTK_BUTTONS_NONE,
944                                    FAILURE_MESSAGE);
945   ok_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
946                                      GTK_STOCK_OK,
947                                      GTK_RESPONSE_OK);
948   gtk_widget_show_all (dialog);
949   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
950     {
951       gtk_widget_destroy (dialog);
952       return true;
953     }
954   return false;
955 }
956 
957 // plugin_user_trusts_documentbase returns true if the given
958 // documentbase is in the documentbase whitelist.  Otherwise it asks
959 // the user if he trusts the given documentbase by calling
960 // plugin_ask_user_about_documentbase.
961 static bool
plugin_user_trusts_documentbase(char * documentbase)962 plugin_user_trusts_documentbase (char* documentbase)
963 {
964   bool applet_in_whitelist = false;
965 
966   // Check if documentbase is in whitelist.
967   while (true)
968     {
969       gchar* whitelist_entry = NULL;
970       gchar* newline_documentbase = NULL;
971 
972       // If reading fails, break out of this loop with
973       // applet_in_whitelist still set to false.
974       if (g_io_channel_read_line (whitelist_file, &whitelist_entry,
975                                   NULL, NULL, &channel_error)
976           != G_IO_STATUS_NORMAL)
977         {
978           if (channel_error)
979             {
980               PLUGIN_ERROR_TWO ("Failed to read line from whitelist file",
981                                 channel_error->message);
982               g_error_free (channel_error);
983               channel_error = NULL;
984             }
985           else
986             PLUGIN_ERROR ("Failed to read line from whitelist file.");
987           g_free (whitelist_entry);
988           whitelist_entry = NULL;
989           break;
990         }
991 
992       newline_documentbase = g_strdup_printf ("%s\n", documentbase);
993       if (!strcmp (newline_documentbase, whitelist_entry))
994         {
995           applet_in_whitelist = true;
996           g_free (newline_documentbase);
997           newline_documentbase = NULL;
998           g_free (whitelist_entry);
999           whitelist_entry = NULL;
1000           break;
1001         }
1002       g_free (whitelist_entry);
1003       whitelist_entry = NULL;
1004       g_free (newline_documentbase);
1005       newline_documentbase = NULL;
1006     }
1007 
1008   return applet_in_whitelist ? true
1009     : plugin_ask_user_about_documentbase (documentbase);
1010 }
1011 
1012 // plugin_add_documentbase_to_whitelist adds the given documentbase to
1013 // the user's documentbase whitelist.
1014 static void
plugin_add_documentbase_to_whitelist(char * documentbase)1015 plugin_add_documentbase_to_whitelist (char* documentbase)
1016 {
1017   gsize bytes_written = 0;
1018   char* newline_documentbase = NULL;
1019   GIOStatus status = G_IO_STATUS_NORMAL;
1020 
1021   newline_documentbase = g_strdup_printf ("%s\n", documentbase);
1022   status = g_io_channel_write_chars (whitelist_file,
1023                                      newline_documentbase, -1, &bytes_written,
1024                                      &channel_error);
1025   g_free (newline_documentbase);
1026   newline_documentbase = NULL;
1027 
1028   if (status != G_IO_STATUS_NORMAL)
1029     {
1030       if (channel_error)
1031         {
1032           PLUGIN_ERROR_TWO ("Error writing to whitelist file",
1033                             channel_error->message);
1034           g_error_free (channel_error);
1035           channel_error = NULL;
1036         }
1037       else
1038         PLUGIN_ERROR ("Error writing to whitelist file.");
1039     }
1040 
1041   if (g_io_channel_flush (whitelist_file, &channel_error)
1042       != G_IO_STATUS_NORMAL)
1043     {
1044       if (channel_error)
1045         {
1046           PLUGIN_ERROR_TWO ("Failed to write whitelist file",
1047                             channel_error->message);
1048           g_error_free (channel_error);
1049           channel_error = NULL;
1050         }
1051       else
1052         PLUGIN_ERROR ("Failed to write whitelist file.");
1053     }
1054 
1055   if (g_io_channel_shutdown (whitelist_file, TRUE, &channel_error)
1056       != G_IO_STATUS_NORMAL)
1057     {
1058       if (channel_error)
1059         {
1060           PLUGIN_ERROR_TWO ("Failed to close whitelist file",
1061                             channel_error->message);
1062           g_error_free (channel_error);
1063           channel_error = NULL;
1064         }
1065       else
1066         PLUGIN_ERROR ("Failed to close whitelist file.");
1067     }
1068 }
1069 
1070 // plugin_ask_user_about_documentbase puts up a dialog box that asks if the
1071 // user trusts applets from this documentbase.  The user has three
1072 // options: "Cancel", "Trust Applet" and "Trust Applet and Add to
1073 // Whitelist".  If the user selects Cancel (the default) then a
1074 // generic error code is returned from GCJ_New, telling the browser
1075 // that the applet failed to load.  If the user selects "Trust Applet"
1076 // then plugin loading proceeds.  If the user selects "Trust Applet
1077 // and Add to Whitelist" then this documentbase is added to the user's
1078 // applet whitelist and plugin loading proceeds.
1079 static bool
plugin_ask_user_about_documentbase(char * documentbase)1080 plugin_ask_user_about_documentbase (char* documentbase)
1081 {
1082   GtkWidget* dialog = NULL;
1083   GtkWidget* ok_button = NULL;
1084   GtkWidget* cancel_button = NULL;
1085   GtkWidget* whitelist_button = NULL;
1086   gint dialog_response = GTK_RESPONSE_NONE;
1087 
1088   dialog = gtk_message_dialog_new (NULL,
1089                                    GTK_DIALOG_MODAL,
1090                                    GTK_MESSAGE_WARNING,
1091                                    GTK_BUTTONS_NONE,
1092                                    SECURITY_WARNING,
1093                                    documentbase);
1094   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1095                                             SECURITY_DESCRIPTION, whitelist_filename);
1096 
1097   cancel_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1098                                          GTK_STOCK_CANCEL,
1099                                          GTK_RESPONSE_CANCEL);
1100   ok_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1101                                      RESPONSE_TRUST_APPLET,
1102                                      GTK_RESPONSE_OK);
1103   whitelist_button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1104                                             RESPONSE_TRUST_APPLET_ADD_TO_LIST,
1105                                             GTK_RESPONSE_APPLY);
1106   gtk_widget_grab_focus (cancel_button);
1107 
1108   gtk_widget_show_all (dialog);
1109   dialog_response = gtk_dialog_run (GTK_DIALOG (dialog));
1110   gtk_widget_destroy (dialog);
1111   if (dialog_response == GTK_RESPONSE_CANCEL)
1112     {
1113       // The user does not trust this documentbase.
1114       return false;
1115     }
1116   else if (dialog_response == GTK_RESPONSE_APPLY)
1117     {
1118       // The user wants this documentbase added to his documentbase
1119       // whitelist.
1120       plugin_add_documentbase_to_whitelist (documentbase);
1121     }
1122   // The user trusts this documentbase.
1123   return true;
1124 }
1125 
1126 // plugin_in_pipe_callback is called when data is available on the
1127 // input pipe, or when the appletviewer crashes or is killed.  It may
1128 // be called after data has been destroyed in which case it simply
1129 // returns FALSE to remove itself from the glib main loop.
1130 static gboolean
plugin_in_pipe_callback(GIOChannel * source,GIOCondition condition,gpointer plugin_data)1131 plugin_in_pipe_callback (GIOChannel* source,
1132                          GIOCondition condition,
1133                          gpointer plugin_data)
1134 {
1135   PLUGIN_DEBUG ("plugin_in_pipe_callback");
1136 
1137   GCJPluginData* data = (GCJPluginData*) plugin_data;
1138   gboolean keep_installed = TRUE;
1139 
1140   // If data is NULL then GCJ_Destroy has already been called and
1141   // plugin_in_pipe_callback is being called after plugin
1142   // destruction.  In that case all we need to do is return FALSE so
1143   // that the plugin_in_pipe_callback watch is removed.
1144   if (data)
1145     {
1146       // Critical region. Set or clear data->appletviewer_alive.
1147       g_mutex_lock (data->appletviewer_mutex);
1148 
1149       if (condition & G_IO_IN)
1150         {
1151           gchar* message = NULL;
1152 
1153           if (g_io_channel_read_line (data->in_from_appletviewer,
1154                                       &message, NULL, NULL,
1155                                       &channel_error)
1156               != G_IO_STATUS_NORMAL)
1157             {
1158               if (channel_error)
1159                 {
1160                   PLUGIN_ERROR_TWO ("Failed to read line from input channel",
1161                                     channel_error->message);
1162                   g_error_free (channel_error);
1163                   channel_error = NULL;
1164                 }
1165               else
1166                 PLUGIN_ERROR ("Failed to read line from input channel");
1167             }
1168           else
1169             {
1170               if (g_str_has_prefix (message, "url "))
1171                 {
1172                   gchar** parts = g_strsplit (message, " ", 3);
1173                   PLUGIN_DEBUG_TWO ("plugin_in_pipe_callback:"
1174                                     " opening URL", parts[1]);
1175                   PLUGIN_DEBUG_TWO ("plugin_in_pipe_callback:"
1176                                     " URL target", parts[2]);
1177                   // Open the URL in a new browser window.
1178                   NPError np_error =
1179                     (*browserFunctions.geturl) (data->owner, parts[1], parts[2]);
1180                   if (np_error != NPERR_NO_ERROR)
1181                     PLUGIN_ERROR ("Failed to load URL.");
1182                   g_strfreev (parts);
1183                   parts = NULL;
1184                 }
1185               else if (g_str_has_prefix (message, "status "))
1186                 {
1187                   gchar** parts = g_strsplit (message, " ", 2);
1188 
1189                   PLUGIN_DEBUG_TWO ("plugin_in_pipe_callback:"
1190                                     " setting status", parts[1]);
1191                   (*browserFunctions.status) (data->owner, parts[1]);
1192                   g_strfreev (parts);
1193                   parts = NULL;
1194                 }
1195               g_print ("  PIPE: plugin read %s\n", message);
1196             }
1197 
1198           g_free (message);
1199           message = NULL;
1200 
1201           keep_installed = TRUE;
1202         }
1203 
1204       if (condition & (G_IO_ERR | G_IO_HUP))
1205         {
1206           PLUGIN_DEBUG ("appletviewer has stopped.");
1207           data->appletviewer_alive = FALSE;
1208           keep_installed = FALSE;
1209         }
1210       g_mutex_unlock (data->appletviewer_mutex);
1211     }
1212 
1213   PLUGIN_DEBUG ("plugin_in_pipe_callback return");
1214 
1215   return keep_installed;
1216 }
1217 
1218 // plugin_out_pipe_callback is called when the appletviewer crashes or
1219 // is killed.  It may be called after data has been destroyed in which
1220 // case it simply returns FALSE to remove itself from the glib main
1221 // loop.
1222 static gboolean
plugin_out_pipe_callback(GIOChannel * source,GIOCondition condition,gpointer plugin_data)1223 plugin_out_pipe_callback (GIOChannel* source,
1224                           GIOCondition condition,
1225                           gpointer plugin_data)
1226 {
1227   PLUGIN_DEBUG ("plugin_out_pipe_callback");
1228 
1229   GCJPluginData* data = (GCJPluginData*) plugin_data;
1230 
1231   // If data is NULL then GCJ_Destroy has already been called and
1232   // plugin_out_pipe_callback is being called after plugin
1233   // destruction.  In that case all we need to do is return FALSE so
1234   // that the plugin_out_pipe_callback watch is removed.
1235   if (data)
1236     {
1237       // Critical region.  Clear data->appletviewer_alive.
1238       g_mutex_lock (data->appletviewer_mutex);
1239 
1240       PLUGIN_DEBUG ("plugin_out_pipe_callback: appletviewer has stopped.");
1241       data->appletviewer_alive = FALSE;
1242 
1243       g_mutex_unlock (data->appletviewer_mutex);
1244     }
1245 
1246   PLUGIN_DEBUG ("plugin_out_pipe_callback return");
1247 
1248   return FALSE;
1249 }
1250 
1251 static NPError
plugin_start_appletviewer(GCJPluginData * data)1252 plugin_start_appletviewer (GCJPluginData* data)
1253 {
1254   PLUGIN_DEBUG ("plugin_start_appletviewer");
1255   NPError error = NPERR_NO_ERROR;
1256 
1257   if (!data->appletviewer_alive)
1258     {
1259       gchar* command_line[3] = { NULL, NULL, NULL };
1260 
1261       command_line[0] = g_strdup (APPLETVIEWER_EXECUTABLE);
1262       // Output from plugin's perspective is appletviewer's input.
1263       // Input from plugin's perspective is appletviewer's output.
1264       command_line[1] = g_strdup_printf ("--plugin=%s,%s",
1265                                          data->out_pipe_name,
1266                                          data->in_pipe_name);
1267       command_line[2] = NULL;
1268 
1269       if (!g_spawn_async (NULL, command_line, NULL, (GSpawnFlags) 0,
1270                           NULL, NULL, NULL, &channel_error))
1271         {
1272           if (channel_error)
1273             {
1274               PLUGIN_ERROR_TWO ("Failed to spawn applet viewer",
1275                                 channel_error->message);
1276               g_error_free (channel_error);
1277               channel_error = NULL;
1278             }
1279           else
1280             PLUGIN_ERROR ("Failed to spawn applet viewer");
1281           error = NPERR_GENERIC_ERROR;
1282           goto cleanup;
1283         }
1284 
1285     cleanup:
1286       g_free (command_line[0]);
1287       command_line[0] = NULL;
1288       g_free (command_line[1]);
1289       command_line[1] = NULL;
1290       g_free (command_line[2]);
1291       command_line[2] = NULL;
1292     }
1293 
1294   PLUGIN_DEBUG ("plugin_start_appletviewer return");
1295   return error;
1296 }
1297 
1298 // Build up the applet tag string that we'll send to the applet
1299 // viewer.
1300 static gchar*
plugin_create_applet_tag(int16 argc,char * argn[],char * argv[])1301 plugin_create_applet_tag (int16 argc, char* argn[], char* argv[])
1302 {
1303   PLUGIN_DEBUG ("plugin_create_applet_tag");
1304 
1305   gchar* applet_tag = g_strdup ("<EMBED ");
1306   gchar* parameters = g_strdup ("");
1307 
1308   for (int16 i = 0; i < argc; i++)
1309     {
1310       if (!g_ascii_strcasecmp (argn[i], "code"))
1311         {
1312           gchar* code = g_strdup_printf ("CODE=\"%s\" ", argv[i]);
1313 	  applet_tag = g_strconcat (applet_tag, code, NULL);
1314           g_free (code);
1315           code = NULL;
1316 	}
1317       else if (!g_ascii_strcasecmp (argn[i], "codebase"))
1318 	{
1319           gchar* codebase = g_strdup_printf ("CODEBASE=\"%s\" ", argv[i]);
1320 	  applet_tag = g_strconcat (applet_tag, codebase, NULL);
1321           g_free (codebase);
1322           codebase = NULL;
1323 	}
1324       else if (!g_ascii_strcasecmp (argn[i], "archive"))
1325 	{
1326           gchar* archive = g_strdup_printf ("ARCHIVE=\"%s\" ", argv[i]);
1327 	  applet_tag = g_strconcat (applet_tag, archive, NULL);
1328           g_free (archive);
1329           archive = NULL;
1330 	}
1331       else if (!g_ascii_strcasecmp (argn[i], "width"))
1332 	{
1333           gchar* width = g_strdup_printf ("WIDTH=\"%s\" ", argv[i]);
1334 	  applet_tag = g_strconcat (applet_tag, width, NULL);
1335           g_free (width);
1336           width = NULL;
1337 	}
1338       else if (!g_ascii_strcasecmp (argn[i], "height"))
1339 	{
1340           gchar* height = g_strdup_printf ("HEIGHT=\"%s\" ", argv[i]);
1341 	  applet_tag = g_strconcat (applet_tag, height, NULL);
1342           g_free (height);
1343           height = NULL;
1344 	}
1345       else
1346         {
1347           // Escape the parameter value so that line termination
1348           // characters will pass through the pipe.
1349           if (argv[i] != '\0')
1350             {
1351               gchar* escaped = NULL;
1352 
1353               escaped = g_strescape (argv[i], NULL);
1354               parameters = g_strconcat (parameters, "<PARAM NAME=\"", argn[i],
1355                                         "\" VALUE=\"", escaped, "\">", NULL);
1356 
1357               g_free (escaped);
1358               escaped = NULL;
1359             }
1360         }
1361     }
1362 
1363   applet_tag = g_strconcat (applet_tag, ">", parameters, "</EMBED>", NULL);
1364 
1365   g_free (parameters);
1366   parameters = NULL;
1367 
1368   PLUGIN_DEBUG ("plugin_create_applet_tag return");
1369 
1370   return applet_tag;
1371 }
1372 
1373 // plugin_send_message_to_appletviewer must be called while holding
1374 // data->appletviewer_mutex.
1375 static void
plugin_send_message_to_appletviewer(GCJPluginData * data,gchar const * message)1376 plugin_send_message_to_appletviewer (GCJPluginData* data, gchar const* message)
1377 {
1378   PLUGIN_DEBUG ("plugin_send_message_to_appletviewer");
1379 
1380   if (data->appletviewer_alive)
1381     {
1382       gchar* newline_message = NULL;
1383       gsize bytes_written = 0;
1384 
1385       // Send message to appletviewer.
1386       newline_message = g_strdup_printf ("%s\n", message);
1387 
1388       // g_io_channel_write_chars will return something other than
1389       // G_IO_STATUS_NORMAL if not all the data is written.  In that
1390       // case we fail rather than retrying.
1391       if (g_io_channel_write_chars (data->out_to_appletviewer,
1392                                     newline_message, -1, &bytes_written,
1393                                     &channel_error)
1394             != G_IO_STATUS_NORMAL)
1395         {
1396           if (channel_error)
1397             {
1398               PLUGIN_ERROR_TWO ("Failed to write bytes to output channel",
1399                                 channel_error->message);
1400               g_error_free (channel_error);
1401               channel_error = NULL;
1402             }
1403           else
1404             PLUGIN_ERROR ("Failed to write bytes to output channel");
1405         }
1406 
1407       if (g_io_channel_flush (data->out_to_appletviewer, &channel_error)
1408           != G_IO_STATUS_NORMAL)
1409         {
1410           if (channel_error)
1411             {
1412               PLUGIN_ERROR_TWO ("Failed to flush bytes to output channel",
1413                                 channel_error->message);
1414               g_error_free (channel_error);
1415               channel_error = NULL;
1416             }
1417           else
1418             PLUGIN_ERROR ("Failed to flush bytes to output channel");
1419         }
1420       g_free (newline_message);
1421       newline_message = NULL;
1422 
1423       g_print ("  PIPE: plugin wrote %s\n", message);
1424     }
1425 
1426   PLUGIN_DEBUG ("plugin_send_message_to_appletviewer return");
1427 }
1428 
1429 // Stop the appletviewer process.  When this is called the
1430 // appletviewer can be in any of three states: running, crashed or
1431 // hung.  If the appletviewer is running then sending it "shutdown"
1432 // will cause it to exit.  This will cause
1433 // plugin_out_pipe_callback/plugin_in_pipe_callback to be called and
1434 // the input and output channels to be shut down.  If the appletviewer
1435 // has crashed then plugin_out_pipe_callback/plugin_in_pipe_callback
1436 // would already have been called and data->appletviewer_alive cleared
1437 // in which case this function simply returns.  If the appletviewer is
1438 // hung then this function will be successful and the input and output
1439 // watches will be removed by plugin_data_destroy.
1440 // plugin_stop_appletviewer must be called with
1441 // data->appletviewer_mutex held.
1442 static void
plugin_stop_appletviewer(GCJPluginData * data)1443 plugin_stop_appletviewer (GCJPluginData* data)
1444 {
1445   PLUGIN_DEBUG ("plugin_stop_appletviewer");
1446 
1447   if (data->appletviewer_alive)
1448     {
1449       // Shut down the appletviewer.
1450       gsize bytes_written = 0;
1451 
1452       if (data->out_to_appletviewer)
1453         {
1454           if (g_io_channel_write_chars (data->out_to_appletviewer, "shutdown",
1455                                         -1, &bytes_written, &channel_error)
1456               != G_IO_STATUS_NORMAL)
1457             {
1458               if (channel_error)
1459                 {
1460                   PLUGIN_ERROR_TWO ("Failed to write shutdown message to"
1461                                     " appletviewer", channel_error->message);
1462                   g_error_free (channel_error);
1463                   channel_error = NULL;
1464                 }
1465               else
1466                 PLUGIN_ERROR ("Failed to write shutdown message to");
1467             }
1468 
1469           if (g_io_channel_flush (data->out_to_appletviewer, &channel_error)
1470               != G_IO_STATUS_NORMAL)
1471             {
1472               if (channel_error)
1473                 {
1474                   PLUGIN_ERROR_TWO ("Failed to write shutdown message to"
1475                                     " appletviewer", channel_error->message);
1476                   g_error_free (channel_error);
1477                   channel_error = NULL;
1478                 }
1479               else
1480                 PLUGIN_ERROR ("Failed to write shutdown message to");
1481             }
1482 
1483           if (g_io_channel_shutdown (data->out_to_appletviewer,
1484                                      TRUE, &channel_error)
1485               != G_IO_STATUS_NORMAL)
1486             {
1487               if (channel_error)
1488                 {
1489                   PLUGIN_ERROR_TWO ("Failed to shut down appletviewer"
1490                                     " output channel", channel_error->message);
1491                   g_error_free (channel_error);
1492                   channel_error = NULL;
1493                 }
1494               else
1495                 PLUGIN_ERROR ("Failed to shut down appletviewer");
1496             }
1497         }
1498 
1499       if (data->in_from_appletviewer)
1500         {
1501           if (g_io_channel_shutdown (data->in_from_appletviewer,
1502                                      TRUE, &channel_error)
1503               != G_IO_STATUS_NORMAL)
1504             {
1505               if (channel_error)
1506                 {
1507                   PLUGIN_ERROR_TWO ("Failed to shut down appletviewer"
1508                                     " input channel", channel_error->message);
1509                   g_error_free (channel_error);
1510                   channel_error = NULL;
1511                 }
1512               else
1513                 PLUGIN_ERROR ("Failed to shut down appletviewer");
1514             }
1515         }
1516     }
1517 
1518   PLUGIN_DEBUG ("plugin_stop_appletviewer return");
1519 }
1520 
1521 static void
plugin_data_destroy(GCJPluginData ** data)1522 plugin_data_destroy (GCJPluginData** data)
1523 {
1524   PLUGIN_DEBUG ("plugin_data_destroy");
1525 
1526   GCJPluginData* tofree = *data;
1527 
1528   tofree->window_handle = NULL;
1529   tofree->window_height = 0;
1530   tofree->window_width = 0;
1531 
1532   // Copied from GCJ_New.
1533 
1534   // cleanup_in_watch_source:
1535   // Removing a source is harmless if it fails since it just means the
1536   // source has already been removed.
1537   g_source_remove (tofree->in_watch_source);
1538   tofree->in_watch_source = 0;
1539 
1540   // cleanup_in_from_appletviewer:
1541   if (tofree->in_from_appletviewer)
1542     g_io_channel_unref (tofree->in_from_appletviewer);
1543   tofree->in_from_appletviewer = NULL;
1544 
1545   // cleanup_out_watch_source:
1546   g_source_remove (tofree->out_watch_source);
1547   tofree->out_watch_source = 0;
1548 
1549   // cleanup_out_to_appletviewer:
1550   if (tofree->out_to_appletviewer)
1551     g_io_channel_unref (tofree->out_to_appletviewer);
1552   tofree->out_to_appletviewer = NULL;
1553 
1554   // cleanup_out_pipe:
1555   // Delete output pipe.
1556   unlink (tofree->out_pipe_name);
1557 
1558   // cleanup_out_pipe_name:
1559   g_free (tofree->out_pipe_name);
1560   tofree->out_pipe_name = NULL;
1561 
1562   // cleanup_in_pipe:
1563   // Delete input pipe.
1564   unlink (tofree->in_pipe_name);
1565 
1566   // cleanup_in_pipe_name:
1567   g_free (tofree->in_pipe_name);
1568   tofree->in_pipe_name = NULL;
1569 
1570   // cleanup_appletviewer_mutex:
1571   g_free (tofree->appletviewer_mutex);
1572   tofree->appletviewer_mutex = NULL;
1573 
1574   // cleanup_instance_string:
1575   g_free (tofree->instance_string);
1576   tofree->instance_string = NULL;
1577 
1578   // cleanup_data:
1579   // Eliminate back-pointer to plugin instance.
1580   tofree->owner = NULL;
1581   (*browserFunctions.memfree) (tofree);
1582   tofree = NULL;
1583 
1584   PLUGIN_DEBUG ("plugin_data_destroy return");
1585 }
1586 
1587 // FACTORY FUNCTIONS
1588 
1589 // Provides the browser with pointers to the plugin functions that we
1590 // implement and initializes a local table with browser functions that
1591 // we may wish to call.  Called once, after browser startup and before
1592 // the first plugin instance is created.
1593 // The field 'initialized' is set to true once this function has
1594 // finished. If 'initialized' is already true at the beginning of
1595 // this function, then it is evident that NP_Initialize has already
1596 // been called. There is no need to call this function more than once and
1597 // this workaround avoids any duplicate calls.
1598 NPError
NP_Initialize(NPNetscapeFuncs * browserTable,NPPluginFuncs * pluginTable)1599 NP_Initialize (NPNetscapeFuncs* browserTable, NPPluginFuncs* pluginTable)
1600 {
1601   PLUGIN_DEBUG ("NP_Initialize");
1602 
1603   if (initialized)
1604     return NPERR_NO_ERROR;
1605   else if ((browserTable == NULL) || (pluginTable == NULL))
1606     {
1607       PLUGIN_ERROR ("Browser or plugin function table is NULL.");
1608 
1609       return NPERR_INVALID_FUNCTABLE_ERROR;
1610     }
1611 
1612   // Ensure that the major version of the plugin API that the browser
1613   // expects is not more recent than the major version of the API that
1614   // we've implemented.
1615   if ((browserTable->version >> 8) > NP_VERSION_MAJOR)
1616     {
1617       PLUGIN_ERROR ("Incompatible version.");
1618 
1619       return NPERR_INCOMPATIBLE_VERSION_ERROR;
1620     }
1621 
1622   // Ensure that the plugin function table we've received is large
1623   // enough to store the number of functions that we may provide.
1624   if (pluginTable->size < sizeof (NPPluginFuncs))
1625     {
1626       PLUGIN_ERROR ("Invalid plugin function table.");
1627 
1628       return NPERR_INVALID_FUNCTABLE_ERROR;
1629     }
1630 
1631   // Ensure that the browser function table is large enough to store
1632   // the number of browser functions that we may use.
1633   if (browserTable->size < sizeof (NPNetscapeFuncs))
1634     {
1635       PLUGIN_ERROR ("Invalid browser function table.");
1636 
1637       return NPERR_INVALID_FUNCTABLE_ERROR;
1638     }
1639 
1640   data_directory = g_strconcat(getenv("HOME"), "/.gcjwebplugin", NULL);
1641   whitelist_filename = g_strconcat (data_directory, "/whitelist.txt", NULL);
1642   // Make sure the plugin data directory exists, creating it if
1643   // necessary.
1644   if (!g_file_test (data_directory,
1645                     (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
1646     {
1647       int file_error = 0;
1648 
1649       file_error = g_mkdir (data_directory, 0700);
1650       if (file_error != 0)
1651         {
1652           PLUGIN_ERROR_THREE ("Failed to create data directory",
1653                               data_directory,
1654                               strerror (errno));
1655           return NPERR_GENERIC_ERROR;
1656         }
1657     }
1658 
1659   // Store in a local table the browser functions that we may use.
1660   browserFunctions.version = browserTable->version;
1661   browserFunctions.size = browserTable->size;
1662   browserFunctions.posturl = browserTable->posturl;
1663   browserFunctions.geturl = browserTable->geturl;
1664   browserFunctions.geturlnotify = browserTable->geturlnotify;
1665   browserFunctions.requestread = browserTable->requestread;
1666   browserFunctions.newstream = browserTable->newstream;
1667   browserFunctions.write = browserTable->write;
1668   browserFunctions.destroystream = browserTable->destroystream;
1669   browserFunctions.status = browserTable->status;
1670   browserFunctions.uagent = browserTable->uagent;
1671   browserFunctions.memalloc = browserTable->memalloc;
1672   browserFunctions.memfree = browserTable->memfree;
1673   browserFunctions.memflush = browserTable->memflush;
1674   browserFunctions.reloadplugins = browserTable->reloadplugins;
1675   browserFunctions.getvalue = browserTable->getvalue;
1676 
1677   // Return to the browser the plugin functions that we implement.
1678   pluginTable->version = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
1679   pluginTable->size = sizeof (NPPluginFuncs);
1680 #if (((NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR) < 20)
1681   pluginTable->newp = NewNPP_NewProc (GCJ_New);
1682   pluginTable->destroy = NewNPP_DestroyProc (GCJ_Destroy);
1683   pluginTable->setwindow = NewNPP_SetWindowProc (GCJ_SetWindow);
1684   pluginTable->newstream = NewNPP_NewStreamProc (GCJ_NewStream);
1685   pluginTable->destroystream = NewNPP_DestroyStreamProc (GCJ_DestroyStream);
1686   pluginTable->asfile = NewNPP_StreamAsFileProc (GCJ_StreamAsFile);
1687   pluginTable->writeready = NewNPP_WriteReadyProc (GCJ_WriteReady);
1688   pluginTable->write = NewNPP_WriteProc (GCJ_Write);
1689   pluginTable->print = NewNPP_PrintProc (GCJ_Print);
1690   pluginTable->urlnotify = NewNPP_URLNotifyProc (GCJ_URLNotify);
1691   pluginTable->getvalue = NewNPP_GetValueProc (GCJ_GetValue);
1692 #else
1693   pluginTable->newp = (NPP_NewProcPtr) (GCJ_New);
1694   pluginTable->destroy = (NPP_DestroyProcPtr) (GCJ_Destroy);
1695   pluginTable->setwindow = (NPP_SetWindowProcPtr) (GCJ_SetWindow);
1696   pluginTable->newstream = (NPP_NewStreamProcPtr) (GCJ_NewStream);
1697   pluginTable->destroystream = (NPP_DestroyStreamProcPtr) (GCJ_DestroyStream);
1698   pluginTable->asfile = (NPP_StreamAsFileProcPtr) (GCJ_StreamAsFile);
1699   pluginTable->writeready = (NPP_WriteReadyProcPtr) (GCJ_WriteReady);
1700   pluginTable->write = (NPP_WriteProcPtr) (GCJ_Write);
1701   pluginTable->print = (NPP_PrintProcPtr) (GCJ_Print);
1702   pluginTable->urlnotify = (NPP_URLNotifyProcPtr) (GCJ_URLNotify);
1703   pluginTable->getvalue = (NPP_GetValueProcPtr) (GCJ_GetValue);
1704 #endif
1705 
1706   initialized = true;
1707 
1708   // Initialize threads (needed for mutexes).
1709   if (!g_thread_supported ())
1710     g_thread_init (NULL);
1711 
1712   plugin_instance_mutex = g_mutex_new ();
1713 
1714   PLUGIN_DEBUG ("NP_Initialize: using " APPLETVIEWER_EXECUTABLE ".");
1715 
1716   PLUGIN_DEBUG ("NP_Initialize return");
1717 
1718   return NPERR_NO_ERROR;
1719 }
1720 
1721 // Returns a string describing the MIME type that this plugin
1722 // handles.
1723 char*
NP_GetMIMEDescription(void)1724 NP_GetMIMEDescription (void)
1725 {
1726   PLUGIN_DEBUG ("NP_GetMIMEDescription");
1727 
1728   PLUGIN_DEBUG ("NP_GetMIMEDescription return");
1729 
1730   return (char*) PLUGIN_MIME_DESC;
1731 }
1732 
1733 // Returns a value relevant to the plugin as a whole.  The browser
1734 // calls this function to obtain information about the plugin.
1735 NPError
NP_GetValue(void * future,NPPVariable variable,void * value)1736 NP_GetValue (void* future, NPPVariable variable, void* value)
1737 {
1738   PLUGIN_DEBUG ("NP_GetValue");
1739 
1740   NPError result = NPERR_NO_ERROR;
1741   gchar** char_value = (gchar**) value;
1742 
1743   switch (variable)
1744     {
1745     case NPPVpluginNameString:
1746       PLUGIN_DEBUG ("NP_GetValue: returning plugin name.");
1747       *char_value = g_strdup (PLUGIN_NAME " " PACKAGE_VERSION);
1748       break;
1749 
1750     case NPPVpluginDescriptionString:
1751       PLUGIN_DEBUG ("NP_GetValue: returning plugin description.");
1752       *char_value = g_strdup (PLUGIN_DESC);
1753       break;
1754 
1755     default:
1756       PLUGIN_ERROR ("Unknown plugin value requested.");
1757       result = NPERR_GENERIC_ERROR;
1758       break;
1759     }
1760 
1761   PLUGIN_DEBUG ("NP_GetValue return");
1762 
1763   return result;
1764 }
1765 
1766 // Shuts down the plugin.  Called after the last plugin instance is
1767 // destroyed.
1768 NPError
NP_Shutdown(void)1769 NP_Shutdown (void)
1770 {
1771   PLUGIN_DEBUG ("NP_Shutdown");
1772 
1773   // Free mutex.
1774   if (plugin_instance_mutex)
1775     {
1776       g_mutex_free (plugin_instance_mutex);
1777       plugin_instance_mutex = NULL;
1778     }
1779 
1780   if (whitelist_file)
1781     {
1782       g_io_channel_close (whitelist_file);
1783       whitelist_file = NULL;
1784     }
1785 
1786   if (data_directory)
1787     {
1788       g_free (data_directory);
1789       data_directory = NULL;
1790     }
1791 
1792   if (whitelist_filename)
1793     {
1794       g_free (whitelist_filename);
1795       whitelist_filename = NULL;
1796     }
1797 
1798   initialized = false;
1799 
1800   PLUGIN_DEBUG ("NP_Shutdown return");
1801 
1802   return NPERR_NO_ERROR;
1803 }
1804