update copyright date
[gnash.git] / plugin / npapi / plugin.cpp
blob98408fb0b97250053a93b28eac5e5ab6a7fbe77f
1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
3 // 2011 Free Software Foundation, Inc
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 #include "gnashconfig.h"
22 #endif
24 #include <cstdlib> // getenv
25 #include <stdlib.h> // putenv
26 #include <sys/types.h>
27 #if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
28 # include <winsock2.h>
29 # include <windows.h>
30 #else
31 # include <netinet/in.h>
32 # include <arpa/inet.h>
33 # include <netdb.h>
34 # include <sys/socket.h>
35 #endif
37 #include <boost/format.hpp>
38 #include <boost/algorithm/string/replace.hpp>
40 #define MIME_TYPES_HANDLED "application/x-shockwave-flash"
41 // The name must be this value to get flash movies that check the
42 // plugin version to load.
43 #define PLUGIN_NAME "Shockwave Flash"
44 #define MIME_TYPES_DESCRIPTION MIME_TYPES_HANDLED":swf:"PLUGIN_NAME
46 // Some javascript plugin detectors use the description
47 // to decide the flash version to display. They expect the
48 // form (major version).(minor version) r(revision).
49 // e.g. "8.0 r99."
50 #define FLASH_VERSION DEFAULT_FLASH_MAJOR_VERSION"."\
51 DEFAULT_FLASH_MINOR_VERSION" r"DEFAULT_FLASH_REV_NUMBER"."
53 #define PLUGIN_DESCRIPTION \
54 "Shockwave Flash "FLASH_VERSION"<br>Gnash "VERSION", the GNU SWF Player. \
55 Copyright (C) 2006, 2007, 2008, 2009, 2010 \
56 <a href=\"http://www.fsf.org\">Free \
57 Software Foundation</a>, Inc. <br> \
58 Gnash comes with NO WARRANTY, to the extent permitted by law. \
59 You may redistribute copies of Gnash under the terms of the \
60 <a href=\"http://www.gnu.org/licenses/gpl.html\">GNU General Public \
61 License</a>. For more information about Gnash, see <a \
62 href=\"http://www.gnu.org/software/gnash/\"> \
63 http://www.gnu.org/software/gnash</a>. \
64 <br>\
65 Compatible Shockwave Flash "FLASH_VERSION
67 #include "plugin.h"
68 #include "GnashSystemIOHeaders.h"
69 #include "StringPredicates.h"
70 #include "external.h"
71 #include "callbacks.h"
72 #include "npapi.h"
73 #include "npruntime.h"
74 #include "npfunctions.h"
75 #include "GnashNPVariant.h"
77 #include <boost/tokenizer.hpp>
78 #include <boost/algorithm/string/join.hpp>
79 #include <boost/algorithm/string/split.hpp>
80 #include <boost/algorithm/string/classification.hpp>
81 #include <boost/format.hpp>
82 #include <sys/param.h>
83 #include <csignal>
84 #include <cstdio>
85 #include <cstddef>
86 #include <cstring>
87 #include <sys/types.h>
88 #include <sys/stat.h>
89 #include <sys/wait.h>
90 #include <fcntl.h>
91 #include <cerrno>
92 #include <climits>
93 #include <string>
94 #include <vector>
95 #include <iostream>
96 #include <fstream>
97 #include <sstream>
99 #ifndef PATH_MAX
100 #define PATH_MAX 1024
101 #endif
103 // Macro to prevent repeated logging calls for the same
104 // event
105 #define LOG_ONCE(x) { \
106 static bool warned = false; \
107 if (!warned) { warned = true; x; } \
110 // For scriptable plugin support
111 #include "pluginScriptObject.h"
113 extern NPNetscapeFuncs NPNFuncs;
114 extern NPPluginFuncs NPPFuncs;
116 namespace gnash {
117 NPBool plugInitialized = FALSE;
120 /// \brief Return the MIME Type description for this plugin.
121 char*
122 NPP_GetMIMEDescription(void)
124 return const_cast<char *>(MIME_TYPES_DESCRIPTION);
127 static bool waitforgdb = false;
128 static bool createSaLauncher = false;
130 static const char* getPluginDescription();
131 // void GnashLogDebug(const std::string& msg);
132 // void GnashLogError(const std::string& msg);
134 static const char*
135 getPluginDescription()
137 static const char* desc = NULL;
138 if (!desc) {
139 desc = std::getenv("GNASH_PLUGIN_DESCRIPTION");
140 if (desc == NULL) desc = PLUGIN_DESCRIPTION;
142 return desc;
146 // general initialization and shutdown
149 /// \brief Initialize the plugin
151 /// This C++ function gets called once when the plugin is loaded,
152 /// regardless of how many instantiations there is actually playing
153 /// movies. So this is where all the one time only initialization
154 /// stuff goes.
155 NPError
156 NS_PluginInitialize()
159 if ( gnash::plugInitialized ) {
160 gnash::log_debug("NS_PluginInitialize called, but ignored (we already initialized)");
161 return NPERR_NO_ERROR;
164 gnash::log_debug("NS_PluginInitialize call ---------------------------");
166 // Browser Functionality Checks
168 NPError err = NPERR_NO_ERROR;
169 NPBool supportsXEmbed = TRUE;
171 // First, check for XEmbed support. The NPAPI Gnash plugin
172 // only works with XEmbed, so tell the plugin API to fail if
173 // XEmbed is not found.
174 err = NPN_GetValue(NULL,NPNVSupportsXEmbedBool,
175 (void *)&supportsXEmbed);
177 if (err != NPERR_NO_ERROR || !supportsXEmbed) {
178 gnash::log_error("NPAPI ERROR: No xEmbed support in this browser!");
179 return NPERR_INCOMPATIBLE_VERSION_ERROR;
180 } else {
181 gnash::log_debug("xEmbed supported in this browser");
184 // GTK is not strictly required, but we do use the Glib main event loop,
185 // so lack of GTK means reduced functionality.
186 NPNToolkitType toolkit;
187 err = NPN_GetValue(NULL, NPNVToolkit, &toolkit);
189 if (err != NPERR_NO_ERROR || toolkit != NPNVGtk2) {
190 gnash::log_error("NPAPI ERROR: No GTK2 support in this browser! Have version %d", (int)toolkit);
191 } else {
192 gnash::log_debug("GTK2 supported in this browser");
196 Check for environment variables.
198 char* opts = std::getenv("GNASH_OPTIONS");
199 if (opts != NULL) {
200 gnash::log_debug("GNASH_OPTIONS: %s", opts);
202 // Should the plugin wait for gdb to be attached?
203 if ( strstr(opts, "waitforgdb") ) {
204 waitforgdb = true;
207 // Should the plugin write a script to invoke
208 // the standalone player for debugging ?
209 if ( strstr(opts, "writelauncher") ) {
210 createSaLauncher = true;
215 // Append SYSCONFDIR/gnashpluginrc and ~/.gnashpluginrc to GNASHRC
217 std::string newGnashRc("GNASHRC=");
219 #if !defined(__OS2__ ) && ! defined(__amigaos4__)
220 newGnashRc.append(SYSCONFDIR);
221 newGnashRc.append("/gnashpluginrc");
222 #endif
224 const char *home = NULL;
225 #if defined(__amigaos4__)
226 //on AmigaOS we have a GNASH: assign that point to program dir
227 home = "/gnash";
228 #elif defined(__HAIKU__)
229 BPath bp;
230 if (B_OK != find_directory(B_USER_SETTINGS_DIRECTORY, &bp))
232 std::cerr << "Failed to find user settings directory" << std::endl;
233 } else {
234 bp.Append("Gnash");
235 home = bp.Path();
237 #else
238 home = std::getenv("HOME");
239 #endif
240 if ( home ) {
241 newGnashRc.append(":");
242 newGnashRc.append(home);
243 #ifdef __HAIKU__
244 newGnashRc.append("/gnashpluginrc");
245 #else
246 newGnashRc.append("/.gnashpluginrc");
247 #endif
248 } else {
249 gnash::log_error("WARNING: NPAPI plugin could not find user home dir");
252 char *gnashrc = std::getenv("GNASHRC");
253 if ( gnashrc ) {
254 newGnashRc.append(":");
255 newGnashRc.append(gnashrc);
258 // putenv doesn't copy the string in standards-conforming implementations
259 gnashrc = new char[PATH_MAX];
260 std::strncpy(gnashrc, newGnashRc.c_str(), PATH_MAX);
261 gnashrc[PATH_MAX-1] = '\0';
263 if ( putenv(gnashrc) ) {
264 gnash::log_debug("WARNING: NPAPI plugin could not append to the GNASHRC env variable");
265 } else {
266 gnash::log_debug("NOTE: NPAPI plugin set GNASHRC to %d", newGnashRc);
269 /* Success */
271 gnash::plugInitialized = TRUE;
273 return NPERR_NO_ERROR;
276 /// \brief Shutdown the plugin
278 /// This C++ function gets called once when the plugin is being
279 /// shutdown, regardless of how many instantiations actually are
280 /// playing movies. So this is where all the one time only
281 /// shutdown stuff goes.
282 void
283 NS_PluginShutdown()
285 #if 0
286 if (!plugInitialized) {
287 gnash::log_debug("Plugin already shut down");
288 return;
291 plugInitialized = FALSE;
292 #endif
295 /// \brief Retrieve values from the plugin for the Browser
297 /// This C++ function is called by the browser to get certain
298 /// information is needs from the plugin. This information is the
299 /// plugin name, a description, etc...
300 NPError
301 NS_PluginGetValue(NPPVariable aVariable, void *aValue)
303 NPError err = NPERR_NO_ERROR;
305 switch (aVariable) {
306 case NPPVpluginNameString:
307 *static_cast<const char **> (aValue) = PLUGIN_NAME;
308 break;
310 // This becomes the description field you see below the opening
311 // text when you type about:plugins and in
312 // navigator.plugins["Shockwave Flash"].description, used in
313 // many flash version detection scripts.
314 case NPPVpluginDescriptionString:
315 *static_cast<const char **>(aValue) = getPluginDescription();
316 break;
318 case NPPVpluginWindowBool:
319 break;
321 case NPPVpluginTimerInterval:
322 break;
324 case NPPVpluginKeepLibraryInMemory:
325 break;
327 case NPPVpluginNeedsXEmbed:
328 #ifdef HAVE_GTK2
329 *static_cast<NPBool *>(aValue) = TRUE;
330 #else
331 *static_cast<NPBool *>(aValue) = FALSE;
332 #endif
333 break;
335 case NPPVpluginScriptableNPObject:
336 break;
338 case NPPVpluginUrlRequestsDisplayedBool:
339 break;
340 case NPPVpluginWantsAllNetworkStreams:
341 break;
343 default:
344 err = NPERR_INVALID_PARAM;
345 break;
347 return err;
350 /// \brief construct our plugin instance object
352 /// This instantiates a new object via a C++ function used by the
353 /// browser.
354 nsPluginInstanceBase *
355 NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
357 // gnash::log_debug(__PRETTY_FUNCTION__);
359 if(!aCreateDataStruct) {
360 return NULL;
363 return new gnash::nsPluginInstance(aCreateDataStruct);
366 /// \brief destroy our plugin instance object
368 /// This destroys our instantiated object via a C++ function used by the
369 /// browser.
370 void
371 NS_DestroyPluginInstance(nsPluginInstanceBase* aPlugin)
373 delete static_cast<gnash::nsPluginInstance *> (aPlugin);
376 namespace gnash {
379 // nsPluginInstance class implementation
382 /// \brief Constructor
383 nsPluginInstance::nsPluginInstance(nsPluginCreateData* data)
385 nsPluginInstanceBase(),
386 _instance(data->instance),
387 _window(0),
388 _width(0),
389 _height(0),
390 _streamfd(-1),
391 _ichan(0),
392 _ichanWatchId(0),
393 _controlfd(-1),
394 _childpid(0),
395 _filefd(-1),
396 _name(),
397 _scriptObject(0)
399 // gnash::log_debug("%s: %x", __PRETTY_FUNCTION__, (void *)this);
401 for (size_t i=0, n=data->argc; i<n; ++i) {
402 std::string name, val;
403 gnash::StringNoCaseEqual noCaseCompare;
405 if (data->argn[i]) {
406 name = data->argn[i];
409 if (data->argv[i]) {
410 val = data->argv[i];
413 if (noCaseCompare(name, "name")) {
414 _name = val;
417 _params[name] = val;
420 #if 1
421 if (NPNFuncs.version >= 14) { // since NPAPI start to support
422 _scriptObject = (GnashPluginScriptObject *)NPNFuncs.createobject(
423 _instance, GnashPluginScriptObject::marshalGetNPClass());
425 #endif
427 return;
430 gboolean
431 cleanup_childpid(gpointer data)
433 int* pid = static_cast<int*>(data);
435 int status;
436 int rv = waitpid(*pid, &status, WNOHANG);
438 if (rv <= 0) {
439 // The child process has not exited; it may be deadlocked. Kill it.
440 // gnash::log_error("BUG: Child process is stuck. Killing it.");
442 kill(*pid, SIGKILL);
443 waitpid(*pid, &status, 0);
446 gnash::log_debug("Child process exited with status %s", status);
448 delete pid;
450 return FALSE;
453 /// \brief Destructor
454 nsPluginInstance::~nsPluginInstance()
456 // gnash::log_debug("plugin instance destruction");
458 if ( _ichanWatchId ) {
459 g_source_remove(_ichanWatchId);
460 _ichanWatchId = 0;
463 if (_childpid > 0) {
464 // When the child has terminated (signaled by _controlfd), it remains
465 // as a defunct process and we remove it from the kernel table now.
467 // If all goes well, Gnash will already have terminated.
468 int status;
469 int rv = waitpid(_childpid, &status, WNOHANG);
471 if (rv <= 0) {
472 int* pid = new int(_childpid);
473 usleep(1000);
474 cleanup_childpid(pid);
475 } else {
476 gnash::log_debug("Child process exited with status %d", status);
479 _childpid = 0;
482 /// \brief Initialize an instance of the plugin object
483 ///
484 /// This methods initializes the plugin object, and is called for
485 /// every movie that gets played. This is where the movie playing
486 /// specific initialization goes.
487 NPBool
488 nsPluginInstance::init(NPWindow* aWindow)
490 if(!aWindow) {
491 gnash::log_error("%s: ERROR: Window handle was bogus!", __PRETTY_FUNCTION__);
492 return FALSE;
493 } else {
494 #if GNASH_PLUGIN_DEBUG > 1
495 std::cout << "X origin: = " << aWindow->x
496 << ", Y Origin = " << aWindow->y
497 << ", Width = " << aWindow->width
498 << ", Height = " << aWindow->height
499 << ", WindowID = " << aWindow->window
500 << ", this = " << static_cast<void*>(this) << std::endl;
501 #endif
504 return TRUE;
507 /// \brief Shutdown an instantiated object
509 /// This shuts down an object, and is called for every movie that gets
510 /// played. This is where the movie playing specific shutdown code
511 /// goes.
512 void
513 nsPluginInstance::shut()
515 gnash::log_debug("Gnash plugin shutting down");
517 if (_streamfd != -1) {
518 if (close(_streamfd) == -1) {
519 perror("closing _streamfd");
520 } else {
521 _streamfd = -1;
525 if (_controlfd != -1) {
526 _scriptObject->closePipe(_controlfd);
527 if (close(_controlfd) != 0) {
528 gnash::log_error("Gnash plugin failed to close the control socket!");
534 /// \brief Set the window to be used to render in
536 /// This sets up the window the plugin is supposed to render
537 /// into. This calls passes in various information used by the plugin
538 /// to setup the window. This may get called multiple times by each
539 /// instantiated object, so it can't do much but window specific
540 /// setup here.
541 NPError
542 nsPluginInstance::SetWindow(NPWindow* aWindow)
544 if(!aWindow) {
545 gnash::log_error(std::string(__FUNCTION__) + ": ERROR: Window handle was bogus!");
546 return NPERR_INVALID_PARAM;
547 #if 0
548 } else {
549 gnash::log_debug("%s: X origin = %d, Y Origin = %d, Width = %d,"
550 " Height = %d, WindowID = %p, this = %p",
551 __FUNCTION__,
552 aWindow->x, aWindow->y, aWindow->width, aWindow->height,
553 aWindow->window, this);
554 #endif
557 if (_window) {
558 return NPERR_GENERIC_ERROR;
561 _width = aWindow->width;
562 _height = aWindow->height;
564 _window = reinterpret_cast<Window> (aWindow->window);
566 // When testing the interface to the plugin, don't start the player
567 // as a debug client "nc -l 1111" is used instead.
568 if (!_childpid && !_swf_url.empty()) {
569 startProc();
572 return NPERR_NO_ERROR;
575 NPError
576 nsPluginInstance::GetValue(NPPVariable aVariable, void *aValue)
579 if (aVariable == NPPVpluginScriptableNPObject) {
580 if (_scriptObject) {
581 void **v = (void **)aValue;
582 NPNFuncs.retainobject(_scriptObject);
583 *v = _scriptObject;
584 } else {
585 gnash::log_debug("_scriptObject is not assigned");
589 // log_debug("SCRIPT OBJECT getValue: %x, ns: %x", (void *)_scriptObject, (void *)this);
591 return NS_PluginGetValue(aVariable, aValue);
594 #if 0
595 // FIXME: debugging stuff, will be gone soon after I figure how this works
596 void myfunc(void */* param */)
598 gnash::log_debug("Here I am!!!\n");
600 #endif
602 /// \brief Open a new data stream
604 /// Opens a new incoming data stream, which is the flash movie we want
605 /// to play.
606 /// A URL can be pretty ugly, like in this example:
607 /// http://www.shockwave.com/swf/navbar/navbar_sw.swf?atomfilms=http%3a//www.atomfilms.com/af/home/&shockwave=http%3a//www.shockwave.com&gameblast=http%3a//gameblast.shockwave.com/gb/gbHome.jsp&known=0
608 /// ../flash/gui.swf?ip_addr=foobar.com&ip_port=3660&show_cursor=true&path_prefix=../flash/&trapallkeys=true"
611 NPError
612 nsPluginInstance::NewStream(NPMIMEType /*type*/, NPStream* stream,
613 NPBool /*seekable*/, uint16_t* /*stype*/)
615 // gnash::log_debug("%s: %x", __PRETTY_FUNCTION__, (void *)this);
617 if (_childpid) {
618 // Apparently the child process has already been started for this
619 // plugin instance. It is puzzling that this method gets called
620 // again. Starting a new process for the same movie will cause
621 // problems later, so we'll stop here.
622 return NPERR_GENERIC_ERROR;
624 _swf_url = stream->url;
626 #if 0
627 // FIXME: debugging crap for now call javascript
628 NPN_PluginThreadAsyncCall(_instance, myfunc, NULL);
629 // gnash::log_debug("FIXME: %s", getEmbedURL());
630 #endif
632 if (!_swf_url.empty() && _window) {
633 startProc();
636 return NPERR_NO_ERROR;
639 /// \brief Destroy the data stream we've been reading.
640 NPError
641 nsPluginInstance::DestroyStream(NPStream* /*stream*/, NPError /*reason*/)
644 if (_streamfd != -1) {
645 if (close(_streamfd) == -1) {
646 perror("closing _streamfd");
647 } else {
648 _streamfd = -1;
652 return NPERR_NO_ERROR;
655 /// \brief Return how many bytes we can read into the buffer
656 int32_t
657 nsPluginInstance::WriteReady(NPStream* /* stream */ )
659 //gnash::log_debug("Stream for %s is ready", stream->url);
660 if ( _streamfd != -1 ) {
661 return 1024;
662 } else {
663 return 0;
667 /// \brief Read the data stream from Mozilla/Firefox
669 int32_t
670 nsPluginInstance::Write(NPStream* /*stream*/, int32_t /*offset*/, int32_t len,
671 void* buffer)
673 int written = write(_streamfd, buffer, len);
674 return written;
677 bool
678 nsPluginInstance::handlePlayerRequestsWrapper(GIOChannel* iochan,
679 GIOCondition cond, nsPluginInstance* plugin)
681 return plugin->handlePlayerRequests(iochan, cond);
684 bool
685 nsPluginInstance::handlePlayerRequests(GIOChannel* iochan, GIOCondition cond)
687 // gnash::log_debug("%s: %d: %x", __PRETTY_FUNCTION__, __LINE__, (void *)this);
689 if ( cond & G_IO_HUP ) {
690 gnash::log_debug("Player control socket hang up");
691 // Returning false here will cause the "watch" to be removed. This watch
692 // is the only reference held to the GIOChannel, so it will be
693 // destroyed. We must make sure we don't attempt to destroy it again.
694 _ichanWatchId = 0;
695 return false;
698 assert(cond & G_IO_IN);
700 gnash::log_debug("Checking player requests on FD #%d",
701 g_io_channel_unix_get_fd(iochan));
703 GError* error = 0;
704 // g_io_channel_set_flags(iochan, G_IO_FLAG_NONBLOCK, &error);
706 size_t retries = 5;
707 gchar* request = 0;
708 gsize requestSize = 0;
709 do {
710 // When in non-blocking mode, we'll get several iterations of this
711 // loop while waiting for data. if data never arrives, we'd be stuck
712 // looping here forever, so this is our escape from that loop.
713 if (retries-- <= 0) {
714 gnash::log_error("Too many attempts to read from the player!");
715 return false;
717 error = 0;
718 request = 0;
719 requestSize = 0;
720 GIOStatus status = g_io_channel_read_line(iochan, &request,
721 &requestSize, NULL, &error);
722 switch (status) {
723 case G_IO_STATUS_ERROR:
724 gnash::log_error("error reading request line: %s",
725 error->message);
726 g_error_free(error);
727 return false;
728 case G_IO_STATUS_EOF:
729 gnash::log_error("EOF (error: %s", error->message);
730 g_error_free(error);
731 return false;
732 case G_IO_STATUS_AGAIN:
733 gnash::log_debug("read again: nonblocking mode set ");
734 continue;
735 case G_IO_STATUS_NORMAL:
736 // process request
737 // Get rid of the newline on the end if there is one. The string
738 // is also NULL terninated, so the requestSize includes it in
739 // the total.
740 if (request[requestSize-1] == '\n') {
741 request[requestSize-1] = 0;
742 requestSize--;
744 gnash::log_debug("Normal read: %s", request);
745 break;
746 default:
747 gnash::log_error("Abnormal status!");
748 return false;
750 } while (g_io_channel_get_buffer_condition(iochan) & G_IO_IN);
752 // process request..
753 processPlayerRequest(request, requestSize);
754 g_free(request);
756 return true;
759 bool
760 nsPluginInstance::processPlayerRequest(gchar* buf, gsize linelen)
762 // gnash::log_debug(__PRETTY_FUNCTION__);
764 // log_debug("SCRIPT OBJECT %d: %x", __LINE__, this->getScriptObject());
766 if ( linelen < 4 ) {
767 if (buf) {
768 gnash::log_error("Invalid player request (too short): %s", buf);
769 } else {
770 gnash::log_error("Invalid player request (too short): %d bytes", linelen);
772 return false;
775 plugin::ExternalInterface::invoke_t *invoke = plugin::ExternalInterface::parseInvoke(buf);
777 if (!invoke->name.empty()) {
778 gnash::log_debug("Requested method is: %s", invoke->name);
781 // The invoke message is also used for getURL. In this case there are 4
782 // possible arguments.
783 if (invoke) {
784 if (invoke->name == "getURL") {
785 // gnash::log_debug("Got a getURL() request: %", invoke->args[0].get());
787 // The first argument is the URL string.
788 std::string url = NPStringToString(NPVARIANT_TO_STRING(
789 invoke->args[0].get()));
790 // The second is the method, namely GET or POST.
791 std::string op = NPStringToString(NPVARIANT_TO_STRING(
792 invoke->args[1].get()));
793 // The third is the optional target, which is something like
794 // _blank or _self. NONE means no target.
795 std::string target;
796 // The fourth is the optional data. If there is data, the target
797 // field is always set so this argument is on the correct index.
798 // No target is "NONE".
799 std::string data;
800 if (invoke->args.size() >= 3) {
801 target = NPStringToString(NPVARIANT_TO_STRING(
802 invoke->args[2].get()));
803 if (target == "NONE") {
804 target.clear();
807 if (invoke->args.size() == 4) {
808 data = NPStringToString(NPVARIANT_TO_STRING(
809 invoke->args[3].get()));
811 if (op == "GET") {
812 gnash::log_debug("Asked to getURL '%s' in target %s", url,
813 target);
814 NPN_GetURL(_instance, url.c_str(), target.c_str());
815 } else if (op == "POST") {
816 gnash::log_debug("Asked to postURL '%s' this data %s", url,
817 data);
818 NPN_PostURL(_instance, url.c_str(), target.c_str(), data.size(),
819 data.c_str(), false);
820 return true;
823 return true;
824 } else if (invoke->name == "fsCommand") {
825 std::string command = NPStringToString(NPVARIANT_TO_STRING(
826 invoke->args[0].get()));
827 std::string arg = NPStringToString(NPVARIANT_TO_STRING(
828 invoke->args[1].get()));
829 std::string name = _name;
830 std::stringstream jsurl;
831 jsurl << "javascript:" << name << "_DoFSCommand('" << command
832 << "','" << arg <<"')";
834 // TODO: check if _self is a good target for this
835 static const char* tgt = "_self";
837 gnash::log_debug("Calling NPN_GetURL(%s, %s)",
838 jsurl.str(), tgt);
840 NPN_GetURL(_instance, jsurl.str().c_str(), tgt);
841 return true;
842 } else if (invoke->name == "addMethod") {
843 // Make this flash function accessible to Javascript. The
844 // actual callback lives in libcore/movie_root, but it
845 // needs to be on the list of supported remote methods so
846 // it can be called by Javascript.
847 std::string method = NPStringToString(NPVARIANT_TO_STRING(
848 invoke->args[0].get()));
849 NPIdentifier id = NPN_GetStringIdentifier(method.c_str());
850 // log_debug("SCRIPT OBJECT addMethod: %x, %s", (void *)_scriptObject, method);
851 this->getScriptObject()->AddMethod(id, remoteCallback);
852 return true;
854 NPVariant result;
855 VOID_TO_NPVARIANT(result);
856 bool invokeResult=false;
857 // This is the player invoking a method in Javascript
858 if (!invoke->name.empty()) {
859 //Convert the as_value argument to NPVariant
860 uint32_t count = invoke->args.size();
861 if(count!=0) //The first argument should exists and be the method name
863 count--;
864 NPVariant* args = new NPVariant[count];
865 //Skip the first argument
866 for(uint32_t i=0;i<count;i++)
867 invoke->args[i+1].copy(args[i]);
868 NPIdentifier id = NPN_GetStringIdentifier(invoke->name.c_str());
869 gnash::log_debug("Invoking JavaScript method %s", invoke->name);
870 NPObject* windowObject;
871 NPN_GetValue(_instance, NPNVWindowNPObject, &windowObject);
872 invokeResult=NPN_Invoke(_instance, windowObject, id, args, count, &result);
873 NPN_ReleaseObject(windowObject);
874 delete[] args;
877 // We got a result from invoking the Javascript method
878 std::stringstream ss;
879 if (invokeResult) {
880 ss << plugin::ExternalInterface::convertNPVariant(&result);
881 NPN_ReleaseVariantValue(&result);
882 } else {
883 // Send response
884 // FIXME: "securityError" also possible, check domain
885 ss << plugin::ExternalInterface::makeString("Error");
887 size_t ret = _scriptObject->writePlayer(ss.str());
888 if (ret != ss.str().size()) {
889 log_error("Couldn't write the response to Gnash, network problems.");
890 return false;
892 return true;
893 } else {
894 gnash::log_error("Unknown player request: " + std::string(buf));
895 return false;
898 return false;
901 std::string
902 getGnashExecutable()
904 std::string procname;
905 bool process_found = false;
906 struct stat procstats;
908 char *gnash_env = std::getenv("GNASH_PLAYER");
910 if (gnash_env) {
911 procname = gnash_env;
912 process_found = (0 == stat(procname.c_str(), &procstats));
913 if (!process_found) {
914 gnash::log_error("Invalid path to gnash executable: ");
915 return "";
919 if (!process_found) {
920 procname = GNASHBINDIR "/gtk-gnash";
921 process_found = (0 == stat(procname.c_str(), &procstats));
923 if (!process_found) {
924 procname = GNASHBINDIR "/kde4-gnash";
925 process_found = (0 == stat(procname.c_str(), &procstats));
928 if (!process_found) {
929 gnash::log_error(std::string("Unable to find Gnash in ") + GNASHBINDIR);
930 return "";
934 return procname;
937 void
938 create_standalone_launcher(const std::string& page_url, const std::string& swf_url,
939 const std::map<std::string, std::string>& params)
941 #ifdef CREATE_STANDALONE_GNASH_LAUNCHER
942 if (!createSaLauncher) {
943 return;
946 std::ofstream saLauncher;
948 std::stringstream ss;
949 static int debugno = 0;
950 debugno = (debugno + 1) % 10;
951 ss << "/tmp/gnash-debug-" << debugno << ".sh";
952 saLauncher.open(ss.str().c_str(), std::ios::out | std::ios::trunc);
954 if (!saLauncher) {
955 gnash::log_error("Failed to open new file for standalone launcher: " + ss.str());
956 return;
959 saLauncher << "#!/bin/sh" << std::endl
960 << "export GNASH_COOKIES_IN="
961 << "/tmp/gnash-cookies." << getpid() << std::endl
962 << getGnashExecutable() << " ";
964 if (!page_url.empty()) {
965 saLauncher << "-U '" << page_url << "' ";
968 for (std::map<std::string,std::string>::const_iterator it = params.begin(),
969 itEnd = params.end(); it != itEnd; ++it) {
970 const std::string& nam = it->first;
971 const std::string& val = it->second;
972 saLauncher << "-P '"
973 << boost::algorithm::replace_all_copy(nam, "'", "'\\''")
974 << "="
975 << boost::algorithm::replace_all_copy(val, "'", "'\\''")
976 << "' ";
979 saLauncher << "'" << swf_url << "' "
980 << "$@" // allow caller to pass any additional argument
981 << std::endl;
983 saLauncher.close();
984 #endif
987 void
988 nsPluginInstance::setupCookies(const std::string& pageurl)
990 // In pre xulrunner 1.9, (Firefox 3.1) this function does not exist,
991 // so we can't use it to read the cookie file. For older browsers
992 // like IceWeasel on Debian lenny, which pre dates the cookie support
993 // in NPAPI, you have to block all Cookie for sites like YouTube to
994 // allow Gnash to work.
995 if (!NPNFuncs.getvalueforurl) {
996 LOG_ONCE( gnash::log_debug("Browser doesn't support reading cookies") );
997 return;
1000 // Cookie appear to drop anything past the domain, so we strip
1001 // that off.
1002 std::string::size_type pos;
1003 pos = pageurl.find("/", pageurl.find("//", 0) + 2) + 1;
1004 std::string url = pageurl.substr(0, pos);
1006 char *cookie = 0;
1007 uint32_t length = 0;
1008 NPN_GetValueForURL(_instance, NPNURLVCookie, url.c_str(),
1009 &cookie, &length);
1010 if (cookie) {
1011 std::string ncookie (cookie, length);
1012 gnash::log_debug("The Cookie for %s is %s", url, ncookie);
1013 std::ofstream cookiefile;
1014 std::stringstream ss;
1015 ss << "/tmp/gnash-cookies." << getpid();
1017 cookiefile.open(ss.str().c_str(), std::ios::out | std::ios::trunc);
1018 cookiefile << "Set-Cookie: " << ncookie << std::endl;
1019 cookiefile.close();
1021 if (setenv("GNASH_COOKIES_IN", ss.str().c_str(), 1) < 0) {
1022 gnash::log_error(
1023 "Couldn't set environment variable GNASH_COOKIES_IN to %s",
1024 ncookie);
1026 NPN_MemFree(cookie);
1027 } else {
1028 gnash::log_debug("No stored Cookie for %s", url);
1032 void
1033 nsPluginInstance::setupProxy(const std::string& url)
1035 // In pre xulrunner 1.9, (Firefox 3.1) this function does not exist,
1036 // so we can't use it to read the proxy information.
1037 if (!NPNFuncs.getvalueforurl) return;
1039 char *proxy = 0;
1040 uint32_t length = 0;
1041 NPN_GetValueForURL(_instance, NPNURLVProxy, url.c_str(),
1042 &proxy, &length);
1043 if (!proxy) {
1044 gnash::log_debug("No proxy setting for %s", url);
1045 return;
1048 std::string nproxy (proxy, length);
1049 NPN_MemFree(proxy);
1051 gnash::log_debug("Proxy setting for %s is %s", url, nproxy);
1053 std::vector<std::string> parts;
1054 boost::split(parts, nproxy,
1055 boost::is_any_of(" "), boost::token_compress_on);
1056 if ( parts[0] == "DIRECT" ) {
1057 // no proxy
1059 else if ( parts[0] == "PROXY" ) {
1060 if (setenv("http_proxy", parts[1].c_str(), 1) < 0) {
1061 gnash::log_error(
1062 "Couldn't set environment variable http_proxy to %s",
1063 nproxy);
1066 else {
1067 gnash::log_error("Unknown proxy type: %s", nproxy);
1072 std::vector<std::string>
1073 nsPluginInstance::getCmdLine(int hostfd, int controlfd)
1075 std::vector<std::string> arg_vec;
1077 std::string cmd = getGnashExecutable();
1078 if (cmd.empty()) {
1079 gnash::log_error("Failed to locate the Gnash executable!");
1080 return arg_vec;
1082 arg_vec.push_back(cmd);
1084 arg_vec.push_back("-u");
1085 arg_vec.push_back(_swf_url);
1087 std::string pageurl = getCurrentPageURL();
1088 if (pageurl.empty()) {
1089 gnash::log_error("Could not get current page URL!");
1090 } else {
1091 arg_vec.push_back("-U");
1092 arg_vec.push_back(pageurl);
1095 setupCookies(pageurl);
1096 setupProxy(pageurl);
1098 std::stringstream pars;
1099 pars << "-x " << _window // X window ID to render into
1100 << " -j " << _width // Width of window
1101 << " -k " << _height; // Height of window
1102 #if GNASH_PLUGIN_DEBUG > 1
1103 pars << " -vv ";
1104 #endif
1105 if ((hostfd > 0) && (controlfd)) {
1106 pars << " -F " << hostfd // Socket to send commands to
1107 << ":" << controlfd; // Socket determining lifespan
1109 std::string pars_str = pars.str();
1110 typedef boost::char_separator<char> char_sep;
1111 boost::tokenizer<char_sep> tok(pars_str, char_sep(" "));
1112 arg_vec.insert(arg_vec.end(), tok.begin(), tok.end());
1114 for (std::map<std::string,std::string>::const_iterator it = _params.begin(),
1115 itEnd = _params.end(); it != itEnd; ++it) {
1116 const std::string& nam = it->first;
1117 const std::string& val = it->second;
1118 arg_vec.push_back("-P");
1119 arg_vec.push_back(nam + "=" + val);
1121 arg_vec.push_back("-");
1123 create_standalone_launcher(pageurl, _swf_url, _params);
1125 return arg_vec;
1128 template <std::size_t N>
1129 void
1130 close_fds(const int (& except)[N])
1132 // Rather than close all the thousands of possible file
1133 // descriptors, we start after stderr and keep closing higher numbers
1134 // until we encounter ten fd's in a row that
1135 // aren't open. This will tend to close most fd's in most programs.
1136 int numfailed = 0, closed = 0;
1137 for (int anfd = fileno(stderr)+1; numfailed < 10; anfd++) {
1138 if (std::find(except, except+N, anfd) != except+N) {
1139 continue;
1141 if (close(anfd) < 0) {
1142 numfailed++;
1143 } else {
1144 numfailed = 0;
1145 closed++;
1148 gnash::log_debug("Closed %d files.", closed);
1151 void
1152 wait_for_gdb()
1154 if (!waitforgdb) {
1155 return;
1158 // For debugging the plugin (GNASH_OPTIONS=waitforgdb)
1159 // Block here until gdb is attached and sets waitforgdb to false.
1161 std::cout << std::endl << " Attach GDB to PID " << getpid()
1162 << " to debug!" << std::endl
1163 << " This thread will block until then!" << std::endl
1164 << " Once blocked here, you can set other breakpoints."
1165 << std::endl
1166 << " Do a \"set variable waitforgdb=$false\" to continue"
1167 << std::endl << std::endl;
1169 while (waitforgdb) {
1170 sleep(1);
1174 void
1175 nsPluginInstance::startProc()
1178 int p2c_pipe[2];
1179 int c2p_pipe[2];
1180 int p2c_controlpipe[2];
1182 int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, p2c_pipe);
1183 if (ret == -1) {
1184 gnash::log_error("socketpair(p2c) failed: %s", strerror(errno));
1185 return;
1187 _streamfd = p2c_pipe[1];
1189 ret = socketpair(AF_UNIX, SOCK_STREAM, 0, c2p_pipe);
1190 if (ret == -1) {
1191 gnash::log_error("socketpair(c2p) failed: %s", strerror(errno));
1192 return;
1195 ret = socketpair(AF_UNIX, SOCK_STREAM, 0, p2c_controlpipe);
1196 if (ret == -1) {
1197 gnash::log_error("socketpair(control) failed: %s", strerror(errno));
1198 return;
1201 _scriptObject->setControlFD(p2c_controlpipe[1]);
1202 _scriptObject->setHostFD(c2p_pipe[0]);
1204 // Setup the command line for starting Gnash
1206 std::vector<std::string> arg_vec = getCmdLine(c2p_pipe[1],
1207 p2c_controlpipe[0]);
1209 if (arg_vec.empty()) {
1210 gnash::log_error("Failed to obtain command line parameters.");
1211 return;
1214 std::vector<const char*> args;
1216 std::transform(arg_vec.begin(), arg_vec.end(), std::back_inserter(args),
1217 std::mem_fun_ref(&std::string::c_str));
1218 args.push_back(0);
1220 // Argument List prepared, now fork(), close file descriptors and execv()
1222 _childpid = fork();
1224 // If the fork failed, childpid is -1. So print out an error message.
1225 if (_childpid == -1) {
1226 gnash::log_error("dup2() failed: %s", strerror(errno));
1227 return;
1230 // If we are the parent and fork() worked, childpid is a positive integer.
1231 if (_childpid > 0) {
1233 // we want to write to p2c pipe, so close read-fd0
1234 ret = close (p2c_pipe[0]);
1235 if (ret == -1) {
1236 // this is not really a fatal error, so continue best as we can
1237 gnash::log_error("p2c_pipe[0] close() failed: %s",
1238 strerror(errno));
1241 // we want to read from c2p pipe, so close read-fd1
1242 ret = close (c2p_pipe[1]);
1243 if (ret == -1) {
1244 // this is not really a fatal error, so continue best as we can
1245 gnash::log_error("c2p_pipe[1] close() failed: %s",
1246 strerror(errno));
1247 gnash::log_debug("Forked successfully but with ignorable errors.");
1248 } else {
1249 gnash::log_debug("Forked successfully, child process PID is %d",
1250 _childpid);
1253 GIOChannel* ichan = g_io_channel_unix_new(c2p_pipe[0]);
1254 gnash::log_debug("New IO Channel for fd #%d",
1255 g_io_channel_unix_get_fd(ichan));
1256 g_io_channel_set_close_on_unref(ichan, true);
1257 _ichanWatchId = g_io_add_watch(ichan,
1258 (GIOCondition)(G_IO_IN|G_IO_HUP),
1259 (GIOFunc)handlePlayerRequestsWrapper,
1260 this);
1261 return;
1264 // This is the child scope.
1266 // FF3 uses jemalloc and it has problems after the fork(), do NOT
1267 // use memory functions (malloc()/free()/new/delete) after the fork()
1268 // in the child thread process
1269 ret = close (p2c_pipe[1]);
1271 // close standard input and direct read-fd1 to standard input
1272 ret = dup2 (p2c_pipe[0], fileno(stdin));
1274 if (ret == -1) {
1275 gnash::log_error("dup2() failed: %s", strerror(errno));
1278 // Close all of the browser's file descriptors that we just inherited
1279 // (including p2c_pipe[0] that we just dup'd to fd 0), but obviously
1280 // not the file descriptors that we want the child to use.
1281 int dontclose[] = {c2p_pipe[1], c2p_pipe[0], p2c_controlpipe[0]};
1282 close_fds(dontclose);
1284 // Start the desired executable and go away.
1286 gnash::log_debug("Starting process: %s", boost::algorithm::join(arg_vec, " "));
1288 wait_for_gdb();
1290 execv(args[0], const_cast<char**>(&args.front()));
1292 // if execv returns, an error has occurred.
1293 perror("executing standalone gnash");
1295 exit (-1);
1298 std::string
1299 nsPluginInstance::getCurrentPageURL() const
1301 // Return:
1302 // window.document.baseURI
1304 // Was (bogus):
1305 // window.document.location.href
1308 NPP npp = _instance;
1310 NPIdentifier sDocument = NPN_GetStringIdentifier("document");
1312 NPObject *window;
1313 NPN_GetValue(npp, NPNVWindowNPObject, &window);
1315 NPVariant vDoc;
1316 NPN_GetProperty(npp, window, sDocument, &vDoc);
1317 NPN_ReleaseObject(window);
1319 if (!NPVARIANT_IS_OBJECT(vDoc)) {
1320 gnash::log_error("Can't get window.document object");
1321 return std::string();
1324 NPObject* npDoc = NPVARIANT_TO_OBJECT(vDoc);
1327 NPIdentifier sLocation = NPN_GetStringIdentifier("location");
1328 NPVariant vLoc;
1329 NPN_GetProperty(npp, npDoc, sLocation, &vLoc);
1330 NPN_ReleaseObject(npDoc);
1332 if (!NPVARIANT_IS_OBJECT(vLoc)) {
1333 gnash::log_error("Can't get window.document.location object");
1334 return std::string();
1337 NPObject* npLoc = NPVARIANT_TO_OBJECT(vLoc);
1339 NPIdentifier sProperty = NPN_GetStringIdentifier("href");
1340 NPVariant vProp;
1341 NPN_GetProperty(npp, npLoc, sProperty, &vProp);
1342 NPN_ReleaseObject(npLoc);
1344 if (!NPVARIANT_IS_STRING(vProp)) {
1345 gnash::log_error("Can't get window.document.location.href string");
1346 return std::string();
1350 NPIdentifier sProperty = NPN_GetStringIdentifier("baseURI");
1351 NPVariant vProp;
1352 NPN_GetProperty(npp, npDoc, sProperty, &vProp);
1353 NPN_ReleaseObject(npDoc);
1355 if (!NPVARIANT_IS_STRING(vProp)) {
1356 gnash::log_error("Can't get window.document.baseURI string");
1357 return std::string();
1360 const NPString& propValue = NPVARIANT_TO_STRING(vProp);
1362 return NPStringToString(propValue);
1365 void
1366 processLog_error(const boost::format& fmt)
1368 std::cerr << "ERROR: " << fmt.str() << std::endl;
1371 #if GNASH_PLUGIN_DEBUG > 1
1372 void
1373 processLog_debug(const boost::format& fmt)
1375 std::cout << "DEBUG: " << fmt.str() << std::endl;
1378 void
1379 processLog_trace(const boost::format& fmt)
1381 std::cout << "TRACE: " << fmt.str() << std::endl;
1383 #else
1384 void
1385 processLog_debug(const boost::format& /* fmt */)
1386 { /* do nothing */ }
1388 void
1389 processLog_trace(const boost::format& /* fmt */)
1390 { /* do nothing */ }
1391 #endif
1393 } // end of gnash namespace
1395 // Local Variables:
1396 // mode: C++
1397 // indent-tabs-mode: nil
1398 // End: