2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
3 // 2016 Free Software Foundation, Inc
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.
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.
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
21 #include "gnashconfig.h"
24 #include <boost/format.hpp>
25 #include <boost/algorithm/string/replace.hpp>
26 #include <boost/algorithm/string/find.hpp>
27 #define BOOST_IOSTREAMS_USE_DEPRECATED
28 #include <boost/iostreams/device/file_descriptor.hpp>
29 #include <boost/iostreams/stream.hpp>
32 #include <cstdlib> // getenv
33 #include <stdlib.h> // putenv
34 #include <sys/types.h>
35 #include "GnashSleep.h" // usleep
37 #if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
38 # include <winsock2.h>
41 # include <netinet/in.h>
42 # include <arpa/inet.h>
44 # include <sys/socket.h>
48 #define MIME_TYPES_HANDLED "application/x-shockwave-flash"
49 // The name must be this value to get flash movies that check the
50 // plugin version to load.
51 #define PLUGIN_NAME "Shockwave Flash"
52 #define MIME_TYPES_DESCRIPTION MIME_TYPES_HANDLED ":swf:" PLUGIN_NAME
54 // Some javascript plugin detectors use the description
55 // to decide the flash version to display. They expect the
56 // form (major version).(minor version) r(revision).
58 #define FLASH_VERSION DEFAULT_FLASH_MAJOR_VERSION "." \
59 DEFAULT_FLASH_MINOR_VERSION " r" DEFAULT_FLASH_REV_NUMBER "."
61 #define PLUGIN_DESCRIPTION \
62 "Shockwave Flash " FLASH_VERSION "<br>Gnash " VERSION ", the GNU SWF Player. \
63 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 \
64 <a href=\"http://www.fsf.org\">Free \
65 Software Foundation</a>, Inc. <br> \
66 Gnash comes with NO WARRANTY, to the extent permitted by law. \
67 You may redistribute copies of Gnash under the terms of the \
68 <a href=\"http://www.gnu.org/licenses/gpl.html\">GNU General Public \
69 License</a>. For more information about Gnash, see <a \
70 href=\"http://www.gnu.org/software/gnash/\"> \
71 http://www.gnu.org/software/gnash</a>. \
73 Compatible Shockwave Flash " FLASH_VERSION
76 #include "GnashSystemIOHeaders.h"
77 #include "StringPredicates.h"
79 #include "callbacks.h"
80 #if NPAPI_VERSION == 190
84 #include "npruntime.h"
85 #include "npfunctions.h"
87 #include "GnashNPVariant.h"
89 #include <boost/tokenizer.hpp>
90 #include <boost/algorithm/string/join.hpp>
91 #include <boost/algorithm/string/split.hpp>
92 #include <boost/algorithm/string/classification.hpp>
93 #include <boost/algorithm/string/trim.hpp>
94 #include <boost/format.hpp>
95 #include <sys/param.h>
100 #include <sys/types.h>
101 #include <sys/stat.h>
102 #include <sys/wait.h>
112 // Macro to prevent repeated logging calls for the same
114 #define LOG_ONCE(x) { \
115 static bool warned = false; \
116 if (!warned) { warned = true; x; } \
119 // For scriptable plugin support
120 #include "pluginScriptObject.h"
122 extern NPNetscapeFuncs NPNFuncs
;
123 extern NPPluginFuncs NPPFuncs
;
126 NPBool plugInitialized
= FALSE
;
127 std::string cookiefile
;
130 /// \brief Return the MIME Type description for this plugin.
135 NPP_GetMIMEDescription(void)
137 return const_cast<char *>(MIME_TYPES_DESCRIPTION
);
140 static bool waitforgdb
= false;
141 static bool createSaLauncher
= false;
143 static std::map
<int, std::string
> cookiemap
;
145 static const char* getPluginDescription();
148 getPluginDescription()
150 static const char* desc
= nullptr;
152 desc
= std::getenv("GNASH_PLUGIN_DESCRIPTION");
153 if (desc
== nullptr) desc
= PLUGIN_DESCRIPTION
;
158 boost::iostreams::file_descriptor_sink
getfdsink(char mkstemplate
[]);
160 boost::iostreams::file_descriptor_sink
161 getfdsink(char mksTemplate
[])
163 int fd
, suffix
= std::string(mksTemplate
).size() - std::string(mksTemplate
).find("XXXXXX") - 6;
165 fd
= mkstemps (mksTemplate
, suffix
);
168 char *mksTNoSuff
= const_cast<char*>(std::string(mksTemplate
).substr(0, std::string(mksTemplate
).size() - suffix
).c_str());
169 fd
= mkstemp (mksTNoSuff
);
170 const char *mksTSuffix
= std::string(mksTemplate
).substr(std::string(mksTemplate
).size() - suffix
, suffix
).c_str();
171 std::stringstream mksTFull
;
172 mksTFull
<< mksTNoSuff
<< mksTSuffix
;
173 if (rename (mksTNoSuff
, mksTFull
.str().c_str()) != 0) {
174 gnash::log_error("Failed to rename %s", mksTNoSuff
);
176 strcpy (mksTemplate
, mksTFull
.str().c_str());
178 fd
= mkstemp (mksTemplate
);
181 #if BOOST_VERSION < 104400
182 boost::iostreams::file_descriptor_sink
fdsink(fd
, true);
184 boost::iostreams::file_descriptor_sink
fdsink(fd
, boost::iostreams::close_handle
);
190 // general initialization and shutdown
193 /// \brief Initialize the plugin
195 /// This C++ function gets called once when the plugin is loaded,
196 /// regardless of how many instantiations there is actually playing
197 /// movies. So this is where all the one time only initialization
200 NS_PluginInitialize()
203 if ( gnash::plugInitialized
) {
204 gnash::log_debug("NS_PluginInitialize called, but ignored (we already initialized)");
205 return NPERR_NO_ERROR
;
208 gnash::log_debug("NS_PluginInitialize call ---------------------------");
210 // Browser Functionality Checks
212 NPError err
= NPERR_NO_ERROR
;
213 NPBool supportsXEmbed
= TRUE
;
215 // First, check for XEmbed support. The NPAPI Gnash plugin
216 // only works with XEmbed, so tell the plugin API to fail if
217 // XEmbed is not found.
218 err
= NPN_GetValue(nullptr,NPNVSupportsXEmbedBool
,
219 (void *)&supportsXEmbed
);
221 if (err
!= NPERR_NO_ERROR
|| !supportsXEmbed
) {
222 gnash::log_error("NPAPI ERROR: No xEmbed support in this browser!");
223 return NPERR_INCOMPATIBLE_VERSION_ERROR
;
225 gnash::log_debug("xEmbed supported in this browser");
228 // GTK is not strictly required, but we do use the Glib main event loop,
229 // so lack of GTK means reduced functionality.
230 NPNToolkitType toolkit
;
231 err
= NPN_GetValue(nullptr, NPNVToolkit
, &toolkit
);
233 if (err
!= NPERR_NO_ERROR
|| toolkit
!= NPNVGtk2
) {
234 gnash::log_error("NPAPI ERROR: No GTK2 support in this browser! Have version %d", (int)toolkit
);
236 gnash::log_debug("GTK2 supported in this browser");
240 Check for environment variables.
242 char* opts
= std::getenv("GNASH_OPTIONS");
243 if (opts
!= nullptr) {
244 gnash::log_debug("GNASH_OPTIONS: %s", opts
);
246 // Should the plugin wait for gdb to be attached?
247 if ( strstr(opts
, "waitforgdb") ) {
251 // Should the plugin write a script to invoke
252 // the standalone player for debugging ?
253 if ( strstr(opts
, "writelauncher") ) {
254 createSaLauncher
= true;
259 // Append SYSCONFDIR/gnashpluginrc and ~/.gnashpluginrc to GNASHRC
261 std::string newGnashRc
;
263 #if !defined(__OS2__ ) && ! defined(__amigaos4__)
264 newGnashRc
.append(SYSCONFDIR
);
265 newGnashRc
.append("/gnashpluginrc");
268 const char *home
= nullptr;
269 #if defined(__amigaos4__)
270 //on AmigaOS we have a GNASH: assign that point to program dir
272 #elif defined(__HAIKU__)
274 if (B_OK
!= find_directory(B_USER_SETTINGS_DIRECTORY
, &bp
))
276 std::cerr
<< "Failed to find user settings directory" << std::endl
;
282 home
= std::getenv("HOME");
285 newGnashRc
.append(":");
286 newGnashRc
.append(home
);
288 newGnashRc
.append("/gnashpluginrc");
290 newGnashRc
.append("/.gnashpluginrc");
293 gnash::log_error("WARNING: NPAPI plugin could not find user home dir");
296 const char *gnashrc
= std::getenv("GNASHRC");
298 newGnashRc
.append(":");
299 newGnashRc
.append(gnashrc
);
302 if ( setenv("GNASHRC", newGnashRc
.c_str(), 1) ) {
303 gnash::log_debug("WARNING: NPAPI plugin could not append to the GNASHRC env variable");
305 gnash::log_debug("NOTE: NPAPI plugin set GNASHRC to %d", newGnashRc
);
310 gnash::plugInitialized
= TRUE
;
312 return NPERR_NO_ERROR
;
315 /// \brief Shutdown the plugin
317 /// This C++ function gets called once when the plugin is being
318 /// shutdown, regardless of how many instantiations actually are
319 /// playing movies. So this is where all the one time only
320 /// shutdown stuff goes.
325 if (!plugInitialized
) {
326 gnash::log_debug("Plugin already shut down");
330 plugInitialized
= FALSE
;
334 /// \brief Retrieve values from the plugin for the Browser
336 /// This C++ function is called by the browser to get certain
337 /// information is needs from the plugin. This information is the
338 /// plugin name, a description, etc...
340 NS_PluginGetValue(NPPVariable aVariable
, void *aValue
)
342 NPError err
= NPERR_NO_ERROR
;
345 case NPPVpluginNameString
:
346 *static_cast<const char **> (aValue
) = PLUGIN_NAME
;
349 // This becomes the description field you see below the opening
350 // text when you type about:plugins and in
351 // navigator.plugins["Shockwave Flash"].description, used in
352 // many flash version detection scripts.
353 case NPPVpluginDescriptionString
:
354 *static_cast<const char **>(aValue
) = getPluginDescription();
357 case NPPVpluginWindowBool
:
360 case NPPVpluginTimerInterval
:
363 case NPPVpluginKeepLibraryInMemory
:
366 case NPPVpluginNeedsXEmbed
:
368 *static_cast<NPBool
*>(aValue
) = TRUE
;
370 *static_cast<NPBool
*>(aValue
) = FALSE
;
374 case NPPVpluginScriptableNPObject
:
377 #if NPAPI_VERSION != 190
378 case NPPVpluginUrlRequestsDisplayedBool
:
380 case NPPVpluginWantsAllNetworkStreams
:
385 err
= NPERR_INVALID_PARAM
;
391 /// \brief construct our plugin instance object
393 /// This instantiates a new object via a C++ function used by the
395 nsPluginInstanceBase
*
396 NS_NewPluginInstance(nsPluginCreateData
* aCreateDataStruct
)
398 // gnash::log_debug(__PRETTY_FUNCTION__);
400 if(!aCreateDataStruct
) {
404 return new gnash::nsPluginInstance(aCreateDataStruct
);
407 /// \brief destroy our plugin instance object
409 /// This destroys our instantiated object via a C++ function used by the
412 NS_DestroyPluginInstance(nsPluginInstanceBase
* aPlugin
)
414 delete static_cast<gnash::nsPluginInstance
*> (aPlugin
);
419 inline bool HasScripting()
421 return (NPNFuncs
.version
>= NPVERS_HAS_NPRUNTIME_SCRIPTING
);
426 // nsPluginInstance class implementation
429 /// \brief Constructor
430 nsPluginInstance::nsPluginInstance(nsPluginCreateData
* data
)
432 nsPluginInstanceBase(),
433 _instance(data
->instance
),
441 _scriptObject(nullptr)
443 // gnash::log_debug("%s: %x", __PRETTY_FUNCTION__, (void *)this);
445 for (size_t i
=0, n
=data
->argc
; i
<n
; ++i
) {
446 std::string name
, val
;
447 gnash::StringNoCaseEqual noCaseCompare
;
450 name
= data
->argn
[i
];
457 if (noCaseCompare(name
, "name")) {
464 if (HasScripting()) {
465 _scriptObject
= (GnashPluginScriptObject
*) NPN_CreateObject( _instance
,
466 GnashPluginScriptObject::marshalGetNPClass());
473 cleanup_childpid(gpointer data
)
475 int* pid
= static_cast<int*>(data
);
478 int rv
= waitpid(*pid
, &status
, WNOHANG
);
481 // The child process has not exited; it may be deadlocked. Kill it.
484 waitpid(*pid
, &status
, 0);
487 gnash::log_debug("Child process exited with status %s", status
);
494 /// \brief Destructor
495 nsPluginInstance::~nsPluginInstance()
497 // gnash::log_debug("plugin instance destruction");
500 NPN_ReleaseObject(_scriptObject
);
503 do { } while (g_source_remove_by_user_data(this));
506 // When the child has terminated (signaled by GTK through GtkSocket), it
507 // remains as a defunct process and we remove it from the kernel table now.
509 // FIXME: we should ideally do this before the GtkSocket goes away, but
510 // after the delete signal has been sent.
512 // If all goes well, Gnash will already have terminated.
514 int rv
= waitpid(_childpid
, &status
, WNOHANG
);
517 int* pid
= new int(_childpid
);
519 cleanup_childpid(pid
);
521 gnash::log_debug("Child process exited with status %d", status
);
524 // Remove cookiefile if writelauncher not set
525 const char* options
= std::getenv("GNASH_OPTIONS");
526 if ((!options
|| !strstr(options
, "writelauncher")) &&
527 (!cookiefile
.empty())) {
528 std::map
<int, std::string
>::iterator it
= cookiemap
.find(_childpid
);
529 if (it
!= cookiemap
.end()) {
530 if (remove(it
->second
.c_str())) {
531 gnash::log_error("Cookiefile %s removal failed [pid %d]",
532 it
->second
, _childpid
);
534 gnash::log_debug("Cookiefile %s removed [pid %d]",
535 it
->second
, _childpid
);
544 /// \brief Initialize an instance of the plugin object
546 /// This methods initializes the plugin object, and is called for
547 /// every movie that gets played. This is where the movie playing
548 /// specific initialization goes.
550 nsPluginInstance::init(NPWindow
* aWindow
)
553 gnash::log_error("%s: ERROR: Window handle was bogus!", __PRETTY_FUNCTION__
);
556 #if GNASH_PLUGIN_DEBUG > 1
557 std::cout
<< "X origin: = " << aWindow
->x
558 << ", Y Origin = " << aWindow
->y
559 << ", Width = " << aWindow
->width
560 << ", Height = " << aWindow
->height
561 << ", WindowID = " << aWindow
->window
562 << ", this = " << static_cast<void*>(this) << std::endl
;
569 /// \brief Shutdown an instantiated object
571 /// This shuts down an object, and is called for every movie that gets
572 /// played. This is where the movie playing specific shutdown code
575 nsPluginInstance::shut()
577 gnash::log_debug("Gnash plugin shutting down");
579 if (_streamfd
!= -1) {
580 if (close(_streamfd
) == -1) {
581 perror("closing _streamfd");
588 /// \brief Set the window to be used to render in
590 /// This sets up the window the plugin is supposed to render
591 /// into. This calls passes in various information used by the plugin
592 /// to setup the window. This may get called multiple times by each
593 /// instantiated object, so it can't do much but window specific
596 nsPluginInstance::SetWindow(NPWindow
* aWindow
)
599 gnash::log_error(std::string(__FUNCTION__
) + ": ERROR: Window handle was bogus!");
600 return NPERR_INVALID_PARAM
;
603 gnash::log_debug("%s: X origin = %d, Y Origin = %d, Width = %d,"
604 " Height = %d, WindowID = %p, this = %p",
606 aWindow
->x
, aWindow
->y
, aWindow
->width
, aWindow
->height
,
607 aWindow
->window
, this);
612 return NPERR_GENERIC_ERROR
;
615 _width
= aWindow
->width
;
616 _height
= aWindow
->height
;
618 _window
= reinterpret_cast<Window
> (aWindow
->window
);
620 // When testing the interface to the plugin, don't start the player
621 // as a debug client "nc -l 1111" is used instead.
622 if (!_childpid
&& !_swf_url
.empty()) {
626 return NPERR_NO_ERROR
;
630 nsPluginInstance::GetValue(NPPVariable aVariable
, void *aValue
)
633 if (aVariable
== NPPVpluginScriptableNPObject
) {
635 void **v
= (void **)aValue
;
636 NPN_RetainObject(_scriptObject
);
639 gnash::log_debug("_scriptObject is not assigned");
643 // log_debug("SCRIPT OBJECT getValue: %x, ns: %x", (void *)_scriptObject, (void *)this);
645 return NS_PluginGetValue(aVariable
, aValue
);
648 /// \brief Open a new data stream
650 /// Opens a new incoming data stream, which is the flash movie we want
652 /// A URL can be pretty ugly, like in this example:
653 /// 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
654 /// ../flash/gui.swf?ip_addr=foobar.com&ip_port=3660&show_cursor=true&path_prefix=../flash/&trapallkeys=true"
658 nsPluginInstance::NewStream(NPMIMEType
/*type*/, NPStream
* stream
,
659 NPBool
/*seekable*/, uint16_t* /*stype*/)
661 // gnash::log_debug("%s: %x", __PRETTY_FUNCTION__, (void *)this);
664 // Apparently the child process has already been started for this
665 // plugin instance. It is puzzling that this method gets called
666 // again. Starting a new process for the same movie will cause
667 // problems later, so we'll stop here.
668 return NPERR_GENERIC_ERROR
;
670 _swf_url
= stream
->url
;
672 if (!_swf_url
.empty() && _window
) {
676 return NPERR_NO_ERROR
;
679 /// \brief Destroy the data stream we've been reading.
681 nsPluginInstance::DestroyStream(NPStream
* /*stream*/, NPError
/*reason*/)
683 if (_streamfd
!= -1) {
684 if (close(_streamfd
) == -1) {
685 perror("closing _streamfd");
691 return NPERR_NO_ERROR
;
694 /// \brief Return how many bytes we can read into the buffer
696 nsPluginInstance::WriteReady(NPStream
* /* stream */ )
698 //gnash::log_debug("Stream for %s is ready", stream->url);
699 if ( _streamfd
!= -1 ) {
706 /// \brief Read the data stream from Mozilla/Firefox
709 nsPluginInstance::Write(NPStream
* /*stream*/, int32_t /*offset*/, int32_t len
,
712 int written
= write(_streamfd
, buffer
, len
);
717 nsPluginInstance::handlePlayerRequestsWrapper(GIOChannel
* iochan
,
718 GIOCondition cond
, nsPluginInstance
* plugin
)
720 return plugin
->handlePlayerRequests(iochan
, cond
);
724 nsPluginInstance::handlePlayerRequests(GIOChannel
* iochan
, GIOCondition cond
)
726 // gnash::log_debug("%s: %d: %x", __PRETTY_FUNCTION__, __LINE__, (void *)this);
728 if ( cond
& G_IO_HUP
) {
729 gnash::log_debug("Player control socket hang up");
733 assert(cond
& G_IO_IN
);
735 gnash::log_debug("Checking player requests on FD #%d",
736 g_io_channel_unix_get_fd(iochan
));
738 const size_t buf_size
= 1;
739 gchar buffer
[buf_size
];
742 GError
* error
= nullptr;
743 gsize bytes_read
= 0;
745 GIOStatus status
= g_io_channel_read_chars(iochan
, buffer
, buf_size
,
746 &bytes_read
, &error
);
749 case G_IO_STATUS_ERROR
:
750 gnash::log_error("error reading request line: %s",
751 error
? error
->message
: "unspecified error");
754 case G_IO_STATUS_EOF
:
755 gnash::log_error("EOF (error: %s)",
756 error
? error
->message
: "unspecified error");
759 case G_IO_STATUS_AGAIN
:
760 gnash::log_debug("read again");
762 case G_IO_STATUS_NORMAL
:
764 _requestbuf
.append(buffer
, buffer
+bytes_read
);
766 gnash::log_debug("Normal read: %s", std::string(buffer
, buffer
+bytes_read
));
770 gnash::log_error("Abnormal status!");
773 } while (g_io_channel_get_buffer_condition(iochan
) & G_IO_IN
);
776 processPlayerRequest();
781 // This GIOFunc handler removes the source from the mainloop.
783 remove_handler(GIOChannel
*, GIOCondition
, gpointer
)
789 // There may be multiple Invoke messages in a single packet, so each
790 // packet needs to be broken up into separate messages to be parsed.
792 nsPluginInstance::processPlayerRequest()
795 gnash::log_debug(__PRETTY_FUNCTION__
);
797 log_debug("SCRIPT OBJECT %d: %x", __LINE__
, this->getScriptObject());
800 if ( _requestbuf
.size() < 4 ) {
801 gnash::log_error("Invalid player request (too short): %s", _requestbuf
);
806 std::string
& packet
= _requestbuf
;
808 boost::trim_left(packet
);
809 if (packet
.empty()) {
813 std::string term
= "</invoke>";
814 std::string::size_type pos
= packet
.find(term
);
815 // no terminator, bad message
816 if (pos
== std::string::npos
) {
817 gnash::log_debug("Incomplete Invoke message. Probably a fragment.");
821 // Extract a message from the packet
822 std::string msg
= packet
.substr(0, pos
+ term
.size());
823 std::shared_ptr
<plugin::ExternalInterface::invoke_t
> invoke
=
824 plugin::ExternalInterface::parseInvoke(_scriptObject
, msg
);
826 // drop the parsed message from the packet
827 packet
.erase(0, msg
.size());
830 log_error("Failed to parse invoke message: %s", msg
);
834 if (!invoke
->name
.empty()) {
835 gnash::log_debug("Requested method is: %s", invoke
->name
);
837 gnash::log_error("Invoke request missing a name to invoke.");
841 if (invoke
->name
== "getURL") {
843 assert(invoke
->args
.size() > 1);
845 // The first argument is the URL string.
846 std::string url
= NPVariantToString(invoke
->args
[0].get());
848 gnash::log_debug("Got a getURL() request: %s", url
);
850 // The second is the method, namely GET or POST.
851 std::string op
= NPVariantToString(invoke
->args
[1].get());
852 // The third is the optional target, which is something like
856 // The fourth is the optional data. If there is data, the target
857 // field is always set so this argument is on the correct index.
860 if (invoke
->args
.size() >= 3) {
861 target
= NPVariantToString(invoke
->args
[2].get());
864 // An empty target defaults to "_self"
865 // This is _required_ for chromium,
866 // see https://savannah.gnu.org/bugs/?32425
867 if ( target
.empty() ) target
= "_self";
869 if (invoke
->args
.size() == 4) {
870 data
= NPVariantToString(invoke
->args
[3].get());
873 gnash::log_debug("Asked to getURL '%s' in target %s", url
,
875 NPN_GetURL(_instance
, url
.c_str(), target
.c_str());
876 } else if (op
== "POST") {
877 gnash::log_debug("Asked to postURL '%s' this data %s", url
,
879 NPN_PostURL(_instance
, url
.c_str(), target
.c_str(), data
.size(),
880 data
.c_str(), false);
882 log_error("Unexpected op in getURL (expected POST or GET).");
886 } else if (invoke
->name
== "fsCommand") {
888 assert(invoke
->args
.size() > 1);
889 std::string command
= NPVariantToString(invoke
->args
[0].get());
890 std::string arg
= NPVariantToString(invoke
->args
[1].get());
891 std::string name
= _name
;
892 std::stringstream jsurl
;
893 jsurl
<< "javascript:" << name
<< "_DoFSCommand('" << command
894 << "','" << arg
<<"')";
896 // TODO: check if _self is a good target for this
897 static const char* tgt
= "_self";
899 gnash::log_debug("Calling NPN_GetURL(%s, %s)",
902 NPN_GetURL(_instance
, jsurl
.str().c_str(), tgt
);
904 } else if (invoke
->name
== "addMethod") {
906 assert(!invoke
->args
.empty());
908 if (!HasScripting()) {
909 LOG_ONCE(log_debug("Ignoring addMethod, no scripting."));
912 // Make this flash function accessible to Javascript. The
913 // actual callback lives in libcore/movie_root, but it
914 // needs to be on the list of supported remote methods so
915 // it can be called by Javascript.
916 std::string method
= NPVariantToString(invoke
->args
[0].get());
917 NPIdentifier id
= NPN_GetStringIdentifier(method
.c_str());
918 // log_debug("SCRIPT OBJECT addMethod: %x, %s", (void *)_scriptObject, method);
919 this->getScriptObject()->AddMethod(id
, remoteCallback
);
923 if (!HasScripting()) {
924 LOG_ONCE(log_debug("Ignoring invoke, no scripting."));
929 VOID_TO_NPVARIANT(result
);
930 bool invokeResult
= false;
932 // This is the player invoking a method in Javascript
933 if (!invoke
->name
.empty() && !invoke
->args
.empty()) {
934 //Convert the as_value argument to NPVariant
935 const size_t count
= invoke
->args
.size() - 1;
936 std::unique_ptr
<NPVariant
[]> args(new NPVariant
[count
]);
937 //Skip the first argument
938 for (size_t i
= 0; i
< count
; ++i
) {
939 invoke
->args
[i
+1].copy(args
[i
]);
942 NPIdentifier id
= NPN_GetStringIdentifier(invoke
->name
.c_str());
943 gnash::log_debug("Invoking JavaScript method %s", invoke
->name
);
944 NPObject
* windowObject
;
945 NPN_GetValue(_instance
, NPNVWindowNPObject
, &windowObject
);
946 invokeResult
=NPN_Invoke(_instance
, windowObject
, id
, args
.get(),
948 NPN_ReleaseObject(windowObject
);
950 // We got a result from invoking the Javascript method
951 std::stringstream ss
;
953 ss
<< plugin::ExternalInterface::convertNPVariant(&result
);
954 NPN_ReleaseVariantValue(&result
);
955 size_t ret
= _scriptObject
->writePlayer(ss
.str());
956 if (ret
!= ss
.str().size()) {
957 log_error("Couldn't write the response to Gnash, network problems.");
960 } while (!packet
.empty());
968 std::string procname
;
969 bool process_found
= false;
970 struct stat procstats
;
972 char *gnash_env
= std::getenv("GNASH_PLAYER");
975 procname
= gnash_env
;
976 process_found
= (0 == stat(procname
.c_str(), &procstats
));
977 if (!process_found
) {
978 gnash::log_error("Invalid path to gnash executable: ");
983 if (!process_found
) {
984 procname
= GNASHBINDIR
"/gtk-gnash";
985 process_found
= (0 == stat(procname
.c_str(), &procstats
));
987 if (!process_found
) {
988 procname
= GNASHBINDIR
"/qt4-gnash";
989 process_found
= (0 == stat(procname
.c_str(), &procstats
));
992 if (!process_found
) {
993 gnash::log_error(std::string("Unable to find Gnash in ") + GNASHBINDIR
);
1001 create_standalone_launcher(const std::string
& page_url
, const std::string
& swf_url
,
1002 const std::map
<std::string
, std::string
>& params
)
1004 #ifdef CREATE_STANDALONE_GNASH_LAUNCHER
1005 if (!createSaLauncher
) {
1009 char debugname
[] = "/tmp/gnash-debug-XXXXXX.sh";
1010 boost::iostreams::file_descriptor_sink fdsink
= getfdsink(debugname
);
1011 #if BOOST_VERSION >= 104400
1012 if (fdsink
.handle() == -1) {
1013 gnash::log_error("Failed to create sink: %s", debugname
);
1017 boost::iostreams::stream
<boost::iostreams::file_descriptor_sink
>
1018 saLauncher (fdsink
);
1021 gnash::log_error("Failed to open new file for standalone launcher: %s", debugname
);
1025 saLauncher
<< "#!/bin/sh" << std::endl
1026 << getGnashExecutable() << " ";
1028 if (!page_url
.empty()) {
1029 saLauncher
<< "-U '" << page_url
<< "' ";
1031 if (!cookiefile
.empty()) {
1032 saLauncher
<< "-C " << cookiefile
<< " ";
1034 for (std::map
<std::string
,std::string
>::const_iterator it
= params
.begin(),
1035 itEnd
= params
.end(); it
!= itEnd
; ++it
) {
1036 const std::string
& nam
= it
->first
;
1037 const std::string
& val
= it
->second
;
1038 saLauncher
<< "-P '"
1039 << boost::algorithm::replace_all_copy(nam
, "'", "'\\''")
1041 << boost::algorithm::replace_all_copy(val
, "'", "'\\''")
1045 saLauncher
<< "'" << swf_url
<< "' "
1046 << "$@" // allow caller to pass any additional argument
1055 nsPluginInstance::getDocumentProp(const std::string
& propname
) const
1059 if (!HasScripting()) {
1060 LOG_ONCE( gnash::log_debug("Browser doesn't support scripting") );
1064 NPObject
* windowobj
;
1065 NPError err
= NPN_GetValue(_instance
, NPNVWindowNPObject
, &windowobj
);
1066 if (err
!= NPERR_NO_ERROR
|| !windowobj
) {
1070 std::shared_ptr
<NPObject
> window_obj(windowobj
, NPN_ReleaseObject
);
1072 NPIdentifier doc_id
= NPN_GetStringIdentifier("document");
1075 if(! NPN_GetProperty(_instance
, windowobj
, doc_id
, &docvar
) ) {
1079 std::shared_ptr
<NPVariant
> doc_var(&docvar
, NPN_ReleaseVariantValue
);
1081 if (!NPVARIANT_IS_OBJECT(docvar
)) {
1085 NPObject
* doc_obj
= NPVARIANT_TO_OBJECT(docvar
);
1087 NPIdentifier prop_id
= NPN_GetStringIdentifier(propname
.c_str());
1090 if (!NPN_GetProperty(_instance
, doc_obj
, prop_id
, &propvar
)) {
1094 std::shared_ptr
<NPVariant
> prop_var(&propvar
, NPN_ReleaseVariantValue
);
1096 if (!NPVARIANT_IS_STRING(propvar
)) {
1100 const NPString
& prop_str
= NPVARIANT_TO_STRING(propvar
);
1102 rv
= NPStringToString(prop_str
);
1109 nsPluginInstance::setupCookies(const std::string
& pageurl
)
1111 // Cookie appear to drop anything past the domain, so we strip
1113 std::string::size_type pos
;
1114 pos
= pageurl
.find("/", pageurl
.find("//", 0) + 2) + 1;
1115 std::string url
= pageurl
.substr(0, pos
);
1117 std::string ncookie
;
1119 char *cookie
= nullptr;
1120 uint32_t length
= 0;
1122 NPError rv
= NPERR_GENERIC_ERROR
;
1123 #if NPAPI_VERSION != 190
1124 if (NPNFuncs
.getvalueforurl
) {
1125 rv
= NPN_GetValueForURL(_instance
, NPNURLVCookie
, url
.c_str(),
1128 LOG_ONCE( gnash::log_debug("Browser doesn't support getvalueforurl") );
1132 // Firefox does not (always) return the cookies that are associated
1133 // with a domain name through GetValueForURL.
1134 if (rv
== NPERR_GENERIC_ERROR
) {
1135 log_debug("Trying window.document.cookie for cookies");
1136 ncookie
= getDocumentProp("cookie");
1140 ncookie
.assign(cookie
, length
);
1141 NPN_MemFree(cookie
);
1144 if (ncookie
.empty()) {
1145 gnash::log_debug("No stored Cookie for %s", url
);
1150 gnash::log_debug("The Cookie for %s is %s", url
, ncookie
);
1151 char mkstemplate
[] = "/tmp/gnash-cookie.XXXXXX";
1152 boost::iostreams::file_descriptor_sink fdsink
= getfdsink(mkstemplate
);
1153 #if BOOST_VERSION >= 104400
1154 if (fdsink
.handle() == -1) {
1155 gnash::log_error("Failed to create sink: %s", mkstemplate
);
1159 cookiefile
= mkstemplate
;
1160 boost::iostreams::stream
<boost::iostreams::file_descriptor_sink
>
1161 cookiestream (fdsink
);
1163 // Firefox provides cookies in the following format:
1165 // cookie1=value1;cookie2=value2;cookie3=value3
1167 // Whereas libcurl expects cookies in the following format:
1169 // Set-Cookie: cookie1=value1;
1170 // Set-Cookie: cookie2=value2;
1172 typedef boost::char_separator
<char> char_sep
;
1173 typedef boost::tokenizer
<char_sep
> tokenizer
;
1174 tokenizer
tok(ncookie
, char_sep(";"));
1176 for (tokenizer::iterator it
=tok
.begin(); it
!= tok
.end(); ++it
) {
1177 cookiestream
<< "Set-Cookie: " << *it
<< std::endl
;
1180 cookiestream
.close();
1182 gnash::log_debug("Cookiefile is %s", cookiefile
);
1186 nsPluginInstance::setupProxy(const std::string
& url
)
1188 // In pre xulrunner 1.9, (Firefox 3.1) this function does not exist,
1189 // so we can't use it to read the proxy information.
1190 #if NPAPI_VERSION != 190
1191 if (!NPNFuncs
.getvalueforurl
) return;
1194 char *proxy
= nullptr;
1195 uint32_t length
= 0;
1196 #if NPAPI_VERSION != 190
1197 NPN_GetValueForURL(_instance
, NPNURLVProxy
, url
.c_str(),
1201 gnash::log_debug("No proxy setting for %s", url
);
1205 std::string
nproxy (proxy
, length
);
1208 gnash::log_debug("Proxy setting for %s is %s", url
, nproxy
);
1210 std::vector
<std::string
> parts
;
1211 boost::split(parts
, nproxy
,
1212 boost::is_any_of(" "), boost::token_compress_on
);
1213 if ( parts
[0] == "DIRECT" ) {
1216 else if ( parts
[0] == "PROXY" ) {
1217 // authenticated proxies: need to specify proxy credentials through
1218 // http_proxy env var set to user:pass@proxy:port. If set, it won't be
1219 // overridden. See https://savannah.gnu.org/bugs/?26713
1220 char *http_proxy
=getenv("http_proxy");
1222 gnash::log_debug("Setting http_proxy to %s", parts
[1].c_str());
1223 if (setenv("http_proxy", parts
[1].c_str(), 1) < 0) {
1225 "Couldn't set environment variable http_proxy to %s",
1229 gnash::log_debug("http_proxy already set to %s", http_proxy
);
1233 gnash::log_error("Unknown proxy type: %s", nproxy
);
1238 std::vector
<std::string
>
1239 nsPluginInstance::getCmdLine(int hostfd
, int controlfd
)
1241 std::vector
<std::string
> arg_vec
;
1243 std::string cmd
= getGnashExecutable();
1245 gnash::log_error("Failed to locate the Gnash executable!");
1248 arg_vec
.push_back(cmd
);
1250 arg_vec
.push_back("-u");
1251 arg_vec
.push_back(_swf_url
);
1253 std::string pageurl
= getCurrentPageURL();
1254 if (pageurl
.empty()) {
1255 gnash::log_error("Could not get current page URL!");
1257 arg_vec
.push_back("-U");
1258 arg_vec
.push_back(pageurl
);
1261 setupCookies(pageurl
);
1262 setupProxy(pageurl
);
1264 std::stringstream pars
;
1265 pars
<< "-x " << _window
// X window ID to render into
1266 << " -j " << _width
// Width of window
1267 << " -k " << _height
; // Height of window
1268 #if GNASH_PLUGIN_DEBUG > 1
1271 if ((hostfd
> 0) && (controlfd
)) {
1272 pars
<< " -F " << hostfd
// Socket to send commands to
1273 << ":" << controlfd
; // Socket determining lifespan
1275 if (!cookiefile
.empty()) {
1276 pars
<< " -C " << cookiefile
;
1278 std::string pars_str
= pars
.str();
1279 typedef boost::char_separator
<char> char_sep
;
1280 boost::tokenizer
<char_sep
> tok(pars_str
, char_sep(" "));
1281 arg_vec
.insert(arg_vec
.end(), tok
.begin(), tok
.end());
1283 for (std::map
<std::string
,std::string
>::const_iterator it
= _params
.begin(),
1284 itEnd
= _params
.end(); it
!= itEnd
; ++it
) {
1285 const std::string
& nam
= it
->first
;
1286 const std::string
& val
= it
->second
;
1287 arg_vec
.push_back("-P");
1288 arg_vec
.push_back(nam
+ "=" + val
);
1290 arg_vec
.push_back("-");
1292 create_standalone_launcher(pageurl
, _swf_url
, _params
);
1297 template <std::size_t N
>
1299 close_fds(const int (& except
)[N
])
1301 // Rather than close all the thousands of possible file
1302 // descriptors, we start after stderr and keep closing higher numbers
1303 // until we encounter ten fd's in a row that
1304 // aren't open. This will tend to close most fd's in most programs.
1305 int numfailed
= 0, closed
= 0;
1306 for (int anfd
= fileno(stderr
)+1; numfailed
< 10; anfd
++) {
1307 if (std::find(except
, except
+N
, anfd
) != except
+N
) {
1310 if (close(anfd
) < 0) {
1317 gnash::log_debug("Closed %d files.", closed
);
1327 // For debugging the plugin (GNASH_OPTIONS=waitforgdb)
1328 // Block here until gdb is attached and sets waitforgdb to false.
1330 std::cout
<< std::endl
<< " Attach GDB to PID " << getpid()
1331 << " to debug!" << std::endl
1332 << " This thread will block until then!" << std::endl
1333 << " Once blocked here, you can set other breakpoints."
1335 << " Do a \"set variable waitforgdb=$false\" to continue"
1336 << std::endl
<< std::endl
;
1338 while (waitforgdb
) {
1344 nsPluginInstance::setupIOChannel(int fd
, GIOFunc handler
, GIOCondition signals
)
1346 GIOChannel
* ichan
= g_io_channel_unix_new(fd
);
1347 g_io_channel_set_close_on_unref(ichan
, true);
1349 gnash::log_debug("New IO Channel for fd #%d",
1350 g_io_channel_unix_get_fd(ichan
));
1351 g_io_add_watch(ichan
, signals
, handler
, this);
1352 g_io_channel_unref(ichan
);
1357 nsPluginInstance::startProc()
1362 int p2c_controlpipe
[2];
1364 int ret
= socketpair(AF_UNIX
, SOCK_STREAM
, 0, p2c_pipe
);
1366 gnash::log_error("socketpair(p2c) failed: %s", strerror(errno
));
1367 return NPERR_GENERIC_ERROR
;
1369 _streamfd
= p2c_pipe
[1];
1371 ret
= socketpair(AF_UNIX
, SOCK_STREAM
, 0, c2p_pipe
);
1373 gnash::log_error("socketpair(c2p) failed: %s", strerror(errno
));
1374 return NPERR_GENERIC_ERROR
;
1377 ret
= socketpair(AF_UNIX
, SOCK_STREAM
, 0, p2c_controlpipe
);
1379 gnash::log_error("socketpair(control) failed: %s", strerror(errno
));
1380 return NPERR_GENERIC_ERROR
;
1383 if (HasScripting() && _scriptObject
) {
1384 _scriptObject
->setControlFD(p2c_controlpipe
[1]);
1385 _scriptObject
->setHostFD(c2p_pipe
[0]);
1388 // Setup the command line for starting Gnash
1390 std::vector
<std::string
> arg_vec
= getCmdLine(c2p_pipe
[1],
1391 p2c_controlpipe
[0]);
1393 if (arg_vec
.empty()) {
1394 gnash::log_error("Failed to obtain command line parameters.");
1395 return NPERR_GENERIC_ERROR
;
1398 std::vector
<const char*> args
;
1400 std::transform(arg_vec
.begin(), arg_vec
.end(), std::back_inserter(args
),
1401 std::mem_fun_ref(&std::string::c_str
));
1402 args
.push_back(nullptr);
1404 // Argument List prepared, now fork(), close file descriptors and execv()
1408 // If the fork failed, childpid is -1. So print out an error message.
1409 if (_childpid
== -1) {
1410 gnash::log_error("fork() failed: %s", strerror(errno
));
1411 return NPERR_OUT_OF_MEMORY_ERROR
;
1414 // If we are the parent and fork() worked, childpid is a positive integer.
1415 if (_childpid
> 0) {
1416 // Close the child's end of the pipes.
1417 int fdstoclose
[] = {p2c_controlpipe
[0], p2c_pipe
[0], c2p_pipe
[1]};
1418 int num_failed
= std::count_if(fdstoclose
, fdstoclose
+3, close
);
1420 if (num_failed
> 0) {
1421 // this is not really a fatal error, so continue best as we can
1422 gnash::log_error("%d fds failed to close: %s (error ignored).",
1423 num_failed
, strerror(errno
));
1426 gnash::log_debug("Forked successfully, child process PID is %d",
1429 if (!cookiefile
.empty()) {
1430 cookiemap
.insert(std::make_pair(_childpid
, cookiefile
));
1431 gnash::log_debug("Pid %d associated with cookiefile %s",
1432 _childpid
, cookiefile
);
1435 setupIOChannel(c2p_pipe
[0], (GIOFunc
)handlePlayerRequestsWrapper
,
1436 (GIOCondition
)(G_IO_IN
|G_IO_HUP
));
1438 setupIOChannel(p2c_controlpipe
[1], remove_handler
, G_IO_HUP
);
1440 return NPERR_NO_ERROR
;
1443 // This is the child scope.
1445 // FF3 uses jemalloc and it has problems after the fork(), do NOT
1446 // use memory functions (malloc()/free()/new/delete) after the fork()
1447 // in the child thread process
1448 ret
= close (p2c_pipe
[1]);
1450 // close standard input and direct read-fd1 to standard input
1451 ret
= dup2 (p2c_pipe
[0], fileno(stdin
));
1454 gnash::log_error("dup2() failed: %s", strerror(errno
));
1457 // Close all of the browser's file descriptors that we just inherited
1458 // (including p2c_pipe[0] that we just dup'd to fd 0), but obviously
1459 // not the file descriptors that we want the child to use.
1460 int dontclose
[] = {c2p_pipe
[1], c2p_pipe
[0], p2c_controlpipe
[0]};
1461 close_fds(dontclose
);
1463 // Start the desired executable and go away.
1465 gnash::log_debug("Starting process: %s", boost::algorithm::join(arg_vec
, " "));
1469 execv(args
[0], const_cast<char**>(&args
.front()));
1471 // if execv returns, an error has occurred.
1472 perror("executing standalone gnash");
1482 nsPluginInstance::getCurrentPageURL() const
1485 // window.document.baseURI
1488 // window.document.location.href
1490 return getDocumentProp("baseURI");
1494 processLog_error(const boost::format
& fmt
)
1496 std::cerr
<< "ERROR: " << fmt
.str() << std::endl
;
1499 #if GNASH_PLUGIN_DEBUG > 1
1501 processLog_debug(const boost::format
& fmt
)
1503 std::cout
<< "DEBUG: " << fmt
.str() << std::endl
;
1507 processLog_trace(const boost::format
& fmt
)
1509 std::cout
<< "TRACE: " << fmt
.str() << std::endl
;
1513 processLog_debug(const boost::format
& /* fmt */)
1514 { /* do nothing */ }
1517 processLog_trace(const boost::format
& /* fmt */)
1518 { /* do nothing */ }
1521 } // end of gnash namespace
1525 // indent-tabs-mode: nil