Update with current status
[gnash.git] / plugin / npapi / plugin.cpp
blob96585e615a45d2c0cf8b674941ccb9aebee4d871
1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
3 // 2016 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 <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>
30 #include <cassert>
31 #include <string>
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>
39 # include <windows.h>
40 #else
41 # include <netinet/in.h>
42 # include <arpa/inet.h>
43 # include <netdb.h>
44 # include <sys/socket.h>
45 #endif
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).
57 // e.g. "8.0 r99."
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>. \
72 <br>\
73 Compatible Shockwave Flash " FLASH_VERSION
75 #include "plugin.h"
76 #include "GnashSystemIOHeaders.h"
77 #include "StringPredicates.h"
78 #include "external.h"
79 #include "callbacks.h"
80 #if NPAPI_VERSION == 190
81 #include "npupp.h"
82 #else
83 #include "npapi.h"
84 #include "npruntime.h"
85 #include "npfunctions.h"
86 #endif
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>
96 #include <csignal>
97 #include <cstdio>
98 #include <cstddef>
99 #include <cstring>
100 #include <sys/types.h>
101 #include <sys/stat.h>
102 #include <sys/wait.h>
103 #include <fcntl.h>
104 #include <cerrno>
105 #include <climits>
106 #include <string>
107 #include <vector>
108 #include <iostream>
109 #include <fstream>
110 #include <sstream>
112 // Macro to prevent repeated logging calls for the same
113 // event
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;
125 namespace gnash {
126 NPBool plugInitialized = FALSE;
127 std::string cookiefile;
130 /// \brief Return the MIME Type description for this plugin.
131 #ifdef NPAPI_CONST
132 const
133 #endif
134 char*
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();
147 static const char*
148 getPluginDescription()
150 static const char* desc = nullptr;
151 if (!desc) {
152 desc = std::getenv("GNASH_PLUGIN_DESCRIPTION");
153 if (desc == nullptr) desc = PLUGIN_DESCRIPTION;
155 return desc;
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;
164 #ifdef HAVE_MKSTEMPS
165 fd = mkstemps (mksTemplate, suffix);
166 #else
167 if (suffix > 0) {
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());
177 } else {
178 fd = mkstemp (mksTemplate);
180 #endif
181 #if BOOST_VERSION < 104400
182 boost::iostreams::file_descriptor_sink fdsink(fd, true);
183 #else
184 boost::iostreams::file_descriptor_sink fdsink(fd, boost::iostreams::close_handle);
185 #endif
186 return fdsink;
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
198 /// stuff goes.
199 NPError
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;
224 } else {
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);
235 } else {
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") ) {
248 waitforgdb = true;
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");
266 #endif
268 const char *home = nullptr;
269 #if defined(__amigaos4__)
270 //on AmigaOS we have a GNASH: assign that point to program dir
271 home = "/gnash";
272 #elif defined(__HAIKU__)
273 BPath bp;
274 if (B_OK != find_directory(B_USER_SETTINGS_DIRECTORY, &bp))
276 std::cerr << "Failed to find user settings directory" << std::endl;
277 } else {
278 bp.Append("Gnash");
279 home = bp.Path();
281 #else
282 home = std::getenv("HOME");
283 #endif
284 if ( home ) {
285 newGnashRc.append(":");
286 newGnashRc.append(home);
287 #ifdef __HAIKU__
288 newGnashRc.append("/gnashpluginrc");
289 #else
290 newGnashRc.append("/.gnashpluginrc");
291 #endif
292 } else {
293 gnash::log_error("WARNING: NPAPI plugin could not find user home dir");
296 const char *gnashrc = std::getenv("GNASHRC");
297 if ( 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");
304 } else {
305 gnash::log_debug("NOTE: NPAPI plugin set GNASHRC to %d", newGnashRc);
308 /* Success */
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.
321 void
322 NS_PluginShutdown()
324 #if 0
325 if (!plugInitialized) {
326 gnash::log_debug("Plugin already shut down");
327 return;
330 plugInitialized = FALSE;
331 #endif
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...
339 NPError
340 NS_PluginGetValue(NPPVariable aVariable, void *aValue)
342 NPError err = NPERR_NO_ERROR;
344 switch (aVariable) {
345 case NPPVpluginNameString:
346 *static_cast<const char **> (aValue) = PLUGIN_NAME;
347 break;
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();
355 break;
357 case NPPVpluginWindowBool:
358 break;
360 case NPPVpluginTimerInterval:
361 break;
363 case NPPVpluginKeepLibraryInMemory:
364 break;
366 case NPPVpluginNeedsXEmbed:
367 #ifdef HAVE_GTK2
368 *static_cast<NPBool *>(aValue) = TRUE;
369 #else
370 *static_cast<NPBool *>(aValue) = FALSE;
371 #endif
372 break;
374 case NPPVpluginScriptableNPObject:
375 break;
377 #if NPAPI_VERSION != 190
378 case NPPVpluginUrlRequestsDisplayedBool:
379 break;
380 case NPPVpluginWantsAllNetworkStreams:
381 break;
382 #endif
384 default:
385 err = NPERR_INVALID_PARAM;
386 break;
388 return err;
391 /// \brief construct our plugin instance object
393 /// This instantiates a new object via a C++ function used by the
394 /// browser.
395 nsPluginInstanceBase *
396 NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
398 // gnash::log_debug(__PRETTY_FUNCTION__);
400 if(!aCreateDataStruct) {
401 return nullptr;
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
410 /// browser.
411 void
412 NS_DestroyPluginInstance(nsPluginInstanceBase* aPlugin)
414 delete static_cast<gnash::nsPluginInstance *> (aPlugin);
417 namespace gnash {
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),
434 _window(0),
435 _width(0),
436 _height(0),
437 _streamfd(-1),
438 _childpid(0),
439 _filefd(-1),
440 _name(),
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;
449 if (data->argn[i]) {
450 name = data->argn[i];
453 if (data->argv[i]) {
454 val = data->argv[i];
457 if (noCaseCompare(name, "name")) {
458 _name = val;
461 _params[name] = val;
464 if (HasScripting()) {
465 _scriptObject = (GnashPluginScriptObject*) NPN_CreateObject( _instance,
466 GnashPluginScriptObject::marshalGetNPClass());
469 return;
472 gboolean
473 cleanup_childpid(gpointer data)
475 int* pid = static_cast<int*>(data);
477 int status;
478 int rv = waitpid(*pid, &status, WNOHANG);
480 if (rv <= 0) {
481 // The child process has not exited; it may be deadlocked. Kill it.
483 kill(*pid, SIGKILL);
484 waitpid(*pid, &status, 0);
487 gnash::log_debug("Child process exited with status %s", status);
489 delete pid;
491 return FALSE;
494 /// \brief Destructor
495 nsPluginInstance::~nsPluginInstance()
497 // gnash::log_debug("plugin instance destruction");
499 if (_scriptObject) {
500 NPN_ReleaseObject(_scriptObject);
503 do { } while (g_source_remove_by_user_data(this));
505 if (_childpid > 0) {
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.
513 int status;
514 int rv = waitpid(_childpid, &status, WNOHANG);
516 if (rv <= 0) {
517 int* pid = new int(_childpid);
518 gnashSleep(1000);
519 cleanup_childpid(pid);
520 } else {
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);
533 } else {
534 gnash::log_debug("Cookiefile %s removed [pid %d]",
535 it->second, _childpid);
537 cookiemap.erase(it);
541 _childpid = 0;
544 /// \brief Initialize an instance of the plugin object
545 ///
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.
549 NPBool
550 nsPluginInstance::init(NPWindow* aWindow)
552 if(!aWindow) {
553 gnash::log_error("%s: ERROR: Window handle was bogus!", __PRETTY_FUNCTION__);
554 return FALSE;
555 } else {
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;
563 #endif
566 return TRUE;
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
573 /// goes.
574 void
575 nsPluginInstance::shut()
577 gnash::log_debug("Gnash plugin shutting down");
579 if (_streamfd != -1) {
580 if (close(_streamfd) == -1) {
581 perror("closing _streamfd");
582 } else {
583 _streamfd = -1;
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
594 /// setup here.
595 NPError
596 nsPluginInstance::SetWindow(NPWindow* aWindow)
598 if(!aWindow) {
599 gnash::log_error(std::string(__FUNCTION__) + ": ERROR: Window handle was bogus!");
600 return NPERR_INVALID_PARAM;
601 #if 0
602 } else {
603 gnash::log_debug("%s: X origin = %d, Y Origin = %d, Width = %d,"
604 " Height = %d, WindowID = %p, this = %p",
605 __FUNCTION__,
606 aWindow->x, aWindow->y, aWindow->width, aWindow->height,
607 aWindow->window, this);
608 #endif
611 if (_window) {
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()) {
623 return startProc();
626 return NPERR_NO_ERROR;
629 NPError
630 nsPluginInstance::GetValue(NPPVariable aVariable, void *aValue)
633 if (aVariable == NPPVpluginScriptableNPObject) {
634 if (_scriptObject) {
635 void **v = (void **)aValue;
636 NPN_RetainObject(_scriptObject);
637 *v = _scriptObject;
638 } else {
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
651 /// to play.
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"
657 NPError
658 nsPluginInstance::NewStream(NPMIMEType /*type*/, NPStream* stream,
659 NPBool /*seekable*/, uint16_t* /*stype*/)
661 // gnash::log_debug("%s: %x", __PRETTY_FUNCTION__, (void *)this);
663 if (_childpid) {
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) {
673 return startProc();
676 return NPERR_NO_ERROR;
679 /// \brief Destroy the data stream we've been reading.
680 NPError
681 nsPluginInstance::DestroyStream(NPStream* /*stream*/, NPError /*reason*/)
683 if (_streamfd != -1) {
684 if (close(_streamfd) == -1) {
685 perror("closing _streamfd");
686 } else {
687 _streamfd = -1;
691 return NPERR_NO_ERROR;
694 /// \brief Return how many bytes we can read into the buffer
695 int32_t
696 nsPluginInstance::WriteReady(NPStream* /* stream */ )
698 //gnash::log_debug("Stream for %s is ready", stream->url);
699 if ( _streamfd != -1 ) {
700 return 1024;
701 } else {
702 return 0;
706 /// \brief Read the data stream from Mozilla/Firefox
708 int32_t
709 nsPluginInstance::Write(NPStream* /*stream*/, int32_t /*offset*/, int32_t len,
710 void* buffer)
712 int written = write(_streamfd, buffer, len);
713 return written;
716 bool
717 nsPluginInstance::handlePlayerRequestsWrapper(GIOChannel* iochan,
718 GIOCondition cond, nsPluginInstance* plugin)
720 return plugin->handlePlayerRequests(iochan, cond);
723 bool
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");
730 return false;
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];
741 do {
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);
748 switch (status) {
749 case G_IO_STATUS_ERROR:
750 gnash::log_error("error reading request line: %s",
751 error ? error->message : "unspecified error");
752 g_error_free(error);
753 return false;
754 case G_IO_STATUS_EOF:
755 gnash::log_error("EOF (error: %s)",
756 error ? error->message : "unspecified error");
757 g_error_free(error);
758 return false;
759 case G_IO_STATUS_AGAIN:
760 gnash::log_debug("read again");
761 continue;
762 case G_IO_STATUS_NORMAL:
763 // process request
764 _requestbuf.append(buffer, buffer+bytes_read);
765 #if 0
766 gnash::log_debug("Normal read: %s", std::string(buffer, buffer+bytes_read));
767 #endif
768 break;
769 default:
770 gnash::log_error("Abnormal status!");
771 return false;
773 } while (g_io_channel_get_buffer_condition(iochan) & G_IO_IN);
775 // process request..
776 processPlayerRequest();
778 return true;
781 // This GIOFunc handler removes the source from the mainloop.
782 gboolean
783 remove_handler(GIOChannel*, GIOCondition, gpointer)
785 return FALSE;
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.
791 bool
792 nsPluginInstance::processPlayerRequest()
794 #if 0
795 gnash::log_debug(__PRETTY_FUNCTION__);
797 log_debug("SCRIPT OBJECT %d: %x", __LINE__, this->getScriptObject());
798 #endif
800 if ( _requestbuf.size() < 4 ) {
801 gnash::log_error("Invalid player request (too short): %s", _requestbuf);
802 return false;
806 std::string& packet = _requestbuf;
807 do {
808 boost::trim_left(packet);
809 if (packet.empty()) {
810 return false;
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.");
818 return false;
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());
829 if (!invoke) {
830 log_error("Failed to parse invoke message: %s", msg);
831 return false;
834 if (!invoke->name.empty()) {
835 gnash::log_debug("Requested method is: %s", invoke->name);
836 } else {
837 gnash::log_error("Invoke request missing a name to invoke.");
838 continue;
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());
847 #if 0
848 gnash::log_debug("Got a getURL() request: %s", url);
849 #endif
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
853 // _blank or _self.
854 std::string target;
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.
858 std::string data;
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());
872 if (op == "GET") {
873 gnash::log_debug("Asked to getURL '%s' in target %s", url,
874 target);
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,
878 data);
879 NPN_PostURL(_instance, url.c_str(), target.c_str(), data.size(),
880 data.c_str(), false);
881 } else {
882 log_error("Unexpected op in getURL (expected POST or GET).");
885 continue;
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)",
900 jsurl.str(), tgt);
902 NPN_GetURL(_instance, jsurl.str().c_str(), tgt);
903 continue;
904 } else if (invoke->name == "addMethod") {
906 assert(!invoke->args.empty());
908 if (!HasScripting()) {
909 LOG_ONCE(log_debug("Ignoring addMethod, no scripting."));
910 continue;
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);
920 continue;
923 if (!HasScripting()) {
924 LOG_ONCE(log_debug("Ignoring invoke, no scripting."));
925 continue;
928 NPVariant result;
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(),
947 count, &result);
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.");
958 return false;
960 } while (!packet.empty());
962 return true;
965 std::string
966 getGnashExecutable()
968 std::string procname;
969 bool process_found = false;
970 struct stat procstats;
972 char *gnash_env = std::getenv("GNASH_PLAYER");
974 if (gnash_env) {
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: ");
979 return "";
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);
994 return "";
997 return procname;
1000 void
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) {
1006 return;
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);
1014 return;
1016 #endif
1017 boost::iostreams::stream<boost::iostreams::file_descriptor_sink>
1018 saLauncher (fdsink);
1020 if (!saLauncher) {
1021 gnash::log_error("Failed to open new file for standalone launcher: %s", debugname);
1022 return;
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, "'", "'\\''")
1040 << "="
1041 << boost::algorithm::replace_all_copy(val, "'", "'\\''")
1042 << "' ";
1045 saLauncher << "'" << swf_url << "' "
1046 << "$@" // allow caller to pass any additional argument
1047 << std::endl;
1049 saLauncher.close();
1050 fdsink.close();
1051 #endif
1054 std::string
1055 nsPluginInstance::getDocumentProp(const std::string& propname) const
1057 std::string rv;
1059 if (!HasScripting()) {
1060 LOG_ONCE( gnash::log_debug("Browser doesn't support scripting") );
1061 return rv;
1064 NPObject* windowobj;
1065 NPError err = NPN_GetValue(_instance, NPNVWindowNPObject, &windowobj);
1066 if (err != NPERR_NO_ERROR || !windowobj) {
1067 return rv;
1070 std::shared_ptr<NPObject> window_obj(windowobj, NPN_ReleaseObject);
1072 NPIdentifier doc_id = NPN_GetStringIdentifier("document");
1074 NPVariant docvar;
1075 if(! NPN_GetProperty(_instance, windowobj, doc_id, &docvar) ) {
1076 return rv;
1079 std::shared_ptr<NPVariant> doc_var(&docvar, NPN_ReleaseVariantValue);
1081 if (!NPVARIANT_IS_OBJECT(docvar)) {
1082 return rv;
1085 NPObject* doc_obj = NPVARIANT_TO_OBJECT(docvar);
1087 NPIdentifier prop_id = NPN_GetStringIdentifier(propname.c_str());
1089 NPVariant propvar;
1090 if (!NPN_GetProperty(_instance, doc_obj, prop_id, &propvar)) {
1091 return rv;
1094 std::shared_ptr<NPVariant> prop_var(&propvar, NPN_ReleaseVariantValue);
1096 if (!NPVARIANT_IS_STRING(propvar)) {
1097 return rv;
1100 const NPString& prop_str = NPVARIANT_TO_STRING(propvar);
1102 rv = NPStringToString(prop_str);
1103 return rv;
1108 void
1109 nsPluginInstance::setupCookies(const std::string& pageurl)
1111 // Cookie appear to drop anything past the domain, so we strip
1112 // that off.
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(),
1126 &cookie, &length);
1127 } else {
1128 LOG_ONCE( gnash::log_debug("Browser doesn't support getvalueforurl") );
1130 #endif
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");
1139 if (cookie) {
1140 ncookie.assign(cookie, length);
1141 NPN_MemFree(cookie);
1144 if (ncookie.empty()) {
1145 gnash::log_debug("No stored Cookie for %s", url);
1146 cookiefile.clear();
1147 return;
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);
1156 return;
1158 #endif
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();
1181 fdsink.close();
1182 gnash::log_debug("Cookiefile is %s", cookiefile);
1185 void
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;
1192 #endif
1194 char *proxy = nullptr;
1195 uint32_t length = 0;
1196 #if NPAPI_VERSION != 190
1197 NPN_GetValueForURL(_instance, NPNURLVProxy, url.c_str(),
1198 &proxy, &length);
1199 #endif
1200 if (!proxy) {
1201 gnash::log_debug("No proxy setting for %s", url);
1202 return;
1205 std::string nproxy (proxy, length);
1206 NPN_MemFree(proxy);
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" ) {
1214 // no proxy
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");
1221 if (!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) {
1224 gnash::log_error(
1225 "Couldn't set environment variable http_proxy to %s",
1226 parts[1].c_str());
1228 } else {
1229 gnash::log_debug("http_proxy already set to %s", http_proxy);
1232 else {
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();
1244 if (cmd.empty()) {
1245 gnash::log_error("Failed to locate the Gnash executable!");
1246 return arg_vec;
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!");
1256 } else {
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
1269 pars << " -vv ";
1270 #endif
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);
1294 return arg_vec;
1297 template <std::size_t N>
1298 void
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) {
1308 continue;
1310 if (close(anfd) < 0) {
1311 numfailed++;
1312 } else {
1313 numfailed = 0;
1314 closed++;
1317 gnash::log_debug("Closed %d files.", closed);
1320 void
1321 wait_for_gdb()
1323 if (!waitforgdb) {
1324 return;
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."
1334 << std::endl
1335 << " Do a \"set variable waitforgdb=$false\" to continue"
1336 << std::endl << std::endl;
1338 while (waitforgdb) {
1339 sleep(1);
1343 void
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);
1356 NPError
1357 nsPluginInstance::startProc()
1360 int p2c_pipe[2];
1361 int c2p_pipe[2];
1362 int p2c_controlpipe[2];
1364 int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, p2c_pipe);
1365 if (ret == -1) {
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);
1372 if (ret == -1) {
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);
1378 if (ret == -1) {
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()
1406 _childpid = fork();
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",
1427 _childpid);
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));
1453 if (ret == -1) {
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, " "));
1467 wait_for_gdb();
1469 execv(args[0], const_cast<char**>(&args.front()));
1471 // if execv returns, an error has occurred.
1472 perror("executing standalone gnash");
1474 exit (-1);
1481 std::string
1482 nsPluginInstance::getCurrentPageURL() const
1484 // Return:
1485 // window.document.baseURI
1487 // Was (bogus):
1488 // window.document.location.href
1490 return getDocumentProp("baseURI");
1493 void
1494 processLog_error(const boost::format& fmt)
1496 std::cerr << "ERROR: " << fmt.str() << std::endl;
1499 #if GNASH_PLUGIN_DEBUG > 1
1500 void
1501 processLog_debug(const boost::format& fmt)
1503 std::cout << "DEBUG: " << fmt.str() << std::endl;
1506 void
1507 processLog_trace(const boost::format& fmt)
1509 std::cout << "TRACE: " << fmt.str() << std::endl;
1511 #else
1512 void
1513 processLog_debug(const boost::format& /* fmt */)
1514 { /* do nothing */ }
1516 void
1517 processLog_trace(const boost::format& /* fmt */)
1518 { /* do nothing */ }
1519 #endif
1521 } // end of gnash namespace
1523 // Local Variables:
1524 // mode: C++
1525 // indent-tabs-mode: nil
1526 // End: