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