dolphin/nsplugins/pluginscan.cpp
2011-05-17 08:48:53 -04:00

687 lines
22 KiB
C++

/*
This application scans for Netscape plugins and create a cache and
the necessary mime and service files.
Copyright (c) 2000 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
Stefan Schimanski <1Stein@gmx.de>
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 <mimetypewriter.h>
#include <config-apps.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QRegExp>
#include <QBuffer>
#include <QtDBus/QtDBus>
#include <kapplication.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <klibrary.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kcrash.h>
#include <kdesktopfile.h>
#include <kservicetype.h>
#include <kmimetype.h>
#include <kcmdlineargs.h>
#include <kaboutdata.h>
#include <klocale.h>
#include <kprocess.h>
#include <ksycoca.h>
#include "sdk/npfunctions.h"
#include "plugin_paths.h"
static int showProgress=0;
// 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
KConfig *infoConfig = 0;
static const char s_mimeTypeMarker[] = "MimeType generated by nspluginscan";
static bool isPluginMimeType( const QString &fname )
{
QFile file(fname);
if (file.open(QIODevice::ReadOnly)) {
const QByteArray firstLine = file.readLine();
if (!firstLine.startsWith("<?xml")) {
kWarning() << "Malformed XML file:" << fname;
return false;
}
const QByteArray secondLine = file.readLine();
// In Qt-4.3.3 the newlines are missing around the comment
// so the comment marker is part of the first line instead of the second one
// So we grep both.
return secondLine.startsWith(s_mimeTypeMarker) || firstLine.contains(s_mimeTypeMarker);
}
return false;
}
static QStringList deletePluginMimeTypes()
{
QStringList removedMimes;
// get local mime type directory
const QString dirPath = KGlobal::dirs()->saveLocation( "xdgdata-mime", "packages" );
kDebug(1433) << "Removing nsplugin MIME types in " << dirPath;
QDir dir( dirPath, QString(), QDir::Name|QDir::IgnoreCase, QDir::Files );
if ( !dir.exists() ) {
kDebug(1433) << "Local mime directory not found, nothing to remove";
return removedMimes;
}
// check all user mime types for our own marker
kDebug(1433) << " - Looking in " << dirPath;
for (unsigned int i=0; i<dir.count(); i++) {
// check mimetype file
const QString file = dir[i];
kDebug(1433) << " - Checking" << file;
if ( isPluginMimeType(dir.absoluteFilePath(file)) ) {
kDebug(1433) << " - Removing" << file;
dir.remove( file );
QString mimeType = file;
mimeType.replace('-', '/');
Q_ASSERT(mimeType.endsWith(".xml"));
mimeType.truncate(mimeType.length()-4);
removedMimes.append(mimeType);
kDebug(1433) << " - Removing" << file << '(' << mimeType << ')';
}
}
return removedMimes;
}
static void generateMimeType( const QString &mime, const QString &extensions, const QString &pluginName, const QString &description )
{
kDebug(1433) << "-> generateMimeType mime=" << mime << " ext="<< extensions;
// get directory from mime string
int pos = mime.lastIndexOf('/');
if ( pos<0 ) {
kDebug(1433) << "Invalid MIME type " << mime;
kDebug(1433) << "<- generateMimeType";
return;
}
// create mime type definition file
MimeTypeWriter mimeTypeWriter(mime);
mimeTypeWriter.setMarker(QString::fromLatin1(s_mimeTypeMarker));
if (!description.isEmpty()) {
mimeTypeWriter.setComment(description);
} else {
mimeTypeWriter.setComment(i18nc("Name of the netscape-plugin which generated this mimetype", "Netscape plugin %1", pluginName));
}
// Maybe we should do it only if the icon named after the mimetype doesn't exist on the system...
// but this is quite unlikely, why would have the icon and not the mimetype.
mimeTypeWriter.setIconName("x-kde-nsplugin-generated");
if (!extensions.isEmpty()) {
const QStringList exts = extensions.split(',');
QStringList patterns;
for (QStringList::const_iterator it=exts.constBegin(); it != exts.constEnd(); ++it)
patterns.append( "*." + (*it).trimmed() );
mimeTypeWriter.setPatterns(patterns);
}
mimeTypeWriter.write();
// In KDE 3 we wrote X-KDE-AutoEmbed=true into the mimetype desktop file.
// In KDE 4 this has moved to a filetypesrc config file.
KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
fileTypesConfig->group("EmbedSettings").writeEntry("embed-" + mime, true);
kDebug(1433) << "<- generateMimeType";
}
void registerPlugin( const QString &name, const QString &description,
const QString &file, const QString &mimeInfo )
{
// global stuff
KConfigGroup cg( infoConfig, QString() );
int num = cg.readEntry( "number", 0 );
cg.writeEntry( "number", num+1 );
cg = KConfigGroup(infoConfig,QString::number(num));
// create plugin info
cg.writeEntry( "name", name );
cg.writeEntry( "description", description );
cg.writeEntry( "file", file );
cg.writeEntry( "mime", mimeInfo );
}
static void segv_handler(int)
{
_exit(255);
}
static int tryCheck(int write_fd, const QString &absFile)
{
KLibrary _handle(absFile);
if (!_handle.load()) {
kDebug(1433) << " - open failed with message " <<
_handle.errorString() << ", skipping " << endl;
return 1;
}
// ask for name and description
QString name = i18n("Unnamed plugin");
QString description;
NPError (*func_GetValue)(void *, NPPVariable, void *) =
(NPError(*)(void *, NPPVariable, void *))
_handle.resolveFunction("NP_GetValue");
if ( func_GetValue ) {
// get name
char *buf = 0;
NPError err = func_GetValue( 0, NPPVpluginNameString,
(void*)&buf );
if ( err==NPERR_NO_ERROR )
name = QString::fromLatin1( buf );
kDebug() << "name = " << name;
// get name
NPError nperr = func_GetValue( 0, NPPVpluginDescriptionString,
(void*)&buf );
if ( nperr==NPERR_NO_ERROR )
description = QString::fromLatin1( buf );
kDebug() << "description = " << description;
}
else
kWarning() << "Plugin doesn't implement NP_GetValue" ;
// get mime description function pointer
char* (*func_GetMIMEDescription)() =
(char *(*)())_handle.resolveFunction("NP_GetMIMEDescription");
if ( !func_GetMIMEDescription ) {
kDebug(1433) << " - no GetMIMEDescription, skipping";
_handle.unload();
return 1;
}
// ask for mime information
QString mimeInfo = func_GetMIMEDescription();
if ( mimeInfo.isEmpty() ) {
kDebug(1433) << " - no mime info returned, skipping";
_handle.unload();
return 1;
}
// remove version info, as it is not used at the moment
QRegExp versionRegExp(";version=[^:]*:");
mimeInfo.replace( versionRegExp, ":");
if (!mimeInfo.isEmpty() && !mimeInfo.endsWith(';')) {
mimeInfo += ';'; // XDG compliance
}
// unload plugin lib
kDebug(1433) << " - unloading plugin";
_handle.unload();
// create a QDataStream for our IPC pipe (to send plugin info back to the parent)
QFile stream_file;
stream_file.open(write_fd, QIODevice::WriteOnly);
QDataStream stream(&stream_file);
// return the gathered info to the parent
stream << name;
stream << description;
stream << mimeInfo;
return 0;
}
void scanDirectory( const QString &dir, QStringList &mimeInfoList,
QTextStream &cache )
{
kDebug(1433) << "-> scanDirectory dir=" << dir;
// iterate over all files
QDir files( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Files );
if ( !files.exists( dir ) ) {
kDebug(1433) << "No files found";
kDebug(1433) << "<- scanDirectory dir=" << dir;
return;
}
for (unsigned int i=0; i<files.count(); i++) {
QString extension;
int j = files[i].lastIndexOf('.');
if (j > 0)
extension = files[i].mid(j+1);
// ignore crashing libs
if ( files[i]=="librvplayer.so" || // RealPlayer 5
files[i]=="libnullplugin.so" || // Netscape Default Plugin
files[i]=="cult3dplugin.so" || // Cult 3d plugin
extension == "jar" || // Java archive
extension == "zip" || // Zip file (for classes)
extension == "class" || // Java class
extension == "png" || // PNG Image
extension == "jpg" || // JPEG image
extension == "gif" || // GIF image
extension == "bak" || // .so.bak-up files
extension == "tmp" || // tmp files
extension == "xpt" || // XPConnect
extension.startsWith("htm") // HTML
)
continue;
// get absolute file path
QString absFile = files.absoluteFilePath( files[i] );
kDebug(1433) << "Checking library " << absFile;
// open the library and ask for the mimetype
kDebug(1433) << " - opening " << absFile;
cache.flush();
// fork, so that a crash in the plugin won't stop the scanning of other plugins
int pipes[2];
if (pipe(pipes) != 0) continue;
int loader_pid = fork();
if (loader_pid == -1) {
// unable to fork
continue;
} else if (loader_pid == 0) {
// inside the child
close(pipes[0]);
KCrash::setCrashHandler(segv_handler);
_exit(tryCheck(pipes[1], absFile));
} else {
close(pipes[1]);
QBuffer m_buffer;
m_buffer.open(QIODevice::WriteOnly);
QFile q_read_pipe;
q_read_pipe.open(pipes[0], QIODevice::ReadOnly);
char *data = (char *)malloc(4096);
if (!data) continue;
int size;
// when the child closes, we'll get an EOF (size == 0)
while ((size = q_read_pipe.read(data, 4096)) > 0)
m_buffer.write(data, size);
free(data);
q_read_pipe.close();
close(pipes[0]); // we no longer need the pipe's reading end
// close the buffer and open for reading (from the start)
m_buffer.close();
m_buffer.open(QIODevice::ReadOnly);
// create a QDataStream for our buffer
QDataStream stream(&m_buffer);
if (stream.atEnd()) continue;
QString name, description, mimeInfo;
stream >> name;
stream >> description;
stream >> mimeInfo;
bool actuallyUsing = false;
// get mime types from string
QStringList types = mimeInfo.split( ';' );
QStringList::const_iterator type;
for ( type=types.constBegin(); type!=types.constEnd(); ++type ) {
kDebug(1433) << " - type=" << *type;
name = name.replace( ':', "%3A" );
QString entry = name + ':' + (*type).trimmed();
if ( !mimeInfoList.contains( entry ) ) {
if (!actuallyUsing) {
// note the plugin name
cache << "[" << absFile << "]" << endl;
actuallyUsing = true;
}
// write into type cache
QStringList tokens = (*type).split(':', QString::KeepEmptyParts);
QStringList::const_iterator token;
token = tokens.constBegin();
cache << (*token).toLower();
++token;
for ( ; token!=tokens.constEnd(); ++token )
cache << ":" << *token;
cache << endl;
// append type to MIME type list
mimeInfoList.append( entry );
}
}
// register plugin for javascript
registerPlugin( name, description, files[i], mimeInfo );
}
}
// iterate over all sub directories
// NOTE: Mozilla doesn't iterate over subdirectories of the plugin dir.
// We still do (as Netscape 4 did).
QDir dirs( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Dirs );
if ( !dirs.exists() )
return;
static int depth = 0; // avoid recursion because of symlink circles
depth++;
for ( unsigned int i=0; i<dirs.count(); i++ ) {
if ( depth<8 && !dirs[i].contains(".") )
scanDirectory( dirs.absoluteFilePath(dirs[i]), mimeInfoList, cache );
}
depth--;
kDebug() << "<- scanDirectory dir=" << dir;
}
void writeServicesFile( const QStringList &mimeTypes )
{
QString fname = KGlobal::dirs()->saveLocation("services", "")
+ "/nsplugin.desktop";
kDebug(1433) << "Creating services file " << fname;
QFile f(fname);
if ( f.open(QIODevice::WriteOnly) ) {
QTextStream ts(&f);
ts << "[Desktop Entry]" << endl;
ts << "Name=" << i18n("Netscape plugin viewer") << endl;
ts << "Type=Service" << endl;
ts << "Icon=netscape" << endl;
ts << "Comment=" << i18n("Netscape plugin viewer") << endl;
ts << "X-KDE-Library=libnsplugin" << endl;
ts << "InitialPreference=0" << endl;
ts << "ServiceTypes=KParts/ReadOnlyPart,Browser/View" << endl;
ts << "X-KDE-BrowserView-PluginsInfo=nsplugins/pluginsinfo" << endl;
if (mimeTypes.count() > 0)
ts << "MimeType=" << mimeTypes.join(";") << ";" << endl;
f.close();
} else
kDebug(1433) << "Failed to open file " << fname;
}
void removeExistingExtensions( QString &extension )
{
QStringList filtered;
const QStringList exts = extension.split( ',' );
for ( QStringList::const_iterator it=exts.constBegin(); it!=exts.constEnd(); ++it ) {
QString ext = (*it).trimmed();
if ( ext == "*" ) // some plugins have that, but we don't want to associate a mimetype with *.*!
continue;
KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("file:///foo."+ext ),
0, true, true );
if( mime->isDefault() ||
mime->comment().startsWith("Netscape") ) {
kDebug() << "accepted";
filtered.append( ext );
}
}
extension = filtered.join( "," );
}
void sigChildHandler(int)
{
// since waitpid and write change errno, we have to save it and restore it
// (Richard Stevens, Advanced programming in the Unix Environment)
int saved_errno = errno;
while (waitpid(-1, 0, WNOHANG) == 0)
;
errno = saved_errno;
}
int main( int argc, char **argv )
{
KAboutData aboutData( "nspluginscan", "nsplugin", ki18n("nspluginscan"),
"0.3", ki18n("nspluginscan"), KAboutData::License_GPL,
ki18n("(c) 2000,2001 by Stefan Schimanski") );
KCmdLineArgs::init( argc, argv, &aboutData );
KCmdLineOptions options;
options.add("verbose", ki18n("Show progress output for GUI"));
KCmdLineArgs::addCmdLineOptions( options );
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
showProgress = args->isSet("verbose");
if (showProgress) {
printf("10\n"); fflush(stdout);
}
KApplication app(false);
// Set up SIGCHLD handler
struct sigaction act;
act.sa_handler=sigChildHandler;
sigemptyset(&(act.sa_mask));
sigaddset(&(act.sa_mask), SIGCHLD);
// Make sure we don't block this signal. gdb tends to do that :-(
sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0);
act.sa_flags = SA_NOCLDSTOP;
// CC: take care of SunOS which automatically restarts interrupted system
// calls (and thus does not have SA_RESTART)
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
struct sigaction oldact;
sigaction( SIGCHLD, &act, &oldact );
// set up the paths used to look for plugins
QStringList searchPaths = getSearchPaths();
QStringList mimeInfoList;
infoConfig = new KConfig( KGlobal::dirs()->saveLocation("data", "nsplugins") +
"/pluginsinfo" );
infoConfig->group("<default>").writeEntry( "number", 0 );
// open the cache file for the mime information
QString cacheName = KGlobal::dirs()->saveLocation("data", "nsplugins")+"/cache";
kDebug(1433) << "Creating MIME cache file " << cacheName;
QFile cachef(cacheName);
if (!cachef.open(QIODevice::WriteOnly))
return -1;
QTextStream cache(&cachef);
if (showProgress) {
printf("20\n"); fflush(stdout);
}
// read in the plugins mime information
kDebug(1433) << "Scanning directories" << searchPaths;
int count = searchPaths.count();
int i = 0;
for ( QStringList::const_iterator it = searchPaths.constBegin();
it != searchPaths.constEnd(); ++it, ++i)
{
if ((*it).isEmpty())
continue;
scanDirectory( *it, mimeInfoList, cache );
if (showProgress) {
printf("%d\n", 25 + (50*i) / count ); fflush(stdout);
}
}
if (showProgress) {
printf("75\n"); fflush(stdout);
}
// We're done with forking,
// KProcess needs SIGCHLD to be reset to what it was initially
sigaction( SIGCHLD, &oldact, 0 );
// delete old mime types
kDebug(1433) << "Removing old mimetypes";
const QStringList oldMimes = deletePluginMimeTypes();
bool mimeTypesChanged = !oldMimes.isEmpty();
if (showProgress) {
printf("80\n"); fflush(stdout);
}
// write mimetype files
kDebug(1433) << "Creating MIME type descriptions";
QStringList mimeTypes;
for ( QStringList::const_iterator it=mimeInfoList.constBegin();
it!=mimeInfoList.constEnd(); ++it) {
kDebug(1433) << "Handling MIME type " << *it;
QStringList info = (*it).split(':', QString::KeepEmptyParts);
if ( info.count()==4 ) {
QString pluginName = info[0];
QString type = info[1].toLower();
QString extension = info[2];
QString desc = info[3];
// append to global mime type list
if ( !mimeTypes.contains(type) ) {
kDebug(1433) << " - mimeType=" << type;
mimeTypes.append( type );
// write or update mime type file, if
// 1) it doesn't exist in ksycoca (meaning we never heard of it)
// 2) or we just deleted it [it's still in ksycoca though]
// This prevents noticing that a shared-mime-info upgrade brought
// us a mimetype we needed; but doing this right requires launching
// kbuildsycoca4 after removing mimetypes above, and that's really slow
bool mustWriteMimeType = KMimeType::mimeType(type).isNull();
if (!mustWriteMimeType)
mustWriteMimeType = oldMimes.contains(type);
if ( mustWriteMimeType ) {
kDebug(1433) << " - creating MIME type description";
removeExistingExtensions( extension );
generateMimeType( type, extension, pluginName, desc );
mimeTypesChanged = true;
} else {
kDebug(1433) << " - already exists";
}
}
}
}
// done with new mimetypes, run update-mime-database
if (mimeTypesChanged) {
MimeTypeWriter::runUpdateMimeDatabase();
// note that we'll run kbuildsycoca below anyway
}
if (showProgress) {
printf("85\n"); fflush(stdout);
}
// close files
kDebug(1433) << "Closing cache file";
cachef.close();
infoConfig->sync();
delete infoConfig;
// write plugin lib service file
writeServicesFile( mimeTypes );
if (showProgress) {
printf("90\n"); fflush(stdout);
}
if (QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kded")) {
// Tell kded to update sycoca database.
QDBusInterface kbuildsycoca("org.kde.kded", "/kbuildsycoca",
"org.kde.kded");
kbuildsycoca.call("recreate");
} else {
// kded not running? fallback to calling kbuildsycoca directly:
KProcess proc;
proc << KStandardDirs::findExe(KBUILDSYCOCA_EXENAME);
proc.setOutputChannelMode(KProcess::MergedChannels); // silence kbuildsycoca output
proc.execute();
}
}