source: trunk/icedtea-web/plugin/icedteanp/IcedTeaNPPlugin.cc

Last change on this file was 436, checked in by dmik, 11 years ago

icedtea-web: Use unnamed pipe instead of unix-style named pipe for debug console.

Similar to r368.

File size: 79.4 KB
Line 
1/* IcedTeaNPPlugin.cc -- web browser plugin to execute Java applets
2 Copyright (C) 2003, 2004, 2006, 2007 Free Software Foundation, Inc.
3 Copyright (C) 2009, 2010 Red Hat
4
5This file is part of GNU Classpath.
6
7GNU Classpath is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2, or (at your option)
10any later version.
11
12GNU Classpath is distributed in the hope that it will be useful, but
13WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Classpath; see the file COPYING. If not, write to the
19Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2002110-1301 USA.
21
22Linking this library statically or dynamically with other modules is
23making a combined work based on this library. Thus, the terms and
24conditions of the GNU General Public License cover the whole
25combination.
26
27As a special exception, the copyright holders of this library give you
28permission to link this library with independent modules to produce an
29executable, regardless of the license terms of these independent
30modules, and to copy and distribute the resulting executable under
31terms of your choice, provided that you also meet, for each linked
32independent module, the terms and conditions of the license of that
33module. An independent module is a module which is not derived from
34or based on this library. If you modify this library, you may extend
35this exception to your version of the library, but you are not
36obligated to do so. If you do not wish to do so, delete this
37exception statement from your version. */
38
39#ifdef __OS2__
40// OS/2 includes.
41#define INCL_DOSPROCESS
42#define INCL_DOSERRORS
43#define INCL_WINDIALOG
44#include <os2.h>
45#include <emx/startup.h>
46#include <sys/socket.h>
47#include <fcntl.h>
48#include <sstream>
49#include "OS_OS2.h"
50#endif
51
52// System includes.
53#include <dlfcn.h>
54#include <unistd.h>
55#include <fcntl.h>
56#include <dirent.h>
57#include <errno.h>
58#include <libgen.h>
59#include <stdio.h>
60#include <stdlib.h>
61#include <string.h>
62#include <string>
63#include <sys/stat.h>
64#include <sys/types.h>
65#include <unistd.h>
66#include <new>
67
68//IcedTea-plugin includes
69#include "IcedTeaPluginUtils.h"
70#include "IcedTeaParseProperties.h"
71// Liveconnect extension
72#include "IcedTeaScriptablePluginObject.h"
73#include "IcedTeaNPPlugin.h"
74
75#ifdef __OS2__
76#define DT_SOCKET_DLL "jdtsock"
77#else
78#define DT_SOCKET_DLL "dt_socket"
79#endif
80
81// Plugin information passed to about:plugins.
82#define PLUGIN_FULL_NAME PLUGIN_NAME " (using " PLUGIN_VERSION ")"
83#define PLUGIN_DESC "The <a href=\"" PACKAGE_URL "\">" PLUGIN_NAME "</a> executes Java applets."
84
85#ifdef HAVE_JAVA7
86 #define JPI_VERSION "1.7.0_" JDK_UPDATE_VERSION
87 #define PLUGIN_APPLET_MIME_DESC7 \
88 "application/x-java-applet;version=1.7:class,jar:IcedTea;"
89 #define PLUGIN_BEAN_MIME_DESC7 \
90 "application/x-java-bean;version=1.7:class,jar:IcedTea;"
91#else
92 #define JPI_VERSION "1.6.0_" JDK_UPDATE_VERSION
93 #define PLUGIN_APPLET_MIME_DESC7
94 #define PLUGIN_BEAN_MIME_DESC7
95#endif
96
97#define PLUGIN_MIME_DESC \
98 "application/x-java-vm:class,jar:IcedTea;" \
99 "application/x-java-applet:class,jar:IcedTea;" \
100 "application/x-java-applet;version=1.1:class,jar:IcedTea;" \
101 "application/x-java-applet;version=1.1.1:class,jar:IcedTea;" \
102 "application/x-java-applet;version=1.1.2:class,jar:IcedTea;" \
103 "application/x-java-applet;version=1.1.3:class,jar:IcedTea;" \
104 "application/x-java-applet;version=1.2:class,jar:IcedTea;" \
105 "application/x-java-applet;version=1.2.1:class,jar:IcedTea;" \
106 "application/x-java-applet;version=1.2.2:class,jar:IcedTea;" \
107 "application/x-java-applet;version=1.3:class,jar:IcedTea;" \
108 "application/x-java-applet;version=1.3.1:class,jar:IcedTea;" \
109 "application/x-java-applet;version=1.4:class,jar:IcedTea;" \
110 "application/x-java-applet;version=1.4.1:class,jar:IcedTea;" \
111 "application/x-java-applet;version=1.4.2:class,jar:IcedTea;" \
112 "application/x-java-applet;version=1.5:class,jar:IcedTea;" \
113 "application/x-java-applet;version=1.6:class,jar:IcedTea;" \
114 PLUGIN_APPLET_MIME_DESC7 \
115 "application/x-java-applet;jpi-version=" JPI_VERSION ":class,jar:IcedTea;" \
116 "application/x-java-bean:class,jar:IcedTea;" \
117 "application/x-java-bean;version=1.1:class,jar:IcedTea;" \
118 "application/x-java-bean;version=1.1.1:class,jar:IcedTea;" \
119 "application/x-java-bean;version=1.1.2:class,jar:IcedTea;" \
120 "application/x-java-bean;version=1.1.3:class,jar:IcedTea;" \
121 "application/x-java-bean;version=1.2:class,jar:IcedTea;" \
122 "application/x-java-bean;version=1.2.1:class,jar:IcedTea;" \
123 "application/x-java-bean;version=1.2.2:class,jar:IcedTea;" \
124 "application/x-java-bean;version=1.3:class,jar:IcedTea;" \
125 "application/x-java-bean;version=1.3.1:class,jar:IcedTea;" \
126 "application/x-java-bean;version=1.4:class,jar:IcedTea;" \
127 "application/x-java-bean;version=1.4.1:class,jar:IcedTea;" \
128 "application/x-java-bean;version=1.4.2:class,jar:IcedTea;" \
129 "application/x-java-bean;version=1.5:class,jar:IcedTea;" \
130 "application/x-java-bean;version=1.6:class,jar:IcedTea;" \
131 PLUGIN_BEAN_MIME_DESC7 \
132 "application/x-java-bean;jpi-version=" JPI_VERSION ":class,jar:IcedTea;" \
133 "application/x-java-vm-npruntime::IcedTea;"
134
135#define PLUGIN_URL NS_INLINE_PLUGIN_CONTRACTID_PREFIX NS_JVM_MIME_TYPE
136#define PLUGIN_MIME_TYPE "application/x-java-vm"
137#define PLUGIN_FILE_EXTS "class,jar,zip"
138#define PLUGIN_MIME_COUNT 1
139
140#define FAILURE_MESSAGE "icedteanp plugin error: Failed to run %s." \
141 " For more detail rerun \"firefox -g\" in a terminal window."
142
143// Data directory for plugin.
144static std::string data_directory;
145static DIR *data_directory_descriptor;
146
147// Fully-qualified appletviewer default executable and rt.jar
148static const char* appletviewer_default_executable = ICEDTEA_WEB_JRE "/bin/java";
149static const char* appletviewer_default_rtjar = ICEDTEA_WEB_JRE "/lib/rt.jar";
150
151// Applet viewer input channel (needs to be static because it is used in plugin_in_pipe_callback)
152static GIOChannel* in_from_appletviewer = NULL;
153
154#ifdef __OS2__
155#define CLOSE_FD(fd) do { if (fd != -1) { close (fd); fd = -1; } } while (0)
156#endif
157
158#ifdef __OS2__
159int in_pipe[2] = { -1 };
160#else
161// Applet viewer input pipe name.
162gchar* in_pipe_name;
163#endif
164
165// Applet viewer input watch source.
166gint in_watch_source;
167
168#ifdef __OS2__
169int out_pipe[2] = { -1 };
170#else
171// Applet viewer output pipe name.
172gchar* out_pipe_name;
173#endif
174
175#ifdef __OS2__
176int debug_pipe[2] = { -1 };
177#else
178// Applet viewer debug pipe name.
179gchar* debug_pipe_name = NULL;
180#endif
181
182// Applet viewer output watch source.
183gint out_watch_source;
184
185// Thread ID of plug-in thread
186pthread_t itnp_plugin_thread_id;
187
188// Mutex to lock async call queue
189pthread_mutex_t pluginAsyncCallMutex;
190
191/*to sync pipe to apletviewer console*/
192pthread_mutex_t debug_pipe_lock = PTHREAD_MUTEX_INITIALIZER;
193
194// Applet viewer output channel.
195GIOChannel* out_to_appletviewer;
196
197// Applet viewer debug channel.
198GIOChannel* debug_to_appletviewer = NULL;
199
200// Tracks jvm status
201gboolean jvm_up = FALSE;
202
203// Keeps track of initialization. NP_Initialize should only be
204// called once.
205gboolean initialized = false;
206
207// browser functions into mozilla
208NPNetscapeFuncs browser_functions;
209
210// Various message buses carrying information to/from Java, and internally
211MessageBus* plugin_to_java_bus;
212MessageBus* java_to_plugin_bus;
213//MessageBus* internal_bus = new MessageBus();
214
215// Processor for plugin requests
216PluginRequestProcessor* plugin_req_proc;
217
218// Sends messages to Java over the bus
219JavaMessageSender* java_req_proc;
220
221// Queue processing threads
222static pthread_t plugin_request_processor_thread1;
223static pthread_t plugin_request_processor_thread2;
224static pthread_t plugin_request_processor_thread3;
225
226#ifdef __OS2__
227static QueueProcessorData queue_processor_data1 = { NULL, false };
228static QueueProcessorData queue_processor_data2 = { NULL, false };
229static QueueProcessorData queue_processor_data3 = { NULL, false };
230#endif
231
232// Static instance helper functions.
233// Retrieve the current document's documentbase.
234static std::string plugin_get_documentbase (NPP instance);
235// Callback used to monitor input pipe status.
236static gboolean plugin_in_pipe_callback (GIOChannel* source,
237 GIOCondition condition,
238 gpointer plugin_data);
239// Callback used to monitor output pipe status.
240static gboolean plugin_out_pipe_callback (GIOChannel* source,
241 GIOCondition condition,
242 gpointer plugin_data);
243std::string plugin_parameters_string (int argc, char* argn[], char* argv[]);
244static void plugin_stop_appletviewer ();
245
246NPError get_cookie_info(const char* siteAddr, char** cookieString, uint32_t* len);
247NPError get_proxy_info(const char* siteAddr, char** proxy, uint32_t* len);
248void consume_message(gchar* message);
249static void appletviewer_monitor(GPid pid, gint status, gpointer data);
250void plugin_send_initialization_message(char* instance, gulong handle,
251 int width, int height,
252 char* url);
253/* Returns JVM options set in itw-settings */
254std::vector<std::string*>* get_jvm_args();
255
256// Global instance counter.
257// Mutex to protect plugin_instance_counter.
258static GMutex* plugin_instance_mutex = NULL;
259// A global variable for reporting GLib errors. This must be free'd
260// and set to NULL after each use.
261static GError* channel_error = NULL;
262
263static GHashTable* instance_to_id_map = g_hash_table_new(NULL, NULL);
264static GHashTable* id_to_instance_map = g_hash_table_new(NULL, NULL);
265static gint instance_counter = 1;
266static GPid appletviewer_pid = -1;
267static guint appletviewer_watch_id = -1;
268
269bool debug_initiated = false;
270int plugin_debug = getenv ("ICEDTEAPLUGIN_DEBUG") != NULL;
271bool plugin_debug_headers = false;
272bool plugin_debug_to_file = false ;
273bool plugin_debug_to_streams = true ;
274bool plugin_debug_to_system = false;
275bool plugin_debug_to_console = true;
276FILE * plugin_file_log;
277std::string plugin_file_log_name;
278
279int plugin_debug_suspend = (getenv("ICEDTEAPLUGIN_DEBUG") != NULL) &&
280 (strcmp(getenv("ICEDTEAPLUGIN_DEBUG"), "suspend") == 0);
281
282
283#ifdef LEGACY_GLIB
284// Returns key from first item stored in hashtable
285gboolean
286find_first_item_in_hash_table(gpointer key, gpointer value, gpointer user_data)
287{
288 user_data = key;
289 return (gboolean)TRUE;
290}
291
292int
293g_strcmp0(char *str1, char *str2)
294{
295 if (str1 != NULL)
296 return str2 != NULL ? strcmp(str1, str2) : 1;
297 else // str1 == NULL
298 return str2 != NULL ? 1 : 0;
299}
300
301
302#endif
303
304static std::string get_plugin_executable(){
305 std::string custom_jre;
306 bool custom_jre_defined = find_custom_jre(custom_jre);
307 if (custom_jre_defined) {
308 if (IcedTeaPluginUtilities::file_exists(custom_jre+"/bin/java")){
309 return custom_jre+"/bin/java";
310 } else {
311 PLUGIN_ERROR("Your custom jre (/bin/java check) %s is not valid. Please fix %s in your %s. In attempt to run using default one. \n", custom_jre.c_str(), custom_jre_key.c_str(), default_file_ITW_deploy_props_name.c_str());
312 }
313 }
314#ifdef __OS2__
315 custom_jre = icedtea_web_jre_dir();
316 if (custom_jre.length())
317 return custom_jre+"/bin/java";
318#endif
319 return appletviewer_default_executable;
320}
321
322static std::string get_plugin_rt_jar(){
323 std::string custom_jre;
324 bool custom_jre_defined = find_custom_jre(custom_jre);
325 if (custom_jre_defined) {
326 if (IcedTeaPluginUtilities::file_exists(custom_jre+"/lib/rt.jar")){
327 return custom_jre+"/lib/rt.jar";
328 } else {
329 PLUGIN_ERROR("Your custom jre (/lib/rt.jar check) %s is not valid. Please fix %s in your %s. In attempt to run using default one. \n", custom_jre.c_str(), custom_jre_key.c_str(), default_file_ITW_deploy_props_name.c_str());
330 }
331 }
332#ifdef __OS2__
333 custom_jre = icedtea_web_jre_dir();
334 if (custom_jre.length())
335 return custom_jre+"/lib/rt.jar";
336#endif
337 return appletviewer_default_rtjar;
338}
339
340static void cleanUpDir(){
341 //free data_directory descriptor
342 if (data_directory_descriptor != NULL) {
343 closedir(data_directory_descriptor);
344 }
345 //clean up pipes directory
346 PLUGIN_DEBUG ("Removing runtime directory %s \n", data_directory.c_str());
347 int removed = rmdir(data_directory.c_str());
348 if (removed != 0) {
349 PLUGIN_ERROR ("Failed to remove runtime directory %s, because of %s \n", data_directory.c_str(), strerror(errno));
350 } else {
351 PLUGIN_DEBUG ("Removed runtime directory %s \n", data_directory.c_str());
352 }
353 data_directory_descriptor = NULL;
354}
355/*
356 * Find first member in GHashTable* depending on version of glib
357 */
358gpointer getFirstInTableInstance(GHashTable* table)
359{
360 gpointer id, instance;
361 #ifndef LEGACY_GLIB
362 GHashTableIter iter;
363 g_hash_table_iter_init (&iter, table);
364 g_hash_table_iter_next (&iter, &instance, &id);
365 #else
366 g_hash_table_find(table, (GHRFunc)find_first_item_in_hash_table, &instance);
367 #endif
368 return instance;
369}
370
371// Functions prefixed by ITNP_ are instance functions. They are called
372// by the browser and operate on instances of ITNPPluginData.
373// Functions prefixed by plugin_ are static helper functions.
374// Functions prefixed by NP_ are factory functions. They are called
375// by the browser and provide functionality needed to create plugin
376// instances.
377
378// INSTANCE FUNCTIONS
379
380// Creates a new icedtea np plugin instance. This function creates a
381// ITNPPluginData* and stores it in instance->pdata. The following
382// ITNPPluginData fields are initialized: instance_id, in_pipe_name,
383// in_from_appletviewer, in_watch_source, out_pipe_name,
384// out_to_appletviewer, out_watch_source, appletviewer_mutex, owner,
385// appletviewer_alive. In addition two pipe files are created. All
386// of those fields must be properly destroyed, and the pipes deleted,
387// by ITNP_Destroy. If an error occurs during initialization then this
388// function will free anything that's been allocated so far, set
389// instance->pdata to NULL and return an error code.
390NPError
391ITNP_New (NPMIMEType pluginType, NPP instance, uint16_t mode,
392 int16_t argc, char* argn[], char* argv[],
393 NPSavedData* saved)
394{
395 PLUGIN_DEBUG("ITNP_New\n");
396
397 static NPObject *window_ptr;
398 NPIdentifier identifier;
399 NPVariant member_ptr;
400 browser_functions.getvalue(instance, NPNVWindowNPObject, &window_ptr);
401 identifier = browser_functions.getstringidentifier("document");
402 if (!browser_functions.hasproperty(instance, window_ptr, identifier))
403 {
404 PLUGIN_ERROR("%s not found!\n", "document");
405 }
406 browser_functions.getproperty(instance, window_ptr, identifier, &member_ptr);
407
408 PLUGIN_DEBUG("Got variant %p\n", &member_ptr);
409
410 if (!instance)
411 {
412 PLUGIN_ERROR ("Browser-provided instance pointer is NULL.\n");
413 return NPERR_INVALID_INSTANCE_ERROR;
414 }
415
416 // data
417 ITNPPluginData* data = plugin_data_new ();
418 if (data == NULL)
419 {
420 PLUGIN_ERROR ("Failed to allocate plugin data.\n");
421 return NPERR_OUT_OF_MEMORY_ERROR;
422 }
423
424 // start the jvm if needed
425 NPError startup_error = start_jvm_if_needed();
426 if (startup_error != NPERR_NO_ERROR) {
427 PLUGIN_ERROR ("Failed to start JVM\n");
428 return startup_error;
429 }
430
431 // Initialize data->instance_id.
432 //
433 // instance_id should be unique for this process so we use a
434 // combination of getpid and plugin_instance_counter.
435 //
436 // Critical region. Reference and increment plugin_instance_counter
437 // global.
438 g_mutex_lock (plugin_instance_mutex);
439
440 // data->instance_id
441 data->instance_id = g_strdup_printf ("%d",
442 instance_counter);
443
444 g_mutex_unlock (plugin_instance_mutex);
445
446 // data->appletviewer_mutex
447 data->appletviewer_mutex = g_mutex_new ();
448
449 g_mutex_lock (data->appletviewer_mutex);
450
451 std::string documentbase = plugin_get_documentbase (instance);
452 // Documentbase retrieval.
453 if (argc != 0)
454 {
455 // Send parameters to appletviewer.
456 std::string params_string = plugin_parameters_string(argc, argn, argv);
457
458 data->parameters_string = g_strdup_printf("tag %s %s", documentbase.c_str(), params_string.c_str());
459
460 data->is_applet_instance = true;
461 }
462 else
463 {
464 data->is_applet_instance = false;
465 }
466
467 g_mutex_unlock (data->appletviewer_mutex);
468
469 // If initialization succeeded entirely then we store the plugin
470 // data in the instance structure and return. Otherwise we free the
471 // data we've allocated so far and set instance->pdata to NULL.
472
473 // Set back-pointer to owner instance.
474 data->owner = instance;
475
476 // source of this instance
477 // don't use documentbase, it is cleared later
478 data->source = plugin_get_documentbase(instance);
479
480 instance->pdata = data;
481
482 // store an identifier for this plugin
483 PLUGIN_DEBUG("Mapping id %d and instance %p\n", instance_counter, instance);
484 g_hash_table_insert(instance_to_id_map, instance, GINT_TO_POINTER(instance_counter));
485 g_hash_table_insert(id_to_instance_map, GINT_TO_POINTER(instance_counter), instance);
486 instance_counter++;
487
488 PLUGIN_DEBUG ("ITNP_New return\n");
489
490 return NPERR_NO_ERROR;
491}
492
493// Starts the JVM if it is not already running
494NPError start_jvm_if_needed()
495{
496
497 // This is asynchronized function. It must
498 // have exclusivity when running.
499
500 GMutex *vm_start_mutex = g_mutex_new();
501 g_mutex_lock(vm_start_mutex);
502
503 PLUGIN_DEBUG("Checking JVM status...\n");
504
505 // If the jvm is already up, do nothing
506 if (jvm_up)
507 {
508 PLUGIN_DEBUG("JVM is up. Returning.\n");
509 return NPERR_NO_ERROR;
510 }
511
512 PLUGIN_DEBUG("No JVM is running. Attempting to start one...\n");
513
514 NPError np_error = NPERR_NO_ERROR;
515 ITNPPluginData* data = NULL;
516
517 // Create appletviewer-to-plugin pipe which we refer to as the input
518 // pipe.
519
520#ifdef __OS2__
521 if (socketpair (AF_LOCAL, SOCK_STREAM, 0, in_pipe) == -1)
522 {
523 PLUGIN_ERROR ("Failed to create input pipe", strerror (errno));
524 np_error = NPERR_GENERIC_ERROR;
525 goto cleanup_in_pipe;
526 }
527 PLUGIN_DEBUG ("ITNP_New: created input fifo: %d/%d\n", in_pipe [0], in_pipe [1]);
528#else
529 // in_pipe_name
530 in_pipe_name = g_strdup_printf ("%s/%d-icedteanp-appletviewer-to-plugin",
531 data_directory.c_str(), getpid());
532 if (!in_pipe_name)
533 {
534 PLUGIN_ERROR ("Failed to create input pipe name.\n");
535 np_error = NPERR_OUT_OF_MEMORY_ERROR;
536 // If in_pipe_name is NULL then the g_free at
537 // cleanup_in_pipe_name will simply return.
538 goto cleanup_in_pipe_name;
539 }
540
541 // clean up any older pip
542 unlink (in_pipe_name);
543
544 PLUGIN_DEBUG ("ITNP_New: creating input fifo: %s\n", in_pipe_name);
545 if (mkfifo (in_pipe_name, 0600) == -1 && errno != EEXIST)
546 {
547 PLUGIN_ERROR ("Failed to create input pipe\n", strerror (errno));
548 np_error = NPERR_GENERIC_ERROR;
549 goto cleanup_in_pipe_name;
550 }
551 PLUGIN_DEBUG ("ITNP_New: created input fifo: %s\n", in_pipe_name);
552#endif
553
554 // Create plugin-to-appletviewer pipe which we refer to as the
555 // output pipe.
556
557#ifdef __OS2__
558 if (socketpair (AF_LOCAL, SOCK_STREAM, 0, out_pipe) == -1)
559 {
560 PLUGIN_ERROR ("Failed to create output pipe", strerror (errno));
561 np_error = NPERR_GENERIC_ERROR;
562 goto cleanup_out_pipe;
563 }
564 PLUGIN_DEBUG ("ITNP_New: created output fifo: %d/%d\n", out_pipe [0], out_pipe [1]);
565#else
566 // out_pipe_name
567 out_pipe_name = g_strdup_printf ("%s/%d-icedteanp-plugin-to-appletviewer",
568 data_directory.c_str(), getpid());
569
570 if (!out_pipe_name)
571 {
572 PLUGIN_ERROR ("Failed to create output pipe name.\n");
573 np_error = NPERR_OUT_OF_MEMORY_ERROR;
574 goto cleanup_out_pipe_name;
575 }
576
577 // clean up any older pip
578 unlink (out_pipe_name);
579
580 PLUGIN_DEBUG ("ITNP_New: creating output fifo: %s\n", out_pipe_name);
581 if (mkfifo (out_pipe_name, 0600) == -1 && errno != EEXIST)
582 {
583 PLUGIN_ERROR ("Failed to create output pipe\n", strerror (errno));
584 np_error = NPERR_GENERIC_ERROR;
585 goto cleanup_out_pipe_name;
586 }
587 PLUGIN_DEBUG ("ITNP_New: created output fifo: %s\n", out_pipe_name);
588#endif
589
590 // Create plugin-debug-to-appletviewer pipe which we refer to as the
591 // debug pipe.
592 initialize_debug();//should be already initialized, but...
593 if (plugin_debug_to_console){
594#ifdef __OS2__
595 if (socketpair (AF_LOCAL, SOCK_STREAM, 0, debug_pipe) == -1)
596 {
597 PLUGIN_ERROR ("Failed to create debug pipe", strerror (errno));
598 np_error = NPERR_GENERIC_ERROR;
599 goto cleanup_debug_pipe;
600 }
601 PLUGIN_DEBUG ("ITNP_New: created debug fifo: %d/%d\n", debug_pipe [0], debug_pipe [1]);
602#else
603 // debug_pipe_name
604 debug_pipe_name = g_strdup_printf ("%s/%d-icedteanp-plugin-debug-to-appletviewer",
605 data_directory.c_str(), getpid());
606
607 if (!debug_pipe_name)
608 {
609 PLUGIN_ERROR ("Failed to create debug pipe name.\n");
610 np_error = NPERR_OUT_OF_MEMORY_ERROR;
611 goto cleanup_debug_pipe_name;
612 }
613
614 // clean up any older pip
615 unlink (debug_pipe_name);
616
617 PLUGIN_DEBUG ("ITNP_New: creating debug fifo: %s\n", debug_pipe_name);
618 if (mkfifo (debug_pipe_name, 0600) == -1 && errno != EEXIST)
619 {
620 PLUGIN_ERROR ("Failed to create debug pipe\n", strerror (errno));
621 np_error = NPERR_GENERIC_ERROR;
622 goto cleanup_debug_pipe_name;
623 }
624 PLUGIN_DEBUG ("ITNP_New: created debug fifo: %s\n", debug_pipe_name);
625#endif
626 }
627
628 // Start a separate appletviewer process for each applet, even if
629 // there are multiple applets in the same page. We may need to
630 // change this behaviour if we find pages with multiple applets that
631 // rely on being run in the same VM.
632
633#ifdef __OS2__
634 // make sure parent ends are not inherited by the child (otherwise read() on
635 // the child's side will not be aborted when the parent closes its end)
636 fcntl (in_pipe [0], F_SETFD, FD_CLOEXEC);
637 fcntl (out_pipe [0], F_SETFD, FD_CLOEXEC);
638 if (plugin_debug_to_console)
639 fcntl (debug_pipe [0], F_SETFD, FD_CLOEXEC);
640#endif
641
642 np_error = plugin_start_appletviewer (data);
643
644#ifdef __OS2__
645 // close child ends of the pipes (not needed)
646 CLOSE_FD (in_pipe [1]);
647 CLOSE_FD (out_pipe [1]);
648 if (plugin_debug_to_console)
649 CLOSE_FD (debug_pipe [1]);
650#endif
651
652 // Create plugin-to-appletviewer channel. The default encoding for
653 // the file is UTF-8.
654 // out_to_appletviewer
655#ifdef __OS2__
656 out_to_appletviewer = g_io_channel_unix_new (out_pipe [0]);
657#else
658 out_to_appletviewer = g_io_channel_new_file (out_pipe_name,
659 "w", &channel_error);
660#endif
661 if (!out_to_appletviewer)
662 {
663 if (channel_error)
664 {
665 PLUGIN_ERROR ("Failed to create output channel, '%s'\n",
666 channel_error->message);
667 g_error_free (channel_error);
668 channel_error = NULL;
669 }
670 else
671 PLUGIN_ERROR ("Failed to create output channel\n");
672
673 np_error = NPERR_GENERIC_ERROR;
674 goto cleanup_out_to_appletviewer;
675 }
676
677 // Watch for hangup and error signals on the output pipe.
678 out_watch_source =
679 g_io_add_watch (out_to_appletviewer,
680 (GIOCondition) (G_IO_ERR | G_IO_HUP),
681 plugin_out_pipe_callback, (gpointer) out_to_appletviewer);
682
683 // Create appletviewer-to-plugin channel. The default encoding for
684 // the file is UTF-8.
685 // in_from_appletviewer
686#ifdef __OS2__
687 in_from_appletviewer = g_io_channel_unix_new (in_pipe [0]);
688#else
689 in_from_appletviewer = g_io_channel_new_file (in_pipe_name,
690 "r", &channel_error);
691#endif
692 if (!in_from_appletviewer)
693 {
694 if (channel_error)
695 {
696 PLUGIN_ERROR ("Failed to create input channel, '%s'\n",
697 channel_error->message);
698 g_error_free (channel_error);
699 channel_error = NULL;
700 }
701 else
702 PLUGIN_ERROR ("Failed to create input channel\n");
703
704 np_error = NPERR_GENERIC_ERROR;
705 goto cleanup_in_from_appletviewer;
706 }
707
708 // Watch for hangup and error signals on the input pipe.
709 in_watch_source =
710 g_io_add_watch (in_from_appletviewer,
711 (GIOCondition) (G_IO_IN | G_IO_ERR | G_IO_HUP),
712 plugin_in_pipe_callback, (gpointer) in_from_appletviewer);
713
714 // Create plugin-to-appletviewer console debug channel. The default encoding for
715 // the file is UTF-8.
716 // debug_to_appletviewer
717 if (plugin_debug_to_console){
718#ifdef __OS2__
719 debug_to_appletviewer = g_io_channel_unix_new (debug_pipe [0]);
720#else
721 debug_to_appletviewer = g_io_channel_new_file (debug_pipe_name,
722 "w", &channel_error);
723#endif
724 if (!debug_to_appletviewer)
725 {
726 if (channel_error)
727 {
728 PLUGIN_ERROR ("Failed to debug output channel, '%s'\n",
729 channel_error->message);
730 g_error_free (channel_error);
731 channel_error = NULL;
732 }
733 else
734 PLUGIN_ERROR ("Failed to create debug channel\n");
735
736 np_error = NPERR_GENERIC_ERROR;
737 goto cleanup_debug_to_appletviewer;
738 }
739 }
740
741 jvm_up = TRUE;
742
743 if (plugin_debug_to_console){
744 //jvm is up, we can start console producer thread
745 pthread_t debug_to_console_consumer;
746 pthread_create(&debug_to_console_consumer,NULL,&flush_pre_init_messages,NULL);
747 }
748 goto done;
749
750 // Free allocated data in case of error
751 cleanup_debug_to_appletviewer:
752 if (plugin_debug_to_console){
753 if (debug_to_appletviewer)
754 g_io_channel_unref (debug_to_appletviewer);
755 debug_to_appletviewer = NULL;
756 }
757
758 cleanup_in_watch_source:
759 // Removing a source is harmless if it fails since it just means the
760 // source has already been removed.
761 g_source_remove (in_watch_source);
762 in_watch_source = 0;
763
764 cleanup_in_from_appletviewer:
765 if (in_from_appletviewer)
766 g_io_channel_unref (in_from_appletviewer);
767 in_from_appletviewer = NULL;
768
769 // cleanup_out_watch_source:
770 g_source_remove (out_watch_source);
771 out_watch_source = 0;
772
773 cleanup_out_to_appletviewer:
774 if (out_to_appletviewer)
775 g_io_channel_unref (out_to_appletviewer);
776 out_to_appletviewer = NULL;
777
778#ifdef __OS2__
779 cleanup_debug_pipe:
780 if (plugin_debug_to_console){
781 CLOSE_FD (debug_pipe [0]);
782 CLOSE_FD (debug_pipe [1]);
783 }
784#else
785 if (plugin_debug_to_console){
786 // cleanup_debug_pipe:
787 // Delete output pipe.
788 PLUGIN_DEBUG ("ITNP_New: deleting debug fifo: %s\n", debug_pipe_name);
789 unlink (debug_pipe_name);
790 PLUGIN_DEBUG ("ITNP_New: deleted debug fifo: %s\n", debug_pipe_name);
791 }
792 cleanup_debug_pipe_name:
793 if (plugin_debug_to_console){
794 g_free (debug_pipe_name);
795 debug_pipe_name = NULL;
796 }
797#endif
798
799 // cleanup_out_pipe:
800#ifdef __OS2__
801 cleanup_out_pipe:
802 CLOSE_FD (out_pipe [0]);
803 CLOSE_FD (out_pipe [1]);
804#else
805 // Delete output pipe.
806 PLUGIN_DEBUG ("ITNP_New: deleting output fifo: %s\n", out_pipe_name);
807 unlink (out_pipe_name);
808 PLUGIN_DEBUG ("ITNP_New: deleted output fifo: %s\n", out_pipe_name);
809
810 cleanup_out_pipe_name:
811 g_free (out_pipe_name);
812 out_pipe_name = NULL;
813#endif
814
815 // cleanup_in_pipe:
816#ifdef __OS2__
817 cleanup_in_pipe:
818 CLOSE_FD (in_pipe [0]);
819 CLOSE_FD (in_pipe [1]);
820#else
821 // Delete input pipe.
822 PLUGIN_DEBUG ("ITNP_New: deleting input fifo: %s\n", in_pipe_name);
823 unlink (in_pipe_name);
824 PLUGIN_DEBUG ("ITNP_New: deleted input fifo: %s\n", in_pipe_name);
825
826 cleanup_in_pipe_name:
827 g_free (in_pipe_name);
828 in_pipe_name = NULL;
829#endif
830
831 cleanUpDir();
832 done:
833
834 IcedTeaPluginUtilities::printDebugStatus();
835 // Now other threads may re-enter.. unlock the mutex
836 g_mutex_unlock(vm_start_mutex);
837 return np_error;
838
839}
840
841NPError
842ITNP_GetValue (NPP instance, NPPVariable variable, void* value)
843{
844 PLUGIN_DEBUG ("ITNP_GetValue\n");
845
846 NPError np_error = NPERR_NO_ERROR;
847
848 switch (variable)
849 {
850 // This plugin needs XEmbed support.
851 case NPPVpluginNeedsXEmbed:
852 {
853 PLUGIN_DEBUG ("ITNP_GetValue: returning TRUE for NeedsXEmbed.\n");
854 bool* bool_value = (bool*) value;
855 *bool_value = true;
856 }
857 break;
858 case NPPVpluginScriptableNPObject:
859 {
860 *(NPObject **)value = get_scriptable_object(instance);
861 }
862 break;
863 default:
864 PLUGIN_ERROR ("Unknown plugin value requested.\n");
865 np_error = NPERR_GENERIC_ERROR;
866 break;
867 }
868
869 PLUGIN_DEBUG ("ITNP_GetValue return\n");
870
871 return np_error;
872}
873
874NPError
875ITNP_Destroy (NPP instance, NPSavedData** save)
876{
877 PLUGIN_DEBUG ("ITNP_Destroy %p\n", instance);
878
879 ITNPPluginData* data = (ITNPPluginData*) instance->pdata;
880
881 int id = get_id_from_instance(instance);
882
883 // Let Java know that this applet needs to be destroyed
884 gchar* msg = (gchar*) g_malloc(512*sizeof(gchar)); // 512 is more than enough. We need < 100
885 g_sprintf(msg, "instance %d destroy", id);
886 plugin_send_message_to_appletviewer(msg);
887 g_free(msg);
888 msg = NULL;
889
890 if (data)
891 {
892 // Free plugin data.
893 plugin_data_destroy (instance);
894 }
895
896 g_hash_table_remove(instance_to_id_map, instance);
897 g_hash_table_remove(id_to_instance_map, GINT_TO_POINTER(id));
898
899 IcedTeaPluginUtilities::invalidateInstance(instance);
900
901 PLUGIN_DEBUG ("ITNP_Destroy return\n");
902
903 return NPERR_NO_ERROR;
904}
905
906NPError
907ITNP_SetWindow (NPP instance, NPWindow* window)
908{
909 PLUGIN_DEBUG ("ITNP_SetWindow\n");
910
911 if (instance == NULL)
912 {
913 PLUGIN_ERROR ("Invalid instance.\n");
914
915 return NPERR_INVALID_INSTANCE_ERROR;
916 }
917
918 gpointer id_ptr = g_hash_table_lookup(instance_to_id_map, instance);
919
920 gint id = 0;
921 if (id_ptr)
922 {
923 id = GPOINTER_TO_INT(id_ptr);
924 }
925
926 ITNPPluginData* data = (ITNPPluginData*) instance->pdata;
927
928 // Simply return if we receive a NULL window.
929 if ((window == NULL) || (window->window == NULL))
930 {
931 PLUGIN_DEBUG ("ITNP_SetWindow: got NULL window.\n");
932
933 return NPERR_NO_ERROR;
934 }
935
936#ifdef __OS2__
937 void *wnd = wrap_window_handle (window->window);
938 if (!wnd)
939 return NPERR_GENERIC_ERROR;
940
941 PLUGIN_DEBUG ("ITNP_SetWindow: wrapped window handle %d (%x) in %d (%x)\n",
942 window->window, window->window, wnd, wnd);
943#endif
944
945 if (data->window_handle)
946 {
947 // The window already exists.
948 if (data->window_handle == wnd)
949 {
950 // The parent window is the same as in previous calls.
951 PLUGIN_DEBUG ("ITNP_SetWindow: window already exists.\n");
952
953 // Critical region. Read data->appletviewer_mutex and send
954 // a message to the appletviewer.
955 g_mutex_lock (data->appletviewer_mutex);
956
957 if (jvm_up)
958 {
959 gboolean dim_changed = FALSE;
960
961 // The window is the same as it was for the last
962 // SetWindow call.
963 if (window->width != data->window_width)
964 {
965 PLUGIN_DEBUG ("ITNP_SetWindow: window width changed.\n");
966 // The width of the plugin window has changed.
967
968 // Store the new width.
969 data->window_width = window->width;
970 dim_changed = TRUE;
971 }
972
973 if (window->height != data->window_height)
974 {
975 PLUGIN_DEBUG ("ITNP_SetWindow: window height changed.\n");
976 // The height of the plugin window has changed.
977
978 // Store the new height.
979 data->window_height = window->height;
980
981 dim_changed = TRUE;
982 }
983
984 if (dim_changed) {
985 gchar* message = g_strdup_printf ("instance %d width %d height %d",
986 id, window->width, window->height);
987 plugin_send_message_to_appletviewer (message);
988 g_free (message);
989 message = NULL;
990 }
991
992
993 }
994 else
995 {
996 // The appletviewer is not running.
997 PLUGIN_DEBUG ("ITNP_SetWindow: appletviewer is not running.\n");
998 }
999
1000 g_mutex_unlock (data->appletviewer_mutex);
1001 }
1002 else
1003 {
1004 // The parent window has changed. This branch does run but
1005 // doing nothing in response seems to be sufficient.
1006 PLUGIN_DEBUG ("ITNP_SetWindow: parent window changed.\n");
1007 }
1008 }
1009 else
1010 {
1011
1012 // Else this is initialization
1013 PLUGIN_DEBUG ("ITNP_SetWindow: setting window.\n");
1014
1015 // Critical region. Send messages to appletviewer.
1016 g_mutex_lock (data->appletviewer_mutex);
1017
1018 // Store the window handle and dimensions
1019 data->window_handle = wnd;
1020 data->window_width = window->width;
1021 data->window_height = window->height;
1022
1023 // Now we have everything. Send this data to the Java side
1024 plugin_send_initialization_message(
1025 data->instance_id, (gulong) data->window_handle,
1026 data->window_width, data->window_height, data->parameters_string);
1027
1028 g_mutex_unlock (data->appletviewer_mutex);
1029
1030 }
1031
1032 PLUGIN_DEBUG ("ITNP_SetWindow return\n");
1033
1034 return NPERR_NO_ERROR;
1035}
1036
1037NPError
1038ITNP_NewStream (NPP instance, NPMIMEType type, NPStream* stream,
1039 NPBool seekable, uint16_t* stype)
1040{
1041 PLUGIN_DEBUG ("ITNP_NewStream\n");
1042
1043 PLUGIN_DEBUG ("ITNP_NewStream return\n");
1044
1045 return NPERR_GENERIC_ERROR;
1046}
1047
1048void
1049ITNP_StreamAsFile (NPP instance, NPStream* stream, const char* filename)
1050{
1051 PLUGIN_DEBUG ("ITNP_StreamAsFile\n");
1052
1053 PLUGIN_DEBUG ("ITNP_StreamAsFile return\n");
1054}
1055
1056NPError
1057ITNP_DestroyStream (NPP instance, NPStream* stream, NPReason reason)
1058{
1059 PLUGIN_DEBUG ("ITNP_DestroyStream\n");
1060
1061 PLUGIN_DEBUG ("ITNP_DestroyStream return\n");
1062
1063 return NPERR_NO_ERROR;
1064}
1065
1066int32_t
1067ITNP_WriteReady (NPP instance, NPStream* stream)
1068{
1069 PLUGIN_DEBUG ("ITNP_WriteReady\n");
1070
1071 PLUGIN_DEBUG ("ITNP_WriteReady return\n");
1072
1073 return 0;
1074}
1075
1076int32_t
1077ITNP_Write (NPP instance, NPStream* stream, int32_t offset, int32_t len,
1078 void* buffer)
1079{
1080 PLUGIN_DEBUG ("ITNP_Write\n");
1081
1082 PLUGIN_DEBUG ("ITNP_Write return\n");
1083
1084 return 0;
1085}
1086
1087void
1088ITNP_Print (NPP instance, NPPrint* platformPrint)
1089{
1090 PLUGIN_DEBUG ("ITNP_Print\n");
1091
1092 PLUGIN_DEBUG ("ITNP_Print return\n");
1093}
1094
1095int16_t
1096ITNP_HandleEvent (NPP instance, void* event)
1097{
1098 PLUGIN_DEBUG ("ITNP_HandleEvent\n");
1099
1100 PLUGIN_DEBUG ("ITNP_HandleEvent return\n");
1101
1102 return 0;
1103}
1104
1105void
1106ITNP_URLNotify (NPP instance, const char* url, NPReason reason,
1107 void* notifyData)
1108{
1109 PLUGIN_DEBUG ("ITNP_URLNotify\n");
1110
1111 PLUGIN_DEBUG ("ITNP_URLNotify return\n");
1112}
1113
1114NPError
1115get_cookie_info(const char* siteAddr, char** cookieString, uint32_t* len)
1116{
1117 // Only attempt to perform this operation if there is a valid plugin instance
1118 if (g_hash_table_size(instance_to_id_map) <= 0)
1119 {
1120 return NPERR_GENERIC_ERROR;
1121 }
1122 // getvalueforurl needs an NPP instance. Quite frankly, there is no easy way
1123 // to know which instance needs the information, as applets on Java side can
1124 // be multi-threaded and the thread making a proxy.cookie request cannot be
1125 // easily tracked.
1126
1127 // Fortunately, XULRunner does not care about the instance as long as it is
1128 // valid. So we just pick the first valid one and use it. Proxy/Cookie
1129 // information is not instance specific anyway, it is URL specific.
1130
1131 if (browser_functions.getvalueforurl)
1132 {
1133 gpointer instance=getFirstInTableInstance(instance_to_id_map);
1134 return browser_functions.getvalueforurl((NPP) instance, NPNURLVCookie, siteAddr, cookieString, len);
1135 } else
1136 {
1137 return NPERR_GENERIC_ERROR;
1138 }
1139
1140 return NPERR_NO_ERROR;
1141}
1142
1143static NPError
1144set_cookie_info(const char* siteAddr, const char* cookieString, uint32_t len)
1145{
1146 // Only attempt to perform this operation if there is a valid plugin instance
1147 if (g_hash_table_size(instance_to_id_map) > 0 && browser_functions.getvalueforurl)
1148 {
1149 // We arbitrarily use the first valid instance we can grab
1150 // For an explanation of the logic behind this, see get_cookie_info
1151 gpointer instance = getFirstInTableInstance(instance_to_id_map);
1152 return browser_functions.setvalueforurl((NPP) instance, NPNURLVCookie, siteAddr, cookieString, len);
1153 }
1154
1155 return NPERR_GENERIC_ERROR;;
1156}
1157
1158// HELPER FUNCTIONS
1159
1160ITNPPluginData*
1161plugin_data_new ()
1162{
1163 PLUGIN_DEBUG ("plugin_data_new\n");
1164
1165 ITNPPluginData* data = (ITNPPluginData*)browser_functions.memalloc(sizeof (struct ITNPPluginData));
1166
1167 if (data)
1168 {
1169 // Call constructor on allocated data
1170 new (data) ITNPPluginData();
1171 }
1172 PLUGIN_DEBUG ("plugin_data_new return\n");
1173
1174 return data;
1175}
1176
1177
1178
1179// Documentbase retrieval. This function gets the current document's
1180// documentbase. This function relies on browser-private data so it
1181// will only work when the plugin is loaded in a Mozilla-based
1182// browser.
1183static std::string
1184plugin_get_documentbase (NPP instance)
1185{
1186 PLUGIN_DEBUG ("plugin_get_documentbase\n");
1187
1188 // FIXME: This method is not ideal, but there are no known NPAPI call
1189 // for this. See thread for more information:
1190 // http://www.mail-archive.com/chromium-dev@googlegroups.com/msg04844.html
1191
1192 // Additionally, since it is insecure, we cannot use it for making
1193 // security decisions.
1194 NPObject* window;
1195 browser_functions.getvalue(instance, NPNVWindowNPObject, &window);
1196
1197 NPVariant location;
1198 NPIdentifier location_id = browser_functions.getstringidentifier("location");
1199 browser_functions.getproperty(instance, window, location_id, &location);
1200
1201 NPVariant href;
1202 NPIdentifier href_id = browser_functions.getstringidentifier("href");
1203 browser_functions.getproperty(instance, NPVARIANT_TO_OBJECT(location),
1204 href_id, &href);
1205
1206 std::string href_str = IcedTeaPluginUtilities::NPVariantAsString(href);
1207
1208 // Release references.
1209 browser_functions.releasevariantvalue(&href);
1210 browser_functions.releasevariantvalue(&location);
1211
1212 PLUGIN_DEBUG ("plugin_get_documentbase return\n");
1213 PLUGIN_DEBUG("plugin_get_documentbase returning: %s\n", href_str.c_str());
1214
1215 return href_str;
1216}
1217
1218// plugin_in_pipe_callback is called when data is available on the
1219// input pipe, or when the appletviewer crashes or is killed. It may
1220// be called after data has been destroyed in which case it simply
1221// returns FALSE to remove itself from the glib main loop.
1222static gboolean
1223plugin_in_pipe_callback (GIOChannel* source,
1224 GIOCondition condition,
1225 gpointer plugin_data)
1226{
1227 PLUGIN_DEBUG ("plugin_in_pipe_callback\n");
1228
1229 gboolean keep_installed = TRUE;
1230
1231 if (condition & G_IO_IN)
1232 {
1233 gchar* message = NULL;
1234
1235 if (g_io_channel_read_line (in_from_appletviewer,
1236 &message, NULL, NULL,
1237 &channel_error)
1238 != G_IO_STATUS_NORMAL)
1239 {
1240 if (channel_error)
1241 {
1242 PLUGIN_ERROR ("Failed to read line from input channel, %s\n",
1243 channel_error->message);
1244 g_error_free (channel_error);
1245 channel_error = NULL;
1246 }
1247 else
1248 PLUGIN_ERROR ("Failed to read line from input channel\n");
1249#ifdef __OS2__
1250 // G_IO_ERR/HUP is not reported on file/pipe handles, simulate it
1251 condition = (GIOCondition) (condition | G_IO_ERR);
1252#endif
1253 } else
1254 {
1255 consume_message(message);
1256 }
1257
1258 g_free (message);
1259 message = NULL;
1260
1261 keep_installed = TRUE;
1262 }
1263
1264 if (condition & (G_IO_ERR | G_IO_HUP))
1265 {
1266 PLUGIN_DEBUG ("appletviewer has stopped.\n");
1267 keep_installed = FALSE;
1268 }
1269
1270 PLUGIN_DEBUG ("plugin_in_pipe_callback return\n");
1271
1272 return keep_installed;
1273}
1274
1275static
1276void consume_plugin_message(gchar* message) {
1277 // internal plugin related message
1278 gchar** parts = g_strsplit (message, " ", 5);
1279 if (g_str_has_prefix(parts[1], "PluginProxyInfo"))
1280 {
1281 gchar* proxy = NULL;
1282 uint32_t len;
1283
1284 gchar* decoded_url = (gchar*) calloc(strlen(parts[4]) + 1, sizeof(gchar));
1285 IcedTeaPluginUtilities::decodeURL(parts[4], &decoded_url);
1286 PLUGIN_DEBUG("parts[0]=%s, parts[1]=%s, reference, parts[3]=%s, parts[4]=%s -- decoded_url=%s\n", parts[0], parts[1], parts[3], parts[4], decoded_url);
1287
1288 gchar* proxy_info;
1289
1290 proxy_info = g_strconcat ("plugin PluginProxyInfo reference ", parts[3], " ", NULL);
1291 if (get_proxy_info(decoded_url, &proxy, &len) == NPERR_NO_ERROR)
1292 {
1293 proxy_info = g_strconcat (proxy_info, proxy, NULL);
1294 }
1295
1296 PLUGIN_DEBUG("Proxy info: %s\n", proxy_info);
1297 plugin_send_message_to_appletviewer(proxy_info);
1298
1299 free(decoded_url);
1300 decoded_url = NULL;
1301 g_free(proxy_info);
1302 proxy_info = NULL;
1303
1304 g_free(proxy);
1305 proxy = NULL;
1306
1307 } else if (g_str_has_prefix(parts[1], "PluginCookieInfo"))
1308 {
1309 gchar* decoded_url = (gchar*) calloc(strlen(parts[4])+1, sizeof(gchar));
1310 IcedTeaPluginUtilities::decodeURL(parts[4], &decoded_url);
1311
1312 gchar* cookie_info = g_strconcat ("plugin PluginCookieInfo reference ", parts[3], " ", NULL);
1313 gchar* cookie_string = NULL;
1314 uint32_t len;
1315 if (get_cookie_info(decoded_url, &cookie_string, &len) == NPERR_NO_ERROR)
1316 {
1317 cookie_info = g_strconcat (cookie_info, cookie_string, NULL);
1318 }
1319
1320 PLUGIN_DEBUG("Cookie info: %s\n", cookie_info);
1321 plugin_send_message_to_appletviewer(cookie_info);
1322
1323 free(decoded_url);
1324 decoded_url = NULL;
1325 g_free(cookie_info);
1326 cookie_info = NULL;
1327 g_free(cookie_string);
1328 cookie_string = NULL;
1329 } else if (g_str_has_prefix(parts[1], "PluginSetCookie"))
1330 {
1331 // Message structure: plugin PluginSetCookie reference -1 <url> <cookie>
1332 gchar** cookie_parts = g_strsplit (message, " ", 6);
1333
1334 if (g_strv_length(cookie_parts) < 6)
1335 {
1336 g_strfreev (parts);
1337 g_strfreev (cookie_parts);
1338 return; // Defensive, message _should_ be properly formatted
1339 }
1340
1341 gchar* decoded_url = (gchar*) calloc(strlen(cookie_parts[4])+1, sizeof(gchar));
1342 IcedTeaPluginUtilities::decodeURL(cookie_parts[4], &decoded_url);
1343
1344 gchar* cookie_string = cookie_parts[5];
1345 uint32_t len = strlen(cookie_string);
1346 if (set_cookie_info(decoded_url, cookie_string, len) == NPERR_NO_ERROR)
1347 {
1348 PLUGIN_DEBUG("Setting cookie for URL %s to %s\n", decoded_url, cookie_string);
1349 } else
1350 {
1351 PLUGIN_DEBUG("Not able to set cookie for URL %s to %s\n", decoded_url, cookie_string);
1352 }
1353
1354 free(decoded_url);
1355 decoded_url = NULL;
1356 g_strfreev (cookie_parts);
1357 cookie_parts = NULL;
1358 }
1359
1360 g_strfreev (parts);
1361 parts = NULL;
1362}
1363
1364void consume_message(gchar* message) {
1365
1366 PLUGIN_DEBUG (" PIPE: plugin read: %s\n", message);
1367
1368 if (g_str_has_prefix (message, "instance"))
1369 {
1370
1371 ITNPPluginData* data;
1372 gchar** parts = g_strsplit (message, " ", -1);
1373 guint parts_sz = g_strv_length (parts);
1374
1375 int instance_id = atoi(parts[1]);
1376 NPP instance = (NPP) g_hash_table_lookup(id_to_instance_map,
1377 GINT_TO_POINTER(instance_id));
1378
1379 if (instance_id > 0 && !instance)
1380 {
1381 PLUGIN_DEBUG("Instance %d is not active. Refusing to consume message \"%s\"\n", instance_id, message);
1382 return;
1383 }
1384 else if (instance)
1385 {
1386 data = (ITNPPluginData*) instance->pdata;
1387 }
1388
1389 if (g_str_has_prefix (parts[2], "status"))
1390 {
1391
1392 // clear the "instance X status" parts
1393 strcpy(parts[0], "");
1394 strcpy(parts[1], "");
1395 strcpy(parts[2], "");
1396
1397 // join the rest
1398 gchar* status_message = g_strjoinv(" ", parts);
1399 PLUGIN_DEBUG ("plugin_in_pipe_callback: setting status %s\n", status_message);
1400 (*browser_functions.status) (data->owner, status_message);
1401
1402 g_free(status_message);
1403 status_message = NULL;
1404 }
1405 else if (g_str_has_prefix (parts[1], "internal"))
1406 {
1407 //s->post(message);
1408 }
1409 else
1410 {
1411 // All other messages are posted to the bus, and subscribers are
1412 // expected to take care of them. They better!
1413
1414 java_to_plugin_bus->post(message);
1415 }
1416
1417 g_strfreev (parts);
1418 parts = NULL;
1419 }
1420 else if (g_str_has_prefix (message, "context"))
1421 {
1422 java_to_plugin_bus->post(message);
1423 }
1424 else if (g_str_has_prefix (message, "plugin "))
1425 {
1426 consume_plugin_message(message);
1427 }
1428 else
1429 {
1430 g_print (" Unable to handle message: %s\n", message);
1431 }
1432
1433}
1434
1435void get_instance_from_id(int id, NPP& instance)
1436{
1437 instance = (NPP) g_hash_table_lookup(id_to_instance_map,
1438 GINT_TO_POINTER(id));
1439}
1440
1441int get_id_from_instance(NPP instance)
1442{
1443 int id = GPOINTER_TO_INT(g_hash_table_lookup(instance_to_id_map,
1444 instance));
1445 PLUGIN_DEBUG("Returning id %d for instance %p\n", id, instance);
1446 return id;
1447}
1448
1449NPError
1450get_proxy_info(const char* siteAddr, char** proxy, uint32_t* len)
1451{
1452 // Only attempt to perform this operation if there is a valid plugin instance
1453 if (g_hash_table_size(instance_to_id_map) <= 0)
1454 {
1455 return NPERR_GENERIC_ERROR;
1456 }
1457 if (browser_functions.getvalueforurl)
1458 {
1459
1460 // As in get_cookie_info, we use the first active instance
1461 gpointer instance=getFirstInTableInstance(instance_to_id_map);
1462 browser_functions.getvalueforurl((NPP) instance, NPNURLVProxy, siteAddr, proxy, len);
1463 } else
1464 {
1465 return NPERR_GENERIC_ERROR;
1466 }
1467
1468 return NPERR_NO_ERROR;
1469}
1470
1471// plugin_out_pipe_callback is called when the appletviewer crashes or
1472// is killed. It may be called after data has been destroyed in which
1473// case it simply returns FALSE to remove itself from the glib main
1474// loop.
1475static gboolean
1476plugin_out_pipe_callback (GIOChannel* source,
1477 GIOCondition condition,
1478 gpointer plugin_data)
1479{
1480 PLUGIN_DEBUG ("plugin_out_pipe_callback\n");
1481
1482 ITNPPluginData* data = (ITNPPluginData*) plugin_data;
1483
1484 PLUGIN_DEBUG ("plugin_out_pipe_callback: appletviewer has stopped.\n");
1485
1486 PLUGIN_DEBUG ("plugin_out_pipe_callback return\n");
1487
1488 return FALSE;
1489}
1490
1491// remove all components from LD_LIBRARY_PATH, which start with
1492// MOZILLA_FIVE_HOME; firefox has its own NSS based security provider,
1493// which conflicts with the one configured in nss.cfg.
1494static gchar*
1495plugin_filter_ld_library_path(gchar *path_old)
1496{
1497 gchar *moz_home = g_strdup (g_getenv ("MOZILLA_FIVE_HOME"));
1498 gchar *moz_prefix;
1499 gchar *path_new;
1500 gchar** components;
1501 int i1, i2;
1502
1503 if (moz_home == NULL || path_old == NULL || strlen (path_old) == 0)
1504 return path_old;
1505 if (g_str_has_suffix (moz_home, "/"))
1506 moz_home[strlen (moz_home - 1)] = '\0';
1507 moz_prefix = g_strconcat (moz_home, "/", NULL);
1508
1509 components = g_strsplit (path_old, ":", -1);
1510 for (i1 = 0, i2 = 0; components[i1] != NULL; i1++)
1511 {
1512 if (g_strcmp0 (components[i1], moz_home) == 0
1513 || g_str_has_prefix (components[i1], moz_home))
1514 components[i2] = components[i1];
1515 else
1516 components[i2++] = components[i1];
1517 }
1518 components[i2] = NULL;
1519
1520 if (i1 > i2)
1521 path_new = g_strjoinv (":", components);
1522 g_strfreev (components);
1523 g_free (moz_home);
1524 g_free (moz_prefix);
1525 g_free (path_old);
1526
1527 if (path_new == NULL || strlen (path_new) == 0)
1528 {
1529 PLUGIN_DEBUG("Unset LD_LIBRARY_PATH\n");
1530 return NULL;
1531 }
1532 else
1533 {
1534 PLUGIN_DEBUG ("Set LD_LIBRARY_PATH: %s\n", path_new);
1535 return path_new;
1536 }
1537}
1538
1539// build the environment to pass to the external plugin process
1540static gchar**
1541plugin_filter_environment(void)
1542{
1543 gchar **var_names = g_listenv();
1544 gchar **new_env = (gchar**) malloc(sizeof(gchar*) * (g_strv_length (var_names) + 1));
1545 int i_var, i_env;
1546
1547 for (i_var = 0, i_env = 0; var_names[i_var] != NULL; i_var++)
1548 {
1549 gchar *env_value = g_strdup (g_getenv (var_names[i_var]));
1550
1551 if (g_str_has_prefix (var_names[i_var], "LD_LIBRARY_PATH"))
1552 env_value = plugin_filter_ld_library_path (env_value);
1553 if (env_value != NULL)
1554 {
1555 new_env[i_env++] = g_strdup_printf ("%s=%s", var_names[i_var], env_value);
1556 g_free (env_value);
1557 }
1558 }
1559 new_env[i_env] = NULL;
1560 return new_env;
1561}
1562
1563static NPError
1564plugin_test_appletviewer ()
1565{
1566
1567 PLUGIN_DEBUG ("plugin_test_appletviewer: %s\n", get_plugin_executable().c_str());
1568 NPError error = NPERR_NO_ERROR;
1569
1570 gchar* command_line[3] = { NULL, NULL, NULL };
1571 gchar** environment;
1572
1573 command_line[0] = g_strdup (get_plugin_executable().c_str());
1574 command_line[1] = g_strdup("-version");
1575 command_line[2] = NULL;
1576
1577 environment = plugin_filter_environment();
1578
1579 if (!g_spawn_async (NULL, command_line, environment,
1580 (GSpawnFlags) 0,
1581 NULL, NULL, NULL, &channel_error))
1582 {
1583 if (channel_error)
1584 {
1585 PLUGIN_ERROR ("Failed to spawn applet viewer %s\n",
1586 channel_error->message);
1587 g_error_free (channel_error);
1588 channel_error = NULL;
1589 }
1590 else
1591 PLUGIN_ERROR ("Failed to spawn applet viewer\n");
1592 error = NPERR_GENERIC_ERROR;
1593 }
1594
1595 g_strfreev (environment);
1596
1597 g_free (command_line[0]);
1598 command_line[0] = NULL;
1599 g_free (command_line[1]);
1600 command_line[1] = NULL;
1601 g_free (command_line[2]);
1602 command_line[2] = NULL;
1603
1604 PLUGIN_DEBUG ("plugin_test_appletviewer return\n");
1605 return error;
1606}
1607
1608NPError
1609plugin_start_appletviewer (ITNPPluginData* data)
1610{
1611 PLUGIN_DEBUG ("plugin_start_appletviewer\n");
1612 NPError error = NPERR_NO_ERROR;
1613
1614 std::vector<std::string> command_line;
1615 gchar** environment = NULL;
1616 std::vector<std::string*>* jvm_args = get_jvm_args();
1617
1618 // Construct command line parameters
1619
1620 command_line.push_back(get_plugin_executable());
1621
1622 //Add JVM args to command_line
1623 for (int i = 0; i < jvm_args->size(); i++)
1624 {
1625 command_line.push_back(*jvm_args->at(i));
1626 }
1627
1628#ifdef __OS2__
1629 {
1630 std::string path = PLUGIN_BOOTCLASSPATH;
1631 static const std::string datadir = "@DATADIR@";
1632 size_t pos = 0;
1633 while ((pos = path.find(datadir, pos)) != std::string::npos) {
1634 path.replace(pos, datadir.length(), icedtea_web_data_dir());
1635 pos += datadir.length();
1636 }
1637 command_line.push_back(path);
1638 }
1639#else
1640 command_line.push_back(PLUGIN_BOOTCLASSPATH);
1641#endif
1642 // set the classpath to avoid using the default (cwd).
1643 command_line.push_back("-classpath");
1644 command_line.push_back(get_plugin_rt_jar());
1645
1646 // Enable coverage agent if we are running instrumented plugin
1647#ifdef COVERAGE_AGENT
1648 command_line.push_back(COVERAGE_AGENT);
1649#endif
1650
1651 if (plugin_debug)
1652 {
1653 command_line.push_back("-Xdebug");
1654 command_line.push_back("-Xnoagent");
1655
1656 //Debug flags
1657 std::string debug_flags = "-Xrunjdwp:transport="DT_SOCKET_DLL",address=8787,server=y,";
1658 debug_flags += plugin_debug_suspend ? "suspend=y" : "suspend=n";
1659 command_line.push_back(debug_flags);
1660 }
1661
1662 command_line.push_back("sun.applet.PluginMain");
1663#ifdef __OS2__
1664 command_line.push_back(static_cast<std::ostringstream &>(std::ostringstream() << out_pipe[1]).str());
1665 command_line.push_back(static_cast<std::ostringstream &>(std::ostringstream() << in_pipe[1]).str());
1666 if (plugin_debug_to_console)
1667 command_line.push_back(static_cast<std::ostringstream &>(std::ostringstream() << debug_pipe[1]).str());
1668#else
1669 command_line.push_back(out_pipe_name);
1670 command_line.push_back(in_pipe_name);
1671 if (plugin_debug_to_console){
1672 command_line.push_back(debug_pipe_name);
1673 }
1674#endif
1675
1676 // Finished command line parameters
1677
1678 environment = plugin_filter_environment();
1679 std::vector<gchar*> vector_gchar = IcedTeaPluginUtilities::vectorStringToVectorGchar(&command_line);
1680 gchar **command_line_args = &vector_gchar[0];
1681
1682 if (!g_spawn_async (NULL, command_line_args, environment,
1683#ifdef __OS2__
1684 (GSpawnFlags) (G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD),
1685#else
1686 (GSpawnFlags) G_SPAWN_DO_NOT_REAP_CHILD,
1687#endif
1688 NULL, NULL, &appletviewer_pid, &channel_error))
1689 {
1690 if (channel_error)
1691 {
1692 PLUGIN_ERROR ("Failed to spawn applet viewer %s\n",
1693 channel_error->message);
1694 g_error_free (channel_error);
1695 channel_error = NULL;
1696 }
1697 else
1698 PLUGIN_ERROR ("Failed to spawn applet viewer\n");
1699 error = NPERR_GENERIC_ERROR;
1700 }
1701
1702 // make sure we get an error rather than crash the browser when talking to the
1703 // jvm process if it dies unexpectedly (we do it here since g_spawn() is known
1704 // to reset SIGPIPE handler to SIG_DFL)
1705 signal (SIGPIPE, SIG_IGN);
1706
1707 //Free memory
1708 g_strfreev(environment);
1709 IcedTeaPluginUtilities::freeStringPtrVector(jvm_args);
1710 jvm_args = NULL;
1711 command_line_args = NULL;
1712
1713 if (appletviewer_pid)
1714 {
1715 PLUGIN_DEBUG("Initialized VM with pid=%d\n", appletviewer_pid);
1716 appletviewer_watch_id = g_child_watch_add(appletviewer_pid, (GChildWatchFunc) appletviewer_monitor, (gpointer) appletviewer_pid);
1717 }
1718
1719
1720 PLUGIN_DEBUG ("plugin_start_appletviewer return\n");
1721 return error;
1722}
1723
1724/*
1725 * Returns JVM options set in itw-settings
1726 */
1727std::vector<std::string*>*
1728get_jvm_args()
1729{
1730 std::string output;
1731 std::vector<std::string*>* tokenOutput = NULL;
1732 bool args_defined = read_deploy_property_value("deployment.plugin.jvm.arguments", output);
1733 if (!args_defined){
1734 return new std::vector<std::string*>();
1735 }
1736 tokenOutput = IcedTeaPluginUtilities::strSplit(output.c_str(), " \n");
1737 return tokenOutput;
1738}
1739
1740
1741/*
1742 * Escape characters for passing to Java.
1743 * "\n" for new line, "\\" for "\", "\:" for ";"
1744 */
1745std::string
1746escape_parameter_string(const char* to_encode) {
1747 std::string encoded;
1748
1749 if (to_encode == NULL)
1750 {
1751 return encoded;
1752 }
1753
1754 size_t length = strlen(to_encode);
1755 for (int i = 0; i < length; i++)
1756 {
1757 if (to_encode[i] == '\n')
1758 encoded += "\\n";
1759 else if (to_encode[i] == '\\')
1760 encoded += "\\\\";
1761 else if (to_encode[i] == ';')
1762 encoded += "\\:";
1763 else
1764 encoded += to_encode[i];
1765 }
1766
1767 return encoded;
1768}
1769
1770/*
1771 * Build a string containing an encoded list of parameters to send to the applet viewer.
1772 * The parameters are separated as 'key1;value1;key2;value2;'. As well, they are
1773 * separated and escaped as:
1774 * "\n" for new line, "\\" for "\", "\:" for ";"
1775 */
1776std::string
1777plugin_parameters_string (int argc, char* argn[], char* argv[])
1778{
1779 PLUGIN_DEBUG ("plugin_parameters_string\n");
1780
1781 std::string parameters;
1782
1783 for (int i = 0; i < argc; i++)
1784 {
1785 if (argv[i] != NULL)
1786 {
1787 std::string name_escaped = escape_parameter_string(argn[i]);
1788 std::string value_escaped = escape_parameter_string(argv[i]);
1789
1790 //Encode parameters and send as 'key1;value1;key2;value2;' etc
1791 parameters += name_escaped;
1792 parameters += ';';
1793 parameters += value_escaped;
1794 parameters += ';';
1795 }
1796 }
1797
1798 PLUGIN_DEBUG ("plugin_parameters_string return\n");
1799
1800 return parameters;
1801}
1802
1803// plugin_send_message_to_appletviewer must be called while holding
1804// data->appletviewer_mutex.
1805void
1806plugin_send_message_to_appletviewer (gchar const* message)
1807{
1808 PLUGIN_DEBUG ("plugin_send_message_to_appletviewer\n");
1809
1810 if (jvm_up)
1811 {
1812 gchar* newline_message = NULL;
1813 gsize bytes_written = 0;
1814
1815 // Send message to appletviewer.
1816 newline_message = g_strdup_printf ("%s\n", message);
1817
1818 // g_io_channel_write_chars will return something other than
1819 // G_IO_STATUS_NORMAL if not all the data is written. In that
1820 // case we fail rather than retrying.
1821 if (g_io_channel_write_chars (out_to_appletviewer,
1822 newline_message, -1, &bytes_written,
1823 &channel_error)
1824 != G_IO_STATUS_NORMAL)
1825 {
1826 if (channel_error)
1827 {
1828 PLUGIN_ERROR ("Failed to write bytes to output channel '%s' \n",
1829 channel_error->message);
1830 g_error_free (channel_error);
1831 channel_error = NULL;
1832 }
1833 else
1834 PLUGIN_ERROR ("Failed to write bytes to output channel for %s", newline_message);
1835 }
1836
1837 if (g_io_channel_flush (out_to_appletviewer, &channel_error)
1838 != G_IO_STATUS_NORMAL)
1839 {
1840 if (channel_error)
1841 {
1842 PLUGIN_ERROR ("Failed to flush bytes to output channel '%s'\n",
1843 channel_error->message);
1844 g_error_free (channel_error);
1845 channel_error = NULL;
1846 }
1847 else
1848 PLUGIN_ERROR ("Failed to flush bytes to output channel for %s", newline_message);
1849 }
1850 g_free (newline_message);
1851 newline_message = NULL;
1852
1853 PLUGIN_DEBUG (" PIPE: plugin wrote(?): %s\n", message);
1854 }
1855
1856 PLUGIN_DEBUG ("plugin_send_message_to_appletviewer return\n");
1857}
1858
1859// unlike like plugin_send_message_to_appletviewer
1860// do not debug
1861// do not error
1862// do not have its own line end
1863// is accesed by only one thread
1864// have own pipe
1865// jvm must be up
1866void
1867plugin_send_message_to_appletviewer_console (gchar const* newline_message)
1868{
1869 gsize bytes_written = 0;
1870 if (g_io_channel_write_chars (debug_to_appletviewer,
1871 newline_message, -1, &bytes_written,
1872 &channel_error) != G_IO_STATUS_NORMAL) {
1873 if (channel_error) {
1874 //error must be freed
1875 g_error_free (channel_error);
1876 channel_error = NULL;
1877 }
1878 }
1879}
1880//flush only when its full
1881void flush_plugin_send_message_to_appletviewer_console (){
1882 if (g_io_channel_flush (debug_to_appletviewer, &channel_error)
1883 != G_IO_STATUS_NORMAL) {
1884 if (channel_error) {
1885 g_error_free (channel_error);
1886 channel_error = NULL;
1887 }
1888 }
1889}
1890
1891/*
1892 * Sends the initialization message (handle/size/url) to the plugin
1893 */
1894void
1895plugin_send_initialization_message(char* instance, gulong handle,
1896 int width, int height, char* url)
1897{
1898 PLUGIN_DEBUG ("plugin_send_initialization_message\n");
1899
1900 gchar *window_message = g_strdup_printf ("instance %s handle %ld width %d height %d %s",
1901 instance, handle, width, height, url);
1902 plugin_send_message_to_appletviewer (window_message);
1903 g_free (window_message);
1904 window_message = NULL;
1905
1906 PLUGIN_DEBUG ("plugin_send_initialization_message return\n");
1907}
1908
1909
1910// Stop the appletviewer process. When this is called the
1911// appletviewer can be in any of three states: running, crashed or
1912// hung. If the appletviewer is running then sending it "shutdown"
1913// will cause it to exit. This will cause
1914// plugin_out_pipe_callback/plugin_in_pipe_callback to be called and
1915// the input and output channels to be shut down. If the appletviewer
1916// has crashed then plugin_out_pipe_callback/plugin_in_pipe_callback
1917// would already have been called and data->appletviewer_alive cleared
1918// in which case this function simply returns. If the appletviewer is
1919// hung then this function will be successful and the input and output
1920// watches will be removed by plugin_data_destroy.
1921// plugin_stop_appletviewer must be called with
1922// data->appletviewer_mutex held.
1923static void
1924plugin_stop_appletviewer ()
1925{
1926 PLUGIN_DEBUG ("plugin_stop_appletviewer\n");
1927
1928 if (jvm_up)
1929 {
1930 // Shut down the appletviewer.
1931 gsize bytes_written = 0;
1932
1933 if (out_to_appletviewer)
1934 {
1935 if (g_io_channel_write_chars (out_to_appletviewer, "shutdown",
1936 -1, &bytes_written, &channel_error)
1937 != G_IO_STATUS_NORMAL)
1938 {
1939 if (channel_error)
1940 {
1941 PLUGIN_ERROR ("Failed to write shutdown message to "
1942 " appletviewer, %s \n", channel_error->message);
1943 g_error_free (channel_error);
1944 channel_error = NULL;
1945 }
1946 else
1947 PLUGIN_ERROR ("Failed to write shutdown message to\n");
1948 }
1949
1950 if (g_io_channel_flush (out_to_appletviewer, &channel_error)
1951 != G_IO_STATUS_NORMAL)
1952 {
1953 if (channel_error)
1954 {
1955 PLUGIN_ERROR ("Failed to write shutdown message to"
1956 " appletviewer %s \n", channel_error->message);
1957 g_error_free (channel_error);
1958 channel_error = NULL;
1959 }
1960 else
1961 PLUGIN_ERROR ("Failed to write shutdown message to\n");
1962 }
1963
1964 if (g_io_channel_shutdown (out_to_appletviewer,
1965 TRUE, &channel_error)
1966 != G_IO_STATUS_NORMAL)
1967 {
1968 if (channel_error)
1969 {
1970 PLUGIN_ERROR ("Failed to shut down appletviewer"
1971 " output channel %s \n", channel_error->message);
1972 g_error_free (channel_error);
1973 channel_error = NULL;
1974 }
1975 else
1976 PLUGIN_ERROR ("Failed to shut down appletviewer\n");
1977 }
1978 }
1979
1980 if (in_from_appletviewer)
1981 {
1982 if (g_io_channel_shutdown (in_from_appletviewer,
1983 TRUE, &channel_error)
1984 != G_IO_STATUS_NORMAL)
1985 {
1986 if (channel_error)
1987 {
1988 PLUGIN_ERROR ("Failed to shut down appletviewer"
1989 " input channel %s \n", channel_error->message);
1990 g_error_free (channel_error);
1991 channel_error = NULL;
1992 }
1993 else
1994 PLUGIN_ERROR ("Failed to shut down appletviewer\n");
1995 }
1996 }
1997 }
1998
1999 jvm_up = FALSE;
2000 sleep(2); /* Needed to prevent crashes during debug (when JDWP port is not freed by the kernel right away) */
2001
2002 PLUGIN_DEBUG ("plugin_stop_appletviewer return\n");
2003}
2004
2005static void appletviewer_monitor(GPid pid, gint status, gpointer data)
2006{
2007 PLUGIN_DEBUG ("appletviewer_monitor\n");
2008 jvm_up = FALSE;
2009 pid = -1;
2010 PLUGIN_DEBUG ("appletviewer_monitor return\n");
2011}
2012
2013void
2014plugin_data_destroy (NPP instance)
2015{
2016 PLUGIN_DEBUG ("plugin_data_destroy\n");
2017
2018 ITNPPluginData* tofree = (ITNPPluginData*) instance->pdata;
2019
2020 // Remove instance from map
2021 gpointer id_ptr = g_hash_table_lookup(instance_to_id_map, instance);
2022
2023 if (id_ptr)
2024 {
2025 gint id = GPOINTER_TO_INT(id_ptr);
2026 g_hash_table_remove(instance_to_id_map, instance);
2027 g_hash_table_remove(id_to_instance_map, id_ptr);
2028 }
2029
2030 /* Explicitly call destructor */
2031 tofree->~ITNPPluginData();
2032
2033 (*browser_functions.memfree) (tofree);
2034
2035 PLUGIN_DEBUG ("plugin_data_destroy return\n");
2036}
2037
2038static bool
2039initialize_browser_functions(const NPNetscapeFuncs* browserTable)
2040{
2041#define NPNETSCAPEFUNCS_LAST_FIELD_USED (browserTable->setvalueforurl)
2042
2043 //Determine the size in bytes, as a difference of the address past the last used field
2044 //And the browser table address
2045 size_t usedSize = (char*)(1 + &NPNETSCAPEFUNCS_LAST_FIELD_USED) - (char*)browserTable;
2046
2047 // compare the reported size versus the size we required
2048 if (browserTable->size < usedSize)
2049 {
2050 return false;
2051 }
2052
2053 //Ensure any unused fields are NULL
2054 memset(&browser_functions, 0, sizeof(NPNetscapeFuncs));
2055
2056 //browserTable->size can be larger than sizeof(NPNetscapeFuncs) (PR1106)
2057 size_t copySize = browserTable->size < sizeof(NPNetscapeFuncs) ?
2058 browserTable->size : sizeof(NPNetscapeFuncs);
2059
2060 //Copy fields according to given size
2061 memcpy(&browser_functions, browserTable, copySize);
2062
2063 return true;
2064}
2065
2066/* Set the plugin table to the correct contents, taking care not to write past
2067 * the provided object space */
2068static bool
2069initialize_plugin_table(NPPluginFuncs* pluginTable)
2070{
2071#define NPPLUGINFUNCS_LAST_FIELD_USED (pluginTable->getvalue)
2072
2073 //Determine the size in bytes, as a difference of the address past the last used field
2074 //And the browser table address
2075 size_t usedSize = (char*)(1 + &NPPLUGINFUNCS_LAST_FIELD_USED) - (char*)pluginTable;
2076
2077 // compare the reported size versus the size we required
2078 if (pluginTable->size < usedSize)
2079 return false;
2080
2081 pluginTable->version = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
2082 pluginTable->size = sizeof (NPPluginFuncs);
2083 pluginTable->newp = NPP_NewProcPtr (ITNP_New);
2084 pluginTable->destroy = NPP_DestroyProcPtr (ITNP_Destroy);
2085 pluginTable->setwindow = NPP_SetWindowProcPtr (ITNP_SetWindow);
2086 pluginTable->newstream = NPP_NewStreamProcPtr (ITNP_NewStream);
2087 pluginTable->destroystream = NPP_DestroyStreamProcPtr (ITNP_DestroyStream);
2088 pluginTable->asfile = NPP_StreamAsFileProcPtr (ITNP_StreamAsFile);
2089 pluginTable->writeready = NPP_WriteReadyProcPtr (ITNP_WriteReady);
2090 pluginTable->write = NPP_WriteProcPtr (ITNP_Write);
2091 pluginTable->print = NPP_PrintProcPtr (ITNP_Print);
2092 pluginTable->urlnotify = NPP_URLNotifyProcPtr (ITNP_URLNotify);
2093 pluginTable->getvalue = NPP_GetValueProcPtr (ITNP_GetValue);
2094
2095 return true;
2096}
2097
2098// Make sure the plugin data directory exists, creating it if necessary.
2099NPError
2100initialize_data_directory()
2101{
2102
2103 data_directory = IcedTeaPluginUtilities::getRuntimePath() + "/icedteaplugin-";
2104 if (getenv("USER") != NULL) {
2105 data_directory = data_directory + getenv("USER") + "-";
2106 }
2107 data_directory += "XXXXXX";
2108 // Now create a icedteaplugin subdir
2109 char fileNameX[data_directory.length()+1];
2110 std::strcpy (fileNameX, data_directory.c_str());
2111 char * fileName = mkdtemp(fileNameX);
2112 if (fileName == NULL) {
2113 PLUGIN_ERROR ("Failed to create data directory %s, %s\n",
2114 data_directory.c_str(),
2115 strerror (errno));
2116 return NPERR_GENERIC_ERROR;
2117 }
2118 data_directory = std::string(fileName);
2119
2120 //open uniques icedteaplugin subdir for one single run
2121 data_directory_descriptor = opendir(data_directory.c_str());
2122 if (data_directory_descriptor == NULL) {
2123 PLUGIN_ERROR ("Failed to open data directory %s %s\n",
2124 data_directory.c_str(), strerror (errno));
2125 return NPERR_GENERIC_ERROR;
2126 }
2127
2128 return NPERR_NO_ERROR;
2129}
2130
2131// FACTORY FUNCTIONS
2132
2133// Provides the browser with pointers to the plugin functions that we
2134// implement and initializes a local table with browser functions that
2135// we may wish to call. Called once, after browser startup and before
2136// the first plugin instance is created.
2137// The field 'initialized' is set to true once this function has
2138// finished. If 'initialized' is already true at the beginning of
2139// this function, then it is evident that NP_Initialize has already
2140// been called. There is no need to call this function more than once and
2141// this workaround avoids any duplicate calls.
2142__attribute__ ((visibility ("default")))
2143NPError
2144#if defined(_WIN32) || defined (__OS2__)
2145OSCALL NP_Initialize (NPNetscapeFuncs* browserTable)
2146#else
2147OSCALL NP_Initialize (NPNetscapeFuncs* browserTable, NPPluginFuncs* pluginTable)
2148#endif
2149{
2150 PLUGIN_DEBUG ("NP_Initialize\n");
2151
2152#if defined(_WIN32) || defined (__OS2__)
2153 if (browserTable == NULL)
2154 {
2155 PLUGIN_ERROR ("Browser function table is NULL.");
2156
2157 return NPERR_INVALID_FUNCTABLE_ERROR;
2158 }
2159#else
2160 if ((browserTable == NULL) || (pluginTable == NULL))
2161 {
2162 PLUGIN_ERROR ("Browser or plugin function table is NULL.\n");
2163
2164 return NPERR_INVALID_FUNCTABLE_ERROR;
2165 }
2166#endif
2167
2168 // Ensure that the major version of the plugin API that the browser
2169 // expects is not more recent than the major version of the API that
2170 // we've implemented.
2171 if ((browserTable->version >> 8) > NP_VERSION_MAJOR)
2172 {
2173 PLUGIN_ERROR ("Incompatible version.\n");
2174
2175 return NPERR_INCOMPATIBLE_VERSION_ERROR;
2176 }
2177
2178 // Copy into a global table (browser_functions) the browser functions that we may use.
2179 // If the browser functions needed change, update NPNETSCAPEFUNCS_LAST_FIELD_USED
2180 // within this function
2181 bool browser_functions_supported = initialize_browser_functions(browserTable);
2182
2183 // Check if everything we rely on is supported
2184 if ( !browser_functions_supported )
2185 {
2186 PLUGIN_ERROR ("Invalid browser function table.\n");
2187
2188 return NPERR_INVALID_FUNCTABLE_ERROR;
2189 }
2190
2191#if !defined(_WIN32) && !defined (__OS2__)
2192 // Return to the browser the plugin functions that we implement.
2193 // If the plugin functions needed change, update NPPLUGINFUNCS_LAST_FIELD_USED
2194 // within this function
2195 bool plugin_functions_supported = initialize_plugin_table(pluginTable);
2196
2197 // Check if everything we rely on is supported
2198 if ( !plugin_functions_supported )
2199 {
2200 PLUGIN_ERROR ("Invalid plugin function table.\n");
2201
2202 return NPERR_INVALID_FUNCTABLE_ERROR;
2203 }
2204#endif
2205
2206 // Re-setting the above tables multiple times is OK (as the
2207 // browser may change its function locations). However
2208 // anything beyond this point should only run once.
2209 if (initialized)
2210 return NPERR_NO_ERROR;
2211
2212#ifdef __OS2__
2213 // perform OS-specific initialization
2214 if (!init_os())
2215 {
2216 PLUGIN_ERROR ("Failed to perform OS-specific initialization.");
2217 return NPERR_GENERIC_ERROR;
2218 }
2219#endif
2220
2221 NPError np_error = NPERR_NO_ERROR;
2222#ifndef __OS2__
2223 // create directory for pipes
2224 np_error = initialize_data_directory();
2225 if (np_error != NPERR_NO_ERROR)
2226 {
2227 PLUGIN_ERROR("Unable to create data directory %s\n", data_directory.c_str());
2228 return np_error;
2229 }
2230#endif
2231
2232 // Set appletviewer_executable.
2233 PLUGIN_DEBUG("Executing java at %s\n", get_plugin_executable().c_str());
2234 np_error = plugin_test_appletviewer ();
2235 if (np_error != NPERR_NO_ERROR)
2236 {
2237 PLUGIN_ERROR("Unable to find java executable %s\n", get_plugin_executable().c_str());
2238 return np_error;
2239 }
2240
2241 // Initialize threads (needed for mutexes).
2242 if (!g_thread_supported ())
2243 g_thread_init (NULL);
2244
2245#ifdef __OS2__
2246 if (!g_main_context_os2_start_pm_integration (NULL))
2247 {
2248 PLUGIN_DEBUG ("Failed to integrate with PM");
2249 return NPERR_GENERIC_ERROR;
2250 }
2251#endif
2252
2253 plugin_instance_mutex = g_mutex_new ();
2254
2255 PLUGIN_DEBUG ("NP_Initialize: using %s\n", get_plugin_executable().c_str());
2256
2257 plugin_req_proc = new PluginRequestProcessor();
2258 java_req_proc = new JavaMessageSender();
2259
2260 java_to_plugin_bus = new MessageBus();
2261 plugin_to_java_bus = new MessageBus();
2262
2263 java_to_plugin_bus->subscribe(plugin_req_proc);
2264 plugin_to_java_bus->subscribe(java_req_proc);
2265
2266#ifdef __OS2__
2267 queue_processor_data1.processor = plugin_req_proc;
2268 queue_processor_data2.processor = plugin_req_proc;
2269 queue_processor_data3.processor = plugin_req_proc;
2270 pthread_create (&plugin_request_processor_thread1, NULL, &queue_processor, (void*) &queue_processor_data1);
2271 pthread_create (&plugin_request_processor_thread2, NULL, &queue_processor, (void*) &queue_processor_data2);
2272 pthread_create (&plugin_request_processor_thread3, NULL, &queue_processor, (void*) &queue_processor_data3);
2273#else
2274 pthread_create (&plugin_request_processor_thread1, NULL, &queue_processor, (void*) plugin_req_proc);
2275 pthread_create (&plugin_request_processor_thread2, NULL, &queue_processor, (void*) plugin_req_proc);
2276 pthread_create (&plugin_request_processor_thread3, NULL, &queue_processor, (void*) plugin_req_proc);
2277#endif
2278
2279 itnp_plugin_thread_id = pthread_self();
2280
2281 pthread_mutexattr_t attribute;
2282 pthread_mutexattr_init(&attribute);
2283 pthread_mutexattr_settype(&attribute, PTHREAD_MUTEX_RECURSIVE);
2284 pthread_mutex_init(&pluginAsyncCallMutex, &attribute);
2285 pthread_mutexattr_destroy(&attribute);
2286
2287 initialized = true;
2288
2289 PLUGIN_DEBUG ("NP_Initialize return\n");
2290
2291 return NPERR_NO_ERROR;
2292}
2293
2294#if defined(_WIN32) || defined (__OS2__)
2295NPError
2296OSCALL NP_GetEntryPoints (NPPluginFuncs* pluginTable)
2297{
2298 PLUGIN_DEBUG ("NP_GetEntryPoints\n");
2299
2300 if (pluginTable == NULL)
2301 {
2302 PLUGIN_ERROR ("Plugin function table is NULL.");
2303
2304 return NPERR_INVALID_FUNCTABLE_ERROR;
2305 }
2306
2307 // Return to the browser the plugin functions that we implement.
2308 // If the plugin functions needed change, update NPPLUGINFUNCS_LAST_FIELD_USED
2309 // within this function
2310 bool plugin_functions_supported = initialize_plugin_table(pluginTable);
2311
2312 // Check if everything we rely on is supported
2313 if ( !plugin_functions_supported )
2314 {
2315 PLUGIN_ERROR ("Invalid plugin function table.");
2316
2317 return NPERR_INVALID_FUNCTABLE_ERROR;
2318 }
2319
2320 return NPERR_NO_ERROR;
2321}
2322#endif
2323
2324// Returns a string describing the MIME type that this plugin
2325// handles.
2326__attribute__ ((visibility ("default")))
2327#ifdef LEGACY_XULRUNNERAPI
2328 char*
2329#else
2330 const char*
2331#endif
2332OSCALL NP_GetMIMEDescription ()
2333{
2334 //this function is called severaltimes between lunches
2335 PLUGIN_DEBUG ("NP_GetMIMEDescription\n");
2336
2337 PLUGIN_DEBUG ("NP_GetMIMEDescription return\n");
2338
2339 return PLUGIN_MIME_DESC;
2340}
2341
2342// Returns a value relevant to the plugin as a whole. The browser
2343// calls this function to obtain information about the plugin.
2344__attribute__ ((visibility ("default")))
2345NPError
2346OSCALL NP_GetValue (void* future, NPPVariable variable, void* value)
2347{
2348 PLUGIN_DEBUG ("NP_GetValue\n");
2349
2350 NPError result = NPERR_NO_ERROR;
2351 gchar** char_value = (gchar**) value;
2352
2353 switch (variable)
2354 {
2355 case NPPVpluginNameString:
2356 PLUGIN_DEBUG ("NP_GetValue: returning plugin name.\n");
2357 *char_value = g_strdup (PLUGIN_FULL_NAME);
2358 break;
2359
2360 case NPPVpluginDescriptionString:
2361 PLUGIN_DEBUG ("NP_GetValue: returning plugin description.\n");
2362 *char_value = g_strdup (PLUGIN_DESC);
2363 break;
2364
2365 default:
2366 PLUGIN_ERROR ("Unknown plugin value requested.\n");
2367 result = NPERR_GENERIC_ERROR;
2368 break;
2369 }
2370
2371 PLUGIN_DEBUG ("NP_GetValue return\n");
2372
2373 return result;
2374}
2375
2376// Shuts down the plugin. Called after the last plugin instance is
2377// destroyed.
2378__attribute__ ((visibility ("default")))
2379NPError
2380OSCALL NP_Shutdown (void)
2381{
2382 PLUGIN_DEBUG ("NP_Shutdown\n");
2383
2384#ifdef __OS2__
2385 g_main_context_os2_stop_pm_integration (NULL);
2386#endif
2387
2388 // Free mutex.
2389 if (plugin_instance_mutex)
2390 {
2391 g_mutex_free (plugin_instance_mutex);
2392 plugin_instance_mutex = NULL;
2393 }
2394
2395 // stop the appletviewer
2396 plugin_stop_appletviewer();
2397
2398 // remove monitor
2399 if (appletviewer_watch_id != -1)
2400 g_source_remove(appletviewer_watch_id);
2401
2402 // Removing a source is harmless if it fails since it just means the
2403 // source has already been removed.
2404 g_source_remove (in_watch_source);
2405 in_watch_source = 0;
2406
2407 // cleanup_in_from_appletviewer:
2408 if (in_from_appletviewer)
2409 g_io_channel_unref (in_from_appletviewer);
2410 in_from_appletviewer = NULL;
2411
2412 // cleanup_out_watch_source:
2413 g_source_remove (out_watch_source);
2414 out_watch_source = 0;
2415
2416 // cleanup_out_to_appletviewer:
2417 if (out_to_appletviewer)
2418 g_io_channel_unref (out_to_appletviewer);
2419 out_to_appletviewer = NULL;
2420
2421 // cleanup_out_pipe:
2422#ifdef __OS2__
2423 CLOSE_FD (out_pipe [0]);
2424 CLOSE_FD (out_pipe [1]);
2425#else
2426 // Delete output pipe.
2427 PLUGIN_DEBUG ("NP_Shutdown: deleting output fifo: %s\n", out_pipe_name);
2428 unlink (out_pipe_name);
2429 PLUGIN_DEBUG ("NP_Shutdown: deleted output fifo: %s\n", out_pipe_name);
2430
2431 // cleanup_out_pipe_name:
2432 g_free (out_pipe_name);
2433 out_pipe_name = NULL;
2434#endif
2435
2436 // cleanup_in_pipe:
2437#ifdef __OS2__
2438 CLOSE_FD (in_pipe [0]);
2439 CLOSE_FD (in_pipe [1]);
2440#else
2441 // Delete input pipe.
2442 PLUGIN_DEBUG ("NP_Shutdown: deleting input fifo: %s\n", in_pipe_name);
2443 unlink (in_pipe_name);
2444 PLUGIN_DEBUG ("NP_Shutdown: deleted input fifo: %s\n", in_pipe_name);
2445
2446 // cleanup_in_pipe_name:
2447 g_free (in_pipe_name);
2448 in_pipe_name = NULL;
2449#endif
2450
2451 if (plugin_debug_to_console){
2452 //jvm_up is now false
2453 if (g_io_channel_shutdown (debug_to_appletviewer,
2454 TRUE, &channel_error)
2455 != G_IO_STATUS_NORMAL)
2456 {
2457 if (channel_error)
2458 {
2459 PLUGIN_ERROR ("Failed to shut down appletviewer"
2460 " debug channel\n", channel_error->message);
2461 g_error_free (channel_error);
2462 channel_error = NULL;
2463 }
2464 else
2465 PLUGIN_ERROR ("Failed to shut down debug to appletviewer\n");
2466 }
2467 // cleanup_out_to_appletviewer:
2468 if (debug_to_appletviewer)
2469 g_io_channel_unref (debug_to_appletviewer);
2470 out_to_appletviewer = NULL;
2471 // cleanup_debug_pipe:
2472#ifdef __OS2__
2473 CLOSE_FD (debug_pipe [0]);
2474 CLOSE_FD (debug_pipe [1]);
2475#else
2476 // Delete debug pipe.
2477 PLUGIN_DEBUG ("NP_Shutdown: deleting debug fifo: %s\n", debug_pipe_name);
2478 unlink (debug_pipe_name);
2479 PLUGIN_DEBUG ("NP_Shutdown: deleted debug fifo: %s\n", debug_pipe_name);
2480 // cleanup_out_pipe_name:
2481 g_free (debug_pipe_name);
2482 debug_pipe_name = NULL;
2483#endif
2484 }
2485
2486 // Destroy the call queue mutex
2487 pthread_mutex_destroy(&pluginAsyncCallMutex);
2488
2489#ifdef __OS2__
2490 // perform OS-specific uninitialization
2491 done_os();
2492#endif
2493
2494 initialized = false;
2495
2496#ifdef __OS2__
2497 // pthread_cancel() isn't implemented on OS/2, so use an old good flag
2498 queue_processor_data1.stopRequested = true;
2499 queue_processor_data2.stopRequested = true;
2500 queue_processor_data3.stopRequested = true;
2501 plugin_req_proc->cancelWait();
2502#else
2503 pthread_cancel(plugin_request_processor_thread1);
2504 pthread_cancel(plugin_request_processor_thread2);
2505 pthread_cancel(plugin_request_processor_thread3);
2506#endif
2507
2508 pthread_join(plugin_request_processor_thread1, NULL);
2509 pthread_join(plugin_request_processor_thread2, NULL);
2510 pthread_join(plugin_request_processor_thread3, NULL);
2511
2512 java_to_plugin_bus->unSubscribe(plugin_req_proc);
2513 plugin_to_java_bus->unSubscribe(java_req_proc);
2514 //internal_bus->unSubscribe(java_req_proc);
2515 //internal_bus->unSubscribe(plugin_req_proc);
2516
2517 delete plugin_req_proc;
2518 delete java_req_proc;
2519 delete java_to_plugin_bus;
2520 delete plugin_to_java_bus;
2521 //delete internal_bus;
2522
2523 cleanUpDir();
2524
2525 PLUGIN_DEBUG ("NP_Shutdown return\n");
2526
2527 if (plugin_debug_to_file){
2528 fflush (plugin_file_log);
2529 //fclose (plugin_file_log);
2530 //keep writing untill possible!
2531 }
2532
2533 return NPERR_NO_ERROR;
2534}
2535
2536NPObject*
2537get_scriptable_object(NPP instance)
2538{
2539 NPObject* obj;
2540 ITNPPluginData* data = (ITNPPluginData*) instance->pdata;
2541
2542 if (data->is_applet_instance) // dummy instance/package?
2543 {
2544 JavaRequestProcessor java_request = JavaRequestProcessor();
2545 JavaResultData* java_result;
2546 std::string instance_id = std::string();
2547 std::string applet_class_id = std::string();
2548
2549 int id = get_id_from_instance(instance);
2550 gchar* id_str = g_strdup_printf ("%d", id);
2551
2552 // Some browsers.. (e.g. chromium) don't call NPP_SetWindow
2553 // for 0x0 plugins and therefore require initialization with
2554 // a 0 handle
2555 if (!data->window_handle)
2556 {
2557 plugin_send_initialization_message(data->instance_id, 0, 0, 0, data->parameters_string);
2558 }
2559
2560 java_result = java_request.getAppletObjectInstance(id_str);
2561
2562 g_free(id_str);
2563
2564 if (java_result->error_occurred)
2565 {
2566 PLUGIN_ERROR("Error: Unable to fetch applet instance id from Java side.\n");
2567 return NULL;
2568 }
2569
2570 instance_id.append(*(java_result->return_string));
2571
2572 java_result = java_request.getClassID(instance_id);
2573
2574 if (java_result->error_occurred)
2575 {
2576 PLUGIN_ERROR("Error: Unable to fetch applet instance id from Java side.\n");
2577 return NULL;
2578 }
2579
2580 applet_class_id.append(*(java_result->return_string));
2581
2582 obj = IcedTeaScriptableJavaObject::get_scriptable_java_object(instance, applet_class_id, instance_id, false);
2583
2584 } else
2585 {
2586 obj = IcedTeaScriptableJavaPackageObject::get_scriptable_java_package_object(instance, "");
2587 }
2588
2589 return obj;
2590}
2591
2592NPObject*
2593allocate_scriptable_object(NPP npp, NPClass *aClass)
2594{
2595 PLUGIN_DEBUG("Allocating new scriptable object\n");
2596 return new IcedTeaScriptablePluginObject(npp);
2597}
2598
2599#ifdef __OS2__
2600
2601// Make sure static initializers in the plugin DLL are called
2602
2603static APIENTRY void cleanup(ULONG ulReason)
2604{
2605 __ctordtorTerm();
2606 _CRT_term();
2607 DosExitList(EXLST_EXIT, cleanup);
2608}
2609
2610unsigned long _System _DLL_InitTerm(unsigned long hModule,
2611 unsigned long ulFlag)
2612{
2613 APIRET arc;
2614
2615 if (ulFlag == 0)
2616 {
2617 arc = DosExitList (EXLST_ADD, cleanup);
2618 if (arc != NO_ERROR)
2619 return 0;
2620
2621 if (_CRT_init() != 0) // failure?
2622 return 0;
2623 __ctordtorInit();
2624 }
2625
2626 return 1;
2627}
2628
2629#endif /* __OS2__ */
Note: See TracBrowser for help on using the repository browser.