dolphin/nsplugins/viewer/nsplugin.cpp
2011-07-31 13:33:56 +02:00

2019 lines
57 KiB
C++

/*
This is an encapsulation of the Netscape plugin API.
Copyright (c) 2000 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
Stefan Schimanski <1Stein@gmx.de>
2003-2005 George Staikos <staikos@kde.org>
2007, 2008, 2010 Maksim Orlovich <maksim@kde.org>
2006, 2007, 2008 Apple Inc.
2008 Collabora, Ltd.
2008 Sebastian Sauer <mail@dipe.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "nsplugin.h"
#include "pluginhost_xembed.h"
#include "pluginhost_xt.h"
#include "resolve.h"
#include "classadaptor.h"
#include "instanceadaptor.h"
#include "vieweradaptor.h"
#include "scripting.h"
#include "nsplugins_callback_interface.h"
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <QDir>
#include <QFile>
#include <QTimer>
#include <QApplication>
#ifdef Bool
#undef Bool
#endif
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kio/netaccess.h>
#include <kprotocolmanager.h>
#include <klibrary.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <ktemporaryfile.h>
#include <kurl.h>
#include <QX11Info>
#include <QProcess>
#include <X11/Intrinsic.h>
#include <X11/Composite.h>
#include <X11/Constraint.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
// provide these symbols when compiling with gcc 3.x
#if defined __GNUC__ && defined __GNUC_MINOR__
# define KDE_GNUC_PREREQ(maj, min) \
((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
#else
# define KDE_GNUC_PREREQ(maj, min) 0
#endif
#if defined(__GNUC__) && KDE_GNUC_PREREQ(3,0)
extern "C" void* __builtin_new(size_t s)
{
return operator new(s);
}
extern "C" void __builtin_delete(void* p)
{
operator delete(p);
}
extern "C" void* __builtin_vec_new(size_t s)
{
return operator new[](s);
}
extern "C" void __builtin_vec_delete(void* p)
{
operator delete[](p);
}
extern "C" void __pure_virtual()
{
abort();
}
#endif
// The NSPluginInstance is always the ndata of the instance. Sometimes, plug-ins will call an instance-specific function
// with a NULL instance. To workaround this, we remember the last NSPluginInstance produced with the
// NSPluginClass::newInstance() method. This specifically works around Flash and Shockwave which do e.g. call NPN_Useragent
// with a NULL instance When we call NPP_New.
// At the moment we do setLastPluginInstance() only if the NSPluginInstance is created. Probably it would be more logical
// to do that more often to prevent some wired situations where we may end with the wrong NSPluginInstance for a plugin.
NSPluginInstance* NSPluginInstance::s_lastPluginInstance = 0;
NSPluginInstance* NSPluginInstance::lastPluginInstance() { return s_lastPluginInstance; }
void NSPluginInstance::setLastPluginInstance(NSPluginInstance* inst) { s_lastPluginInstance = inst; }
static NSPluginInstance* pluginViewForInstance(NPP instance)
{
if (instance && instance->ndata)
return static_cast<NSPluginInstance*>(instance->ndata);
return NSPluginInstance::lastPluginInstance();
}
// server side functions -----------------------------------------------------
// allocate memory
namespace kdeNsPluginViewer {
void *g_NPN_MemAlloc(uint32_t size)
{
void *mem = ::malloc(size);
//kDebug(1431) << "g_NPN_MemAlloc(), size=" << size << " allocated at " << mem;
return mem;
}
// free memory
void g_NPN_MemFree(void *ptr)
{
//kDebug(1431) << "g_NPN_MemFree() at " << ptr;
if (ptr)
::free(ptr);
}
}
uint32_t g_NPN_MemFlush(uint32_t size)
{
Q_UNUSED(size);
//kDebug(1431) << "g_NPN_MemFlush()";
// MAC OS only.. we don't use this
return 0;
}
// redraw
void g_NPN_ForceRedraw(NPP /*instance*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api3.html#999401
// FIXME
kDebug(1431) << "g_NPN_ForceRedraw() [unimplemented]";
}
// invalidate rect
void g_NPN_InvalidateRect(NPP /*instance*/, NPRect* /*invalidRect*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api7.html#999503
// FIXME
kDebug(1431) << "g_NPN_InvalidateRect() [unimplemented]";
}
// invalidate region
void g_NPN_InvalidateRegion(NPP /*instance*/, NPRegion /*invalidRegion*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api8.html#999528
// FIXME
kDebug(1431) << "g_NPN_InvalidateRegion() [unimplemented]";
}
// get value
NPError g_NPN_GetValue(NPP instance, NPNVariable variable, void *value)
{
kDebug(1431) << "g_NPN_GetValue(), variable=" << static_cast<int>(variable);
NSPluginInstance* inst = pluginViewForInstance(instance);
switch (variable)
{
case NPNVxDisplay:
*(void**)value = QX11Info::display();
return NPERR_NO_ERROR;
case NPNVxtAppContext:
*(void**)value = XtDisplayToApplicationContext(QX11Info::display());
return NPERR_NO_ERROR;
case NPNVjavascriptEnabledBool:
*(bool*)value = true;
return NPERR_NO_ERROR;
case NPNVasdEnabledBool:
// SmartUpdate - we don't do this
*(bool*)value = false;
return NPERR_NO_ERROR;
case NPNVisOfflineBool:
// Offline browsing - no thanks
*(bool*)value = false;
return NPERR_NO_ERROR;
case NPNVSupportsXEmbedBool:
// ### may depend on event loop setting
*(bool*)value = true;
return NPERR_NO_ERROR;
case NPNVToolkit:
// This is messy. OSS things want to see "Gtk2" here;
// but commercial flash works better if we return something else.
// So we return a KHTML classic, since we can work with
// the community members far easier.
*(NPNToolkitType*)value = (NPNToolkitType)0xFEEDABEE;
return NPERR_NO_ERROR;
case NPNVWindowNPObject:
if (inst && inst->scripting()) {
*(NPObject**)value = inst->scripting()->acquireWindow();
return NPERR_NO_ERROR;
} else {
kDebug(1431) << "script object queried, but no scripting active";
return NPERR_INVALID_PARAM;
}
#if 0
case NPNVPluginElementNPObject:
if (inst && inst->scripting()) {
*(NPObject**)value = inst->scripting()->acquirePluginElement();
return NPERR_NO_ERROR;
} else {
kDebug(1431) << "script object queried, but no scripting active";
return NPERR_INVALID_PARAM;
}
#endif
default:
kDebug(1431) << "g_NPN_GetValue(), [unimplemented] variable=" << variable;
return NPERR_INVALID_PARAM;
}
}
NPError g_NPN_DestroyStream(NPP instance, NPStream* stream,
NPReason reason)
{
// FIXME: is this correct? I imagine it is not. (GS)
kDebug(1431) << "g_NPN_DestroyStream()";
NSPluginInstance *inst = pluginViewForInstance(instance);
inst->streamFinished( (NSPluginStream *)stream->ndata );
switch (reason) {
case NPRES_DONE:
return NPERR_NO_ERROR;
case NPRES_USER_BREAK:
// FIXME: notify the user
case NPRES_NETWORK_ERR:
// FIXME: notify the user
default:
return NPERR_GENERIC_ERROR;
}
}
NPError g_NPN_RequestRead(NPStream* /*stream*/, NPByteRange* /*rangeList*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api16.html#999734
kDebug(1431) << "g_NPN_RequestRead() [unimplemented]";
// FIXME
return NPERR_GENERIC_ERROR;
}
NPError g_NPN_NewStream(NPP /*instance*/, NPMIMEType /*type*/,
const char* /*target*/, NPStream** /*stream*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api12.html#999628
kDebug(1431) << "g_NPN_NewStream() [unimplemented]";
// FIXME
// This creates a stream from the plugin to the browser of type "type" to
// display in "target"
return NPERR_GENERIC_ERROR;
}
int32_t g_NPN_Write(NPP /*instance*/, NPStream* /*stream*/, int32_t /*len*/, void* /*buf*/)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api21.html#999859
kDebug(1431) << "g_NPN_Write() [unimplemented]";
// FIXME
return 0;
}
// URL functions
NPError g_NPN_GetURL(NPP instance, const char *url, const char *target)
{
kDebug(1431) << "g_NPN_GetURL: url=" << url << " target=" << target;
NSPluginInstance *inst = pluginViewForInstance(instance);
if (inst) {
inst->requestURL( QString::fromLatin1(url), QString(),
QString::fromLatin1(target), 0 );
}
return NPERR_NO_ERROR;
}
NPError g_NPN_GetURLNotify(NPP instance, const char *url, const char *target,
void* notifyData)
{
kDebug(1431) << "g_NPN_GetURLNotify: url=" << url << " target=" << target << " inst=" << (void*)instance;
NSPluginInstance *inst = pluginViewForInstance(instance);
if (inst) {
kDebug(1431) << "g_NPN_GetURLNotify: ndata=" << (void*)inst;
inst->requestURL( QString::fromLatin1(url), QString(),
QString::fromLatin1(target), notifyData, true );
}
return NPERR_NO_ERROR;
}
NPError g_NPN_PostURLNotify(NPP instance, const char* url, const char* target,
uint32_t len, const char* buf, NPBool file, void* notifyData)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api14.html
kDebug(1431) << "g_NPN_PostURLNotify() [incomplete]";
kDebug(1431) << "url=[" << url << "] target=[" << target << "]";
QByteArray postdata;
KParts::OpenUrlArguments args;
KParts::BrowserArguments browserArgs;
if (len == 0) {
return NPERR_NO_DATA;
}
if (file) { // buf is a filename
QFile f(buf);
if (!f.open(QIODevice::ReadOnly)) {
return NPERR_FILE_NOT_FOUND;
}
// FIXME: this will not work because we need to strip the header out!
postdata = f.readAll();
f.close();
} else { // buf is raw data
// First strip out the header
const char *previousStart = buf;
uint32_t l;
bool previousCR = true;
for (l = 1;; ++l) {
if (l == len) {
break;
}
if (buf[l-1] == '\n' || (previousCR && buf[l-1] == '\r')) {
if (previousCR) { // header is done!
if ((buf[l-1] == '\r' && buf[l] == '\n') ||
(buf[l-1] == '\n' && buf[l] == '\r'))
l++;
l++;
previousStart = &buf[l-1];
break;
}
QString thisLine = QString::fromLatin1(previousStart, &buf[l-1] - previousStart).trimmed();
previousStart = &buf[l];
previousCR = true;
kDebug(1431) << "Found header line: [" << thisLine << "]";
if (thisLine.startsWith("Content-Type: ")) {
browserArgs.setContentType(thisLine);
}
} else {
previousCR = false;
}
}
postdata = QByteArray(previousStart, len - l + 1);
}
kDebug(1431) << "Post data: " << postdata.size() << " bytes";
#if 0
QFile f("/tmp/nspostdata");
f.open(QIODevice::WriteOnly);
f.write(postdata);
f.close();
#endif
if (!target || !*target) {
// Send the results of the post to the plugin
// (works by default)
} else if (!strcmp(target, "_current") || !strcmp(target, "_self") ||
!strcmp(target, "_top")) {
// Unload the plugin, put the results in the frame/window that the
// plugin was loaded in
// FIXME
} else if (!strcmp(target, "_new") || !strcmp(target, "_blank")){
// Open a new browser window and write the results there
// FIXME
} else {
// Write the results to the specified frame
// FIXME
}
NSPluginInstance *inst = pluginViewForInstance(instance);
if (inst && !inst->normalizedURL(QString::fromLatin1(url)).isNull()) {
inst->postURL( QString::fromLatin1(url), postdata, browserArgs.contentType(),
QString::fromLatin1(target), notifyData, args, browserArgs, true );
} else {
// Unsupported / insecure
return NPERR_INVALID_URL;
}
return NPERR_NO_ERROR;
}
NPError g_NPN_PostURL(NPP instance, const char* url, const char* target,
uint32_t len, const char* buf, NPBool file)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api13.html
kDebug(1431) << "g_NPN_PostURL()";
kDebug(1431) << "url=[" << url << "] target=[" << target << "]";
QByteArray postdata;
KParts::OpenUrlArguments args;
KParts::BrowserArguments browserArgs;
if (len == 0) {
return NPERR_NO_DATA;
}
if (file) { // buf is a filename
QFile f(buf);
if (!f.open(QIODevice::ReadOnly)) {
return NPERR_FILE_NOT_FOUND;
}
// FIXME: this will not work because we need to strip the header out!
postdata = f.readAll();
f.close();
} else { // buf is raw data
// First strip out the header
const char *previousStart = buf;
uint32_t l;
bool previousCR = true;
for (l = 1;; ++l) {
if (l == len) {
break;
}
if (buf[l-1] == '\n' || (previousCR && buf[l-1] == '\r')) {
if (previousCR) { // header is done!
if ((buf[l-1] == '\r' && buf[l] == '\n') ||
(buf[l-1] == '\n' && buf[l] == '\r'))
l++;
l++;
previousStart = &buf[l-1];
break;
}
QString thisLine = QString::fromLatin1(previousStart, &buf[l-1] - previousStart).trimmed();
previousStart = &buf[l];
previousCR = true;
kDebug(1431) << "Found header line: [" << thisLine << "]";
if (thisLine.startsWith("Content-Type: ")) {
browserArgs.setContentType(thisLine);
}
} else {
previousCR = false;
}
}
postdata = QByteArray(previousStart, len - l + 1);
}
kDebug(1431) << "Post data: " << postdata.size() << " bytes";
#if 0
QFile f("/tmp/nspostdata");
f.open(QIODevice::WriteOnly);
f.write(postdata);
f.close();
#endif
if (!target || !*target) {
// Send the results of the post to the plugin
// (works by default)
} else if (!strcmp(target, "_current") || !strcmp(target, "_self") ||
!strcmp(target, "_top")) {
// Unload the plugin, put the results in the frame/window that the
// plugin was loaded in
// FIXME
} else if (!strcmp(target, "_new") || !strcmp(target, "_blank")){
// Open a new browser window and write the results there
// FIXME
} else {
// Write the results to the specified frame
// FIXME
}
NSPluginInstance *inst = pluginViewForInstance(instance);
if (inst && !inst->normalizedURL(QString::fromLatin1(url)).isNull()) {
inst->postURL( QString::fromLatin1(url), postdata, browserArgs.contentType(),
QString::fromLatin1(target), 0L, args, browserArgs, false );
} else {
// Unsupported / insecure
return NPERR_INVALID_URL;
}
return NPERR_NO_ERROR;
}
// display status message
void g_NPN_Status(NPP instance, const char *message)
{
kDebug(1431) << "g_NPN_Status(): " << message;
if (!instance)
return;
// turn into an instance signal
NSPluginInstance *inst = pluginViewForInstance(instance);
inst->emitStatus(message);
}
static QByteArray uaStore;
static QByteArray uaEmpty("Gecko");
// inquire user agent
const char *g_NPN_UserAgent(NPP instance)
{
if (!instance)
return uaEmpty.data();
if (uaStore.isEmpty()) {
KProtocolManager kpm;
QString agent = kpm.userAgentForHost("nspluginviewer");
uaStore = agent.toLatin1();
}
kDebug(1431) << "g_NPN_UserAgent() = " << uaStore;
return uaStore.data();
}
// inquire version information
void g_NPN_Version(int *plugin_major, int *plugin_minor, int *browser_major, int *browser_minor)
{
kDebug(1431) << "g_NPN_Version()";
// FIXME: Use the sensible values
*browser_major = NP_VERSION_MAJOR;
*browser_minor = NP_VERSION_MINOR;
*plugin_major = NP_VERSION_MAJOR;
*plugin_minor = NP_VERSION_MINOR;
}
void g_NPN_ReloadPlugins(NPBool reloadPages)
{
// http://devedge.netscape.com/library/manuals/2002/plugin/1.0/npn_api15.html#999713
kDebug(1431) << "g_NPN_ReloadPlugins()";
QString prog = KGlobal::dirs()->findExe("nspluginscan");
if (reloadPages) {
// This is the proper way, but it cannot be done because we have no
// handle to the caller! How stupid! We cannot force all konqi windows
// to reload - that would be evil.
//p.start(K3Process::Block);
// Let's only allow the caller to be reloaded, not everything.
//if (_callback)
// _callback->reloadPage();
QProcess::startDetached(prog);
} else {
QProcess::startDetached(prog);
}
}
// JAVA functions
void *g_NPN_GetJavaEnv()
{
kDebug(1431) << "g_NPN_GetJavaEnv() [unimplemented]";
// FIXME - what do these do? I can't find docs, and even Mozilla doesn't
// implement them
return 0;
}
void* g_NPN_GetJavaPeer(NPP /*instance*/)
{
kDebug(1431) << "g_NPN_GetJavaPeer() [unimplemented]";
// FIXME - what do these do? I can't find docs, and even Mozilla doesn't
// implement them
return 0;
}
NPError g_NPN_SetValue(NPP /*instance*/, NPPVariable variable, void* /*value*/)
{
kDebug(1431) << "g_NPN_SetValue() [unimplemented]";
switch (variable) {
case NPPVpluginWindowBool:
// FIXME
// If true, the plugin is windowless. If false, it is in a window.
case NPPVpluginTransparentBool:
// FIXME
// If true, the plugin is displayed transparent
default:
return NPERR_GENERIC_ERROR;
}
}
/**
These two are in the ABI version 16 which we don't claim to support, but
flash uses anyway.
*/
static void g_NPN_PushPopupsEnabledState(NPP /*instance*/, NPBool enabled)
{
kDebug(1431) << "[unimplemented]" << enabled;
}
static void g_NPN_PopPopupsEnabledState(NPP /*instance*/)
{
kDebug(1431) << "[unimplemented]";
}
/******************************************************************/
static int s_instanceCounter = 0;
NSPluginInstance::NSPluginInstance(NPPluginFuncs *pluginFuncs,
KLibrary *handle,
const QString &url, const QString &mimeType,
const QStringList &argn, const QStringList &argv,
const QString &appId, const QString &callbackId,
bool embed,
QObject *parent )
: QObject( parent )
{
// Setup d-bus infrastructure early, will need it for script stuff
kdeNsPluginViewer::initDBusTypes();
// The object name is the dbus object path
(void) new InstanceAdaptor( this );
setObjectName( QLatin1String( "/Instance_" ) + QString::number( ++s_instanceCounter ) );
QDBusConnection::sessionBus().registerObject( objectName(), this );
_callback = new org::kde::nsplugins::CallBack( appId, callbackId, QDBusConnection::sessionBus() );
_embedded = false;
_destroyed = false;
_handle = handle;
_numJSRequests = 0;
_scripting = 0;
// set the current plugin instance
NSPluginInstance::setLastPluginInstance(this);
memcpy(&_pluginFuncs, pluginFuncs, sizeof(_pluginFuncs));
// See want the scripting stuff very early, since newp can use it.
_scripting = new ScriptExportEngine(this);
// copy parameters over, and extract out base url if specified
int argc = argn.count();
char **_argn = new char*[argc];
char **_argv = new char*[argc];
QString baseURL = url;
for (int i=0; i<argc; i++)
{
QByteArray encN = argn[i].toUtf8();
QByteArray encV = argv[i].toUtf8();
_argn[i] = strdup(encN.constData());
_argv[i] = strdup(encV.constData());
if (argn[i] == QLatin1String("__KHTML__PLUGINBASEURL"))
baseURL = argv[i];
if (argn[i] == QLatin1String("__KHTML__PLUGINPAGEURL"))
_pageURL = argv[i];
kDebug(1431) << "argn=" << _argn[i] << " argv=" << _argv[i];
}
// create plugin instance
char* mime = strdup(mimeType.toAscii().constData());
_npp = (NPP)malloc(sizeof(NPP_t)); // I think we should be using
// malloc here, just to be safe,
// since the nsplugin plays with
// this thing
memset(_npp, 0, sizeof(NPP_t));
_npp->ndata = this;
// create actual plugin's instance
NPError error = _pluginFuncs.newp(mime, _npp, embed ? NP_EMBED : NP_FULL,
argc, _argn, _argv, 0);
kDebug(1431) << "NPP_New = " << (int)error;
// free arrays with arguments
delete [] _argn;
delete [] _argv;
// check for error
if ( error!=NPERR_NO_ERROR )
{
::free(_npp);
::free(mime);
kDebug(1431) << "<- newp failed";
_initializedOK = false;
return;
}
_initializedOK = true;
KUrl base(baseURL);
base.setFileName( QString() );
_baseURL = base.url();
_timer = new QTimer( this );
connect( _timer, SIGNAL(timeout()), SLOT(timer()) );
kDebug(1431) << "NSPluginInstance::NSPluginInstance";
kDebug(1431) << "pdata = " << _npp->pdata;
kDebug(1431) << "ndata = " << _npp->ndata;
// now let's see if the plugin offers any scriptable stuff, too.
_scripting->connectToPlugin();
// Create the appropriate host for the plugin type.
_pluginHost = 0;
int result = 0; /* false */
//### iceweasel does something odd here --- it enabled XEmbed for swfdec,
// even though that doesn't provide GetValue at all(!)
if (NPGetValue(NPPVpluginNeedsXEmbed, &result) == NPERR_NO_ERROR && result) {
kDebug(1431) << "plugin reqests XEmbed";
_pluginHost = new PluginHostXEmbed(this);
} else {
kDebug(1431) << "plugin requests Xt";
_pluginHost = new PluginHostXt(this);
}
XSync(QX11Info::display(), false);
}
NSPluginInstance::~NSPluginInstance()
{
kDebug(1431) << "-> ~NSPluginInstance";
destroy();
kDebug(1431) << "<- ~NSPluginInstance";
}
// Note: here we again narrow the types to the same ulong as LiveConnectExtension uses,
// so we don't end up with extra precision floating around
NSLiveConnectResult NSPluginInstance::lcGet(qulonglong objid, const QString& field)
{
NSLiveConnectResult result;
if (_scripting) {
KParts::LiveConnectExtension::Type type;
unsigned long outId;
result.success = _scripting->get(static_cast<unsigned long>(objid), field,
type, outId, result.value);
result.type = type;
result.objid = outId;
}
return result;
}
bool NSPluginInstance::lcPut(qulonglong objid, const QString& field, const QString& value)
{
if (_scripting)
return _scripting->put(static_cast<unsigned long>(objid), field, value);
return false;
}
NSLiveConnectResult NSPluginInstance::lcCall(qulonglong objid, const QString& func,
const QStringList& args)
{
NSLiveConnectResult result;
if (_scripting) {
KParts::LiveConnectExtension::Type type;
unsigned long outId;
result.success = _scripting->call(static_cast<unsigned long>(objid), func, args,
type, outId, result.value);
result.type = type;
result.objid = outId;
}
return result;
}
void NSPluginInstance::lcUnregister(qulonglong objid)
{
if (_scripting)
_scripting->unregister(static_cast<unsigned long>(objid));
}
void NSPluginInstance::destroy()
{
if ( !_destroyed ) {
kDebug(1431) << "delete streams";
qDeleteAll( _waitingRequests );
while ( !_streams.isEmpty() ) {
NSPluginStreamBase *s = _streams.takeFirst();
s->stop();
delete s;
}
kDebug(1431) << "delete tempfiles";
qDeleteAll( _tempFiles );
kDebug(1431) << "delete callbacks";
delete _callback;
_callback = 0;
kDebug(1431) << "delete scripting";
delete _scripting;
_scripting = 0;
if (!_initializedOK) {
_destroyed = true;
return;
}
kDebug(1431) << "destroy plugin";
NPSavedData *saved = 0;
// As of 7/31/01, nsplugin crashes when used with Qt
// linked with libGL if the destroy function is called.
// A patch on that date hacked out the following call.
// On 11/17/01, Jeremy White has reenabled this destroy
// in a an attempt to better understand why this crash
// occurs so that the real problem can be found and solved.
// It's possible that a flaw in the SetWindow call
// caused the crash and it is now fixed.
if ( _pluginFuncs.destroy )
_pluginFuncs.destroy( _npp, &saved );
if (saved && saved->len && saved->buf)
g_NPN_MemFree(saved->buf);
if (saved)
g_NPN_MemFree(saved);
delete _pluginHost;
_pluginHost = 0;
if (_npp) {
::free(_npp); // matched with malloc() in newInstance
}
_destroyed = true;
}
}
void NSPluginInstance::shutdown()
{
NSPluginClass *cls = dynamic_cast<NSPluginClass*>(parent());
//destroy();
if (cls) {
cls->destroyInstance( this );
}
}
bool NSPluginInstance::hasPendingJSRequests() const
{
return _numJSRequests > 0;
}
void NSPluginInstance::timer()
{
if (!_embedded) {
_timer->setSingleShot( true );
_timer->start( 100 );
return;
}
//_streams.clear();
// start queued requests
kDebug(1431) << "looking for waiting requests";
while ( !_waitingRequests.isEmpty() ) {
kDebug(1431) << "request found";
Request req( *_waitingRequests.head() );
delete _waitingRequests.dequeue();
QString url;
// Note: sync javascript: handling with requestURL
// make absolute url
if ( req.url.left(11).toLower()=="javascript:" )
url = req.url;
else if ( KUrl::isRelativeUrl(req.url) ) {
KUrl bu( _baseURL );
KUrl absUrl( bu, req.url );
url = absUrl.url();
} else if ( req.url[0]=='/' && KUrl(_baseURL).hasHost() ) {
KUrl absUrl( _baseURL );
absUrl.setPath( req.url );
url = absUrl.url();
} else
url = req.url;
// non empty target = frame target
if ( !req.target.isEmpty())
{
if (_callback)
{
if ( req.post ) {
_callback->postURL( url, req.target, req.data, req.mime );
} else {
_callback->requestURL( url, req.target );
}
if ( req.notify ) {
NPURLNotify( req.url, NPRES_DONE, req.notify );
}
}
} else {
if (!url.isEmpty())
{
kDebug(1431) << "Starting new stream " << req.url;
if (req.post) {
// create stream
NSPluginStream *s = new NSPluginStream( this );
connect( s, SIGNAL(finished(NSPluginStreamBase*)),
SLOT(streamFinished(NSPluginStreamBase*)) );
_streams.append( s );
kDebug() << "posting to " << url;
emitStatus( i18n("Submitting data to %1", url) );
s->post( url, req.data, req.mime, req.notify, req.args, req.browserArgs );
} else if (url.toLower().startsWith("javascript:")){
if (_callback) {
static int _jsrequestid = 0;
_jsrequests.insert(_jsrequestid, new Request(req));
_callback->evalJavaScript(_jsrequestid++, url.mid(11));
} else {
--_numJSRequests;
kDebug() << "No callback for javascript: url!";
}
} else {
// create stream
NSPluginStream *s = new NSPluginStream( this );
connect( s, SIGNAL(finished(NSPluginStreamBase*)),
SLOT(streamFinished(NSPluginStreamBase*)) );
_streams.append( s );
kDebug() << "getting " << url;
emitStatus( i18n("Requesting %1", url) );
s->get( url, req.mime, req.notify, req.reload );
}
//break;
}
}
}
}
QString NSPluginInstance::normalizedURL(const QString& url) const {
// ### for dfaure: KUrl(KUrl("http://www.youtube.com/?v=JvOSnRD5aNk"), KUrl("javascript:window.location+"__flashplugin_unique__"));
//### hack, prolly evil, etc.
if (url.startsWith("javascript:"))
return url;
KUrl bu( _baseURL );
KUrl inURL(bu, url);
KConfig _cfg( "kcmnspluginrc" );
KConfigGroup cfg(&_cfg, "Misc");
if (!cfg.readEntry("HTTP URLs Only", false) ||
inURL.protocol() == "http" ||
inURL.protocol() == "https" ||
inURL.protocol() == "javascript") {
return inURL.url();
}
// Allow: javascript:, http, https, or no protocol (match loading)
kDebug(1431) << "NSPluginInstance::normalizedURL - I don't think so. http or https only!";
return QString();
}
void NSPluginInstance::requestURL( const QString &url, const QString &mime,
const QString &target, void *notify, bool forceNotify, bool reload )
{
// Generally this should already be done, but let's be safe for now.
QString nurl = normalizedURL(url);
if (nurl.isNull()) {
return;
}
// We dispatch JS events in target for empty target GET only.(see timer());
if (target.isEmpty() && nurl.left(11).toLower()=="javascript:")
++_numJSRequests;
kDebug(1431) << "NSPluginInstance::requestURL url=" << nurl << " target=" << target << " notify=" << notify << "JS jobs now:" << _numJSRequests;
_waitingRequests.enqueue( new Request( nurl, mime, target, notify, forceNotify, reload ) );
_timer->setSingleShot( true );
_timer->start( 100 );
}
void NSPluginInstance::postURL( const QString &url, const QByteArray& data,
const QString &mime,
const QString &target, void *notify,
const KParts::OpenUrlArguments& args,
const KParts::BrowserArguments& browserArgs,
bool forceNotify )
{
// Generally this should already be done, but let's be safe for now.
QString nurl = normalizedURL(url);
if (nurl.isNull()) {
return;
}
kDebug(1431) << "NSPluginInstance::postURL url=" << nurl << " target=" << target << " notify=" << notify;
_waitingRequests.enqueue( new Request( nurl, data, mime, target, notify, args, browserArgs, forceNotify) );
_timer->setSingleShot( true );
_timer->start( 100 );
}
void NSPluginInstance::emitStatus(const QString &message)
{
if( _callback )
_callback->statusMessage( message );
}
void NSPluginInstance::streamFinished( NSPluginStreamBase* strm )
{
kDebug(1431) << "-> NSPluginInstance::streamFinished";
emitStatus( QString() );
_streams.removeOne(strm);
strm->deleteLater();
_timer->setSingleShot( true );
_timer->start( 100 );
}
void NSPluginInstance::setupWindow(int winId, int w, int h)
{
kDebug(1431) << "-> NSPluginInstance::setupWindow( winid =" << winId << " w=" << w << ", h=" << h << " ) ";
if (_pluginHost)
_pluginHost->setupWindow(winId, w, h);
else
kWarning(1431) << "No plugin host!";
kDebug(1431) << "<- NSPluginInstance::setupWindow";
_width = w;
_height = h;
_embedded = true;
}
void NSPluginInstance::resizePlugin(int clientWinId, int w, int h)
{
kDebug() << _width << w << _height << h << _embedded;
if (!_embedded)
return;
if (w == _width && h == _height)
return;
_pluginHost->resizePlugin(clientWinId, w, h);
_width = w;
_height = h;
}
void NSPluginInstance::javascriptResult(int id, const QString &result) {
QMap<int, Request*>::iterator i = _jsrequests.find( id );
if (i != _jsrequests.end()) {
--_numJSRequests;
Request *req = i.value();
_jsrequests.erase( i );
NSPluginStream *s = new NSPluginStream( this );
connect( s, SIGNAL(finished(NSPluginStreamBase*)),
SLOT(streamFinished(NSPluginStreamBase*)) );
_streams.append( s );
int len = result.length();
s->create( req->url, QString("text/plain"), req->notify, req->forceNotify );
kDebug(1431) << "javascriptResult has been called with: "<<result << "num JS requests now:" << _numJSRequests;
if (len > 0) {
QByteArray data(len + 1, 0);
memcpy(data.data(), result.toLatin1(), len);
data[len] = 0;
s->process(data, 0);
} else {
len = 7; // "unknown"
QByteArray data(len + 1, 0);
memcpy(data.data(), "unknown", len);
data[len] = 0;
s->process(data, 0);
}
s->finish(false);
delete req;
}
}
NPError NSPluginInstance::NPGetValue(NPPVariable variable, void *value)
{
if( value==0 ) {
kDebug() << "FIXME: value==0 in NSPluginInstance::NPGetValue";
return NPERR_GENERIC_ERROR;
}
if (!_pluginFuncs.getvalue)
return NPERR_GENERIC_ERROR;
NPError error = _pluginFuncs.getvalue(_npp, variable, value);
CHECK(GetValue,error);
}
NPError NSPluginInstance::NPSetValue(NPNVariable variable, void *value)
{
if( value==0 ) {
kDebug() << "FIXME: value==0 in NSPluginInstance::NPSetValue";
return NPERR_GENERIC_ERROR;
}
if (!_pluginFuncs.setvalue)
return NPERR_GENERIC_ERROR;
NPError error = _pluginFuncs.setvalue(_npp, variable, value);
CHECK(SetValue,error);
}
NPError NSPluginInstance::NPSetWindow(NPWindow *window)
{
if( window==0 ) {
kDebug() << "FIXME: window==0 in NSPluginInstance::NPSetWindow";
return NPERR_GENERIC_ERROR;
}
if (!_pluginFuncs.setwindow)
return NPERR_GENERIC_ERROR;
NPError error = _pluginFuncs.setwindow(_npp, window);
CHECK(SetWindow,error);
}
NPError NSPluginInstance::NPDestroyStream(NPStream *stream, NPReason reason)
{
if( stream==0 ) {
kDebug() << "FIXME: stream==0 in NSPluginInstance::NPDestroyStream";
return NPERR_GENERIC_ERROR;
}
if (!_pluginFuncs.destroystream)
return NPERR_GENERIC_ERROR;
NPError error = _pluginFuncs.destroystream(_npp, stream, reason);
CHECK(DestroyStream,error);
}
NPError NSPluginInstance::NPNewStream(NPMIMEType type, NPStream *stream, NPBool seekable, uint16_t *stype)
{
if( stream==0 ) {
kDebug() << "FIXME: stream==0 in NSPluginInstance::NPNewStream";
return NPERR_GENERIC_ERROR;
}
if( stype==0 ) {
kDebug() << "FIXME: stype==0 in NSPluginInstance::NPNewStream";
return NPERR_GENERIC_ERROR;
}
if (!_pluginFuncs.newstream)
return NPERR_GENERIC_ERROR;
NPError error = _pluginFuncs.newstream(_npp, type, stream, seekable, stype);
CHECK(NewStream,error);
}
void NSPluginInstance::NPStreamAsFile(NPStream *stream, const char *fname)
{
if( stream==0 ) {
kDebug() << "FIXME: stream==0 in NSPluginInstance::NPStreamAsFile";
return;
}
if( fname==0 ) {
kDebug() << "FIXME: fname==0 in NSPluginInstance::NPStreamAsFile";
return;
}
if (!_pluginFuncs.asfile)
return;
_pluginFuncs.asfile(_npp, stream, fname);
}
int32_t NSPluginInstance::NPWrite(NPStream *stream, int32_t offset, int32_t len, void *buf)
{
if( stream==0 ) {
kDebug() << "FIXME: stream==0 in NSPluginInstance::NPWrite";
return 0;
}
if( buf==0 ) {
kDebug() << "FIXME: buf==0 in NSPluginInstance::NPWrite";
return 0;
}
if (!_pluginFuncs.write)
return 0;
return _pluginFuncs.write(_npp, stream, offset, len, buf);
}
int32_t NSPluginInstance::NPWriteReady(NPStream *stream)
{
if( stream==0 ) {
kDebug() << "FIXME: stream==0 in NSPluginInstance::NPWriteReady";
return 0;
}
if (!_pluginFuncs.writeready)
return 0;
return _pluginFuncs.writeready(_npp, stream);
}
void NSPluginInstance::NPURLNotify(const QString &url, NPReason reason, void *notifyData)
{
if (!_pluginFuncs.urlnotify)
return;
_pluginFuncs.urlnotify(_npp, url.toAscii(), reason, notifyData);
}
void NSPluginInstance::addTempFile(KTemporaryFile *tmpFile)
{
_tempFiles.append(tmpFile);
}
static bool has_focus = false;
void NSPluginInstance::gotFocusIn()
{
has_focus = true;
}
void NSPluginInstance::gotFocusOut()
{
has_focus = false;
}
#include <dlfcn.h>
// Prevent plugins from polling the keyboard regardless of focus.
static int (*real_xquerykeymap)( Display*, char[32] ) = NULL;
extern "C" KDE_EXPORT
int XQueryKeymap( Display* dpy, char k[32] )
{
if( real_xquerykeymap == NULL )
real_xquerykeymap = (int (*)( Display*, char[32] )) dlsym( RTLD_NEXT, "XQueryKeymap" );
if( has_focus )
return real_xquerykeymap( dpy, k );
memset( k, 0, 32 );
return 1;
}
/***************************************************************************/
NSPluginViewer::NSPluginViewer( QObject *parent )
: QObject( parent )
{
(void) new ViewerAdaptor( this );
QDBusConnection::sessionBus().registerObject( "/Viewer", this );
QObject::connect(QDBusConnection::sessionBus().interface(),
SIGNAL(serviceOwnerChanged(QString,QString,QString)),
this, SLOT(appChanged(QString,QString,QString)));
}
NSPluginViewer::~NSPluginViewer()
{
kDebug(1431) << "NSPluginViewer::~NSPluginViewer";
}
void NSPluginViewer::appChanged( const QString& id, const QString& oldOwner, const QString& newOwner) {
Q_UNUSED(id);
if ( oldOwner.isEmpty() || !newOwner.isEmpty() ) // only care about unregistering apps
return;
QMap<QString, NSPluginClass*>::iterator it = _classes.begin();
const QMap<QString, NSPluginClass*>::iterator end = _classes.end();
for ( ; it != end; ++it )
{
if (it.value()->app() == oldOwner) {
it = _classes.erase(it);
}
}
if (_classes.isEmpty()) {
shutdown();
}
}
void NSPluginViewer::shutdown()
{
kDebug(1431) << "NSPluginViewer::shutdown";
_classes.clear();
qApp->quit();
}
QDBusObjectPath NSPluginViewer::newClass( const QString& plugin, const QString& senderId )
{
kDebug(1431) << "NSPluginViewer::NewClass( " << plugin << ")";
// search existing class
NSPluginClass *cls = _classes.value( plugin );
if ( !cls ) {
// create new class
cls = new NSPluginClass( plugin, this );
cls->setApp(senderId.toLatin1());
if ( cls->error() ) {
kError(1431) << "Can't create plugin class" << endl;
delete cls;
return QDBusObjectPath("/null");
}
_classes.insert( plugin, cls );
}
return QDBusObjectPath(cls->objectName());
}
/****************************************************************************/
static int s_classCounter = 0;
bool NSPluginClass::s_initedGTK = false;
typedef void gtkInitFunc(int *argc, char ***argv);
NSPluginClass::NSPluginClass( const QString &library,
QObject *parent )
: QObject( parent )
{
(void) new ClassAdaptor( this );
// The object name is used to store the dbus object path
setObjectName( QLatin1String( "/Class_" ) + QString::number( ++s_classCounter ) );
QDBusConnection::sessionBus().registerObject( objectName(), this );
// initialize members
_handle = new KLibrary(QFile::encodeName(library), KGlobal::mainComponent(), this);
_libname = library;
_constructed = false;
_error = true;
_NP_GetMIMEDescription = 0;
_NP_Initialize = 0;
_NP_Shutdown = 0;
_timer = new QTimer( this );
connect( _timer, SIGNAL(timeout()), SLOT(timer()) );
// check lib handle
if (!_handle->load()) {
kDebug(1431) << "Could not dlopen " << library;
return;
}
// get exported lib functions
_NP_GetMIMEDescription = (NP_GetMIMEDescriptionUPP *)_handle->resolveFunction("NP_GetMIMEDescription");
_NP_Initialize = (NP_InitializeUPP *)_handle->resolveFunction("NP_Initialize");
_NP_Shutdown = (NP_ShutdownUPP *)_handle->resolveFunction("NP_Shutdown");
// check for valid returned ptrs
if (!_NP_GetMIMEDescription) {
kDebug(1431) << "Could not get symbol NP_GetMIMEDescription";
return;
}
if (!_NP_Initialize) {
kDebug(1431) << "Could not get symbol NP_Initialize";
return;
}
if (!_NP_Shutdown) {
kDebug(1431) << "Could not get symbol NP_Shutdown";
return;
}
// initialize plugin
kDebug(1431) << "Plugin library " << library << " loaded!";
// see if it uses gtk
if (!s_initedGTK) {
gtkInitFunc* gtkInit = (gtkInitFunc*)_handle->resolveFunction("gtk_init");
if (gtkInit) {
kDebug(1431) << "Calling gtk_init for the plugin";
// Prevent gtk_init() from replacing the X error handlers, since the Gtk
// handlers abort when they receive an X error, thus killing the viewer.
int (*old_error_handler)(Display*,XErrorEvent*) = XSetErrorHandler(0);
int (*old_io_error_handler)(Display*) = XSetIOErrorHandler(0);
gtkInit(0, 0);
XSetErrorHandler(old_error_handler);
XSetIOErrorHandler(old_io_error_handler);
s_initedGTK = true;
}
}
_constructed = true;
_error = initialize()!=NPERR_NO_ERROR;
}
NSPluginClass::~NSPluginClass()
{
qDeleteAll( _instances );
qDeleteAll( _trash );
shutdown();
if (_handle)
_handle->unload();
}
void NSPluginClass::timer()
{
// delete instances
while ( !_trash.isEmpty() ) {
NSPluginInstance *it = _trash.takeFirst();
int i = _instances.indexOf(it);
if ( i != -1 )
delete _instances.takeAt(i);
else // there should be no instansces in trash, which are not in _instances
delete it;
}
}
int NSPluginClass::initialize()
{
kDebug(1431) << "NSPluginClass::Initialize()";
if ( !_constructed )
return NPERR_GENERIC_ERROR;
// initialize nescape exported functions
memset(&_pluginFuncs, 0, sizeof(_pluginFuncs));
memset(&_nsFuncs, 0, sizeof(_nsFuncs));
_pluginFuncs.size = sizeof(_pluginFuncs);
_nsFuncs.size = sizeof(_nsFuncs);
_nsFuncs.version = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
_nsFuncs.geturl = g_NPN_GetURL;
_nsFuncs.posturl = g_NPN_PostURL;
_nsFuncs.requestread = g_NPN_RequestRead;
_nsFuncs.newstream = g_NPN_NewStream;
_nsFuncs.write = g_NPN_Write;
_nsFuncs.destroystream = g_NPN_DestroyStream;
_nsFuncs.status = g_NPN_Status;
_nsFuncs.uagent = g_NPN_UserAgent;
_nsFuncs.memalloc = g_NPN_MemAlloc;
_nsFuncs.memfree = g_NPN_MemFree;
_nsFuncs.memflush = g_NPN_MemFlush;
_nsFuncs.reloadplugins = g_NPN_ReloadPlugins;
_nsFuncs.getJavaEnv = g_NPN_GetJavaEnv;
_nsFuncs.getJavaPeer = g_NPN_GetJavaPeer;
_nsFuncs.geturlnotify = g_NPN_GetURLNotify;
_nsFuncs.posturlnotify = g_NPN_PostURLNotify;
_nsFuncs.getvalue = g_NPN_GetValue;
_nsFuncs.setvalue = g_NPN_SetValue;
_nsFuncs.invalidaterect = g_NPN_InvalidateRect;
_nsFuncs.invalidateregion = g_NPN_InvalidateRegion;
_nsFuncs.forceredraw = g_NPN_ForceRedraw;
ScriptExportEngine::fillInScriptingFunctions(&_nsFuncs);
_nsFuncs.pushpopupsenabledstate = g_NPN_PushPopupsEnabledState;
_nsFuncs.poppopupsenabledstate = g_NPN_PopPopupsEnabledState;
// initialize plugin
NPError error = _NP_Initialize(&_nsFuncs, &_pluginFuncs);
assert(_nsFuncs.size == sizeof(_nsFuncs));
CHECK(Initialize,error);
}
QString NSPluginClass::getMIMEDescription()
{
return _NP_GetMIMEDescription();
}
void NSPluginClass::shutdown()
{
kDebug(1431) << "NSPluginClass::shutdown error=" << _error;
if( _NP_Shutdown && !_error )
_NP_Shutdown();
}
QDBusObjectPath NSPluginClass::newInstance( const QString &url, const QString &mimeType, bool embed,
const QStringList &argn, const QStringList &argv,
const QString &appId, const QString &callbackId, bool reload )
{
kDebug(1431) << url << mimeType;
if ( !_constructed )
return QDBusObjectPath("/null");
// Create plugin instance object
NSPluginInstance *inst = new NSPluginInstance( &_pluginFuncs, _handle,
url, mimeType, argn, argv,
appId, callbackId, embed, this );
if ( !inst->wasInitializedOK() ) {
delete inst;
return QDBusObjectPath("/null");
}
// create source stream
if ( !url.isEmpty() )
inst->requestURL( url, mimeType, QString(), 0, false, reload );
_instances.append( inst );
return QDBusObjectPath(inst->objectName());
}
void NSPluginClass::destroyInstance( NSPluginInstance* inst )
{
// be sure we don't deal with a dangling pointer
if ( NSPluginInstance::lastPluginInstance() == inst )
NSPluginInstance::setLastPluginInstance(0);
// mark for destruction
_trash.append( inst );
timer(); //_timer->start( 0, TRUE );
}
/****************************************************************************/
NSPluginStreamBase::NSPluginStreamBase( NSPluginInstance *instance )
: QObject( instance ), _instance(instance), _stream(0), _tempFile(0L),
_pos(0), _queuePos(0), _error(false)
{
_informed = false;
}
NSPluginStreamBase::~NSPluginStreamBase()
{
if (_stream) {
_instance->NPDestroyStream( _stream, NPRES_USER_BREAK );
if (_stream && _stream->url)
free(const_cast<char*>(_stream->url));
delete _stream;
_stream = 0;
}
delete _tempFile;
_tempFile = 0;
}
void NSPluginStreamBase::stop()
{
finish( true );
}
void NSPluginStreamBase::inform()
{
if (! _informed)
{
KUrl src(_url);
_informed = true;
// inform the plugin
_instance->NPNewStream( _mimeType.isEmpty() ? (char *) "text/plain" : (char*)_mimeType.data(),
_stream, false, &_streamType );
kDebug(1431) << "NewStream stype=" << _streamType << " url=" << _url << " mime=" << _mimeType;
// prepare data transfer
_tempFile = 0L;
if ( _streamType==NP_ASFILE || _streamType==NP_ASFILEONLY ) {
_onlyAsFile = _streamType==NP_ASFILEONLY;
if ( KUrl(_url).isLocalFile() ) {
kDebug(1431) << "local file";
// local file can be passed directly
_fileURL = KUrl(_url).toLocalFile();
// without streaming stream is finished already
if ( _onlyAsFile ) {
kDebug() << "local file AS_FILE_ONLY";
finish( false );
}
} else {
kDebug() << "remote file";
// stream into temporary file (use lower() in case the
// filename as an upper case X in it)
_tempFile = new KTemporaryFile;
_tempFile->open();
_fileURL = _tempFile->fileName();
kDebug() << "saving into " << _fileURL;
}
}
}
}
bool NSPluginStreamBase::create( const QString& url, const QString& mimeType, void *notify, bool forceNotify)
{
if ( _stream )
return false;
_url = url;
_notifyData = notify;
_pos = 0;
_tries = 0;
_onlyAsFile = false;
_streamType = NP_NORMAL;
_informed = false;
_forceNotify = forceNotify;
// create new stream
_stream = new NPStream;
_stream->ndata = this;
_stream->url = strdup(url.toAscii());
_stream->end = 0;
_stream->pdata = 0;
_stream->lastmodified = 0;
_stream->notifyData = _notifyData;
_stream->headers = 0;
_mimeType = mimeType;
return true;
}
void NSPluginStreamBase::updateURL( const KUrl& newURL )
{
_url = newURL;
free(const_cast<char*>(_stream->url));
_stream->url = strdup(_url.url().toLatin1().data());
}
int NSPluginStreamBase::process( const QByteArray &data, int start )
{
int32_t max, sent, to_sent, len;
#ifdef __GNUC__
#warning added a const_cast
#endif
char *d = const_cast<char*>(data.data()) + start;
to_sent = data.size() - start;
while (to_sent > 0)
{
inform();
max = _instance->NPWriteReady(_stream);
//kDebug(1431) << "to_sent == " << to_sent << " and max = " << max;
len = qMin(max, to_sent);
//kDebug(1431) << "-> Feeding stream to plugin: offset=" << _pos << ", len=" << len;
sent = _instance->NPWrite( _stream, _pos, len, d );
//kDebug(1431) << "<- Feeding stream: sent = " << sent;
if (sent == 0) // interrupt the stream for a few ms
break;
if (sent < 0) {
// stream data rejected/error
kDebug(1431) << "stream data rejected/error";
_error = true;
break;
}
if (_tempFile) {
_tempFile->write(d, sent);
}
to_sent -= sent;
_pos += sent;
d += sent;
}
return data.size() - to_sent;
}
bool NSPluginStreamBase::pump()
{
//kDebug(1431) << "queue pos " << _queuePos << ", size " << _queue.size();
inform();
// Suspend until JS handled..
if (_instance->hasPendingJSRequests())
return false;
if ( _queuePos<_queue.size() ) {
int newPos;
// handle AS_FILE_ONLY streams
if ( _onlyAsFile ) {
if (_tempFile) {
_tempFile->write( _queue, _queue.size() );
}
newPos = _queuePos+_queue.size();
} else {
// normal streams
newPos = process( _queue, _queuePos );
}
// count tries
if ( newPos==_queuePos )
_tries++;
else
_tries = 0;
_queuePos = newPos;
}
// return true if queue finished
return _queuePos>=_queue.size();
}
void NSPluginStreamBase::queue( const QByteArray &data )
{
_queue = data;
_queue.detach();
_queuePos = 0;
_tries = 0;
/*
kDebug(1431) << "new queue size=" << data.size()
<< " data=" << (void*)data.data()
<< " queue=" << (void*)_queue.data() << " qsize="
<< _queue.size() << endl;
*/
}
void NSPluginStreamBase::finish( bool err )
{
kDebug(1431) << "finish error=" << err;
_queue.resize( 0 );
_pos = 0;
_queuePos = 0;
inform();
if ( !err ) {
if ( _tempFile ) {
_tempFile->close();
_instance->addTempFile( _tempFile );
_tempFile = 0;
}
if ( !_fileURL.isEmpty() ) {
kDebug() << "stream as file " << _fileURL;
_instance->NPStreamAsFile( _stream, _fileURL.toAscii() );
}
_instance->NPDestroyStream( _stream, NPRES_DONE );
if (_notifyData || _forceNotify)
_instance->NPURLNotify( _url.url(), NPRES_DONE, _notifyData );
} else {
// close temp file
if ( _tempFile ) {
_tempFile->close();
}
// destroy stream
_instance->NPDestroyStream( _stream, NPRES_NETWORK_ERR );
if (_notifyData || _forceNotify)
_instance->NPURLNotify( _url.url(), NPRES_NETWORK_ERR, _notifyData );
}
// delete stream
if (_stream && _stream->url)
free(const_cast<char *>(_stream->url));
delete _stream;
_stream = 0;
// destroy NSPluginStream object
emit finished( this );
}
/****************************************************************************/
NSPluginBufStream::NSPluginBufStream( class NSPluginInstance *instance )
: NSPluginStreamBase( instance )
{
_timer = new QTimer( this );
connect( _timer, SIGNAL(timeout()), this, SLOT(timer()) );
}
NSPluginBufStream::~NSPluginBufStream()
{
}
bool NSPluginBufStream::get( const QString& url, const QString& mimeType,
const QByteArray &buf, void *notifyData,
bool singleShot )
{
_singleShot = singleShot;
if ( create( url, mimeType, notifyData ) ) {
queue( buf );
_timer->setSingleShot( true );
_timer->start( 100 );
}
return false;
}
void NSPluginBufStream::timer()
{
bool finished = pump();
if ( _singleShot )
finish( false );
else {
if ( !finished && tries()<=8 ) {
_timer->setSingleShot( true );
_timer->start( 100 );
} else
finish( error() || tries()>8 );
}
}
/****************************************************************************/
NSPluginStream::NSPluginStream( NSPluginInstance *instance )
: NSPluginStreamBase( instance ), _job(0)
{
_resumeTimer = new QTimer( this );
connect(_resumeTimer, SIGNAL(timeout()), this, SLOT(resume()));
}
NSPluginStream::~NSPluginStream()
{
if ( _job )
_job->kill( KJob::Quietly );
}
bool NSPluginStream::get( const QString& url, const QString& mimeType,
void *notify, bool reload )
{
// create new stream
if ( create( url, mimeType, notify ) ) {
// start the kio job
_job = KIO::get(KUrl( url ), KIO::NoReload, KIO::HideProgressInfo);
_job->addMetaData("errorPage", "false");
_job->addMetaData("AllowCompressedPage", "false");
_job->addMetaData("PropagateHttpHeader", "true");
if (reload) {
_job->addMetaData("cache", "reload");
}
connect(_job, SIGNAL(data(KIO::Job*,QByteArray)),
SLOT(data(KIO::Job*,QByteArray)));
connect(_job, SIGNAL(result(KJob*)), SLOT(result(KJob*)));
connect(_job, SIGNAL(totalSize(KJob*,qulonglong)),
SLOT(totalSize(KJob*,qulonglong)));
connect(_job, SIGNAL(mimetype(KIO::Job*,QString)),
SLOT(mimetype(KIO::Job*,QString)));
connect(_job, SIGNAL(redirection(KIO::Job*,KUrl)),
SLOT(redirection(KIO::Job*,KUrl)));
}
return false;
}
bool NSPluginStream::post( const QString& url, const QByteArray& data,
const QString& mimeType, void *notify, const KParts::OpenUrlArguments& args,
const KParts::BrowserArguments& browserArgs )
{
Q_UNUSED( args )
// create new stream
if ( create( url, mimeType, notify ) ) {
// start the kio job
_job = KIO::http_post(KUrl( url ), data, KIO::HideProgressInfo);
_job->addMetaData("content-type", browserArgs.contentType());
_job->addMetaData("errorPage", "false");
_job->addMetaData("PropagateHttpHeader", "true");
_job->addMetaData("AllowCompressedPage", "false");
connect(_job, SIGNAL(data(KIO::Job*,QByteArray)),
SLOT(data(KIO::Job*,QByteArray)));
connect(_job, SIGNAL(result(KJob*)), SLOT(result(KJob*)));
connect(_job, SIGNAL(totalSize(KJob*,qulonglong)),
SLOT(totalSize(KJob*,qulonglong)));
connect(_job, SIGNAL(mimetype(KIO::Job*,QString)),
SLOT(mimetype(KIO::Job*,QString)));
connect(_job, SIGNAL(redirection(KIO::Job*,KUrl)),
SLOT(redirection(KIO::Job*,KUrl)));
}
return false;
}
void NSPluginStream::data(KIO::Job*, const QByteArray &data)
{
//kDebug(1431) << "NSPluginStream::data - job=" << (void*)job << " data size=" << data.size();
queue( data );
if ( !pump() ) {
_job->suspend();
_resumeTimer->setSingleShot( true );
_resumeTimer->start( 100 );
}
}
void NSPluginStream::redirection(KIO::Job * /*job*/, const KUrl& url)
{
updateURL(url);
}
void NSPluginStream::totalSize(KJob * job, qulonglong size)
{
kDebug(1431) << "NSPluginStream::totalSize - job=" << (void*)job << " size=" << KIO::number(size);
_stream->end = size;
}
void NSPluginStream::mimetype(KIO::Job * job, const QString &mimeType)
{
kDebug(1431) << "NSPluginStream::QByteArray - job=" << (void*)job << " mimeType=" << mimeType;
_mimeType = mimeType;
_headers = job->metaData()["HTTP-Headers"].toLatin1();
_stream->headers = _headers.data();
}
void NSPluginStream::resume()
{
if ( error() || tries()>8 ) {
_job->kill( KJob::Quietly );
finish( true );
return;
}
if ( pump() ) {
kDebug(1431) << "resume job";
_job->resume();
} else {
kDebug(1431) << "restart timer";
_resumeTimer->setSingleShot( true );
_resumeTimer->start( 100 );
}
}
void NSPluginStream::result(KJob *job)
{
int err = job->error();
_job = 0;
finish( err!=0 || error() );
}
#include "nsplugin.moc"
// vim: ts=4 sw=4 et
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;