You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/nsplugins/pluginscan.cpp

662 lines
20 KiB

/*
This application scans for Netscape plugins and create a cache and
the necessary mimelnk 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 <config.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <qdir.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qregexp.h>
#include <qbuffer.h>
#include <dcopclient.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <klibloader.h>
#include <kconfig.h>
#include <kcrash.h>
#include <kdesktopfile.h>
#include <kservicetype.h>
#include <kmimetype.h>
#include <kcmdlineargs.h>
#include <kaboutdata.h>
#include <klocale.h>
#include "sdk/npupp.h"
#include <X11/Intrinsic.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
// The only purpose of this function is just to pull in libXt.
// Using --enable-new-ldflags makes the linker use --as-needed and since
// otherwise nspluginscan wouldn't actually use libXt it wouldn't be really
// linked against it. However some plugins are not linked against libXt
// yet expect to have it available in the hosting application.
void pullInXt()
{
XtFree( NULL );
}
KConfig *infoConfig = 0;
bool isPluginMimeType( QString fname )
{
KDesktopFile cfg( fname, true );
cfg.setDesktopGroup();
return cfg.hasKey( "X-KDE-nsplugin" );
}
void deletePluginMimeTypes()
{
// iterate through local mime type directories
QString dir = KGlobal::dirs()->saveLocation( "mime" );
kdDebug(1433) << "Removing nsplugin MIME types in " << dir << endl;
QDir dirs( dir, QString::null, QDir::Name|QDir::IgnoreCase, QDir::Dirs );
if ( !dirs.exists() ) {
kdDebug(1433) << "Directory not found" << endl;
return;
}
for ( unsigned int i=0; i<dirs.count(); i++ ) {
if ( !dirs[i].contains(".") ) {
// check all mime types for X-KDE-nsplugin flag
kdDebug(1433) << " - Looking in " << dirs[i] << endl;
QDir files( dirs.absFilePath(dirs[i]), QString::null,
QDir::Name|QDir::IgnoreCase, QDir::Files );
if ( files.exists( dir ) ) {
for (unsigned int i=0; i<files.count(); i++) {
// check .desktop file
kdDebug(1433) << " - Checking " << files[i] << endl;
if ( isPluginMimeType(files.absFilePath(files[i])) ) {
kdDebug(1433) << " - Removing " << files[i] << endl;
files.remove( files[i] );
}
}
}
}
}
}
void generateMimeType( QString mime, QString extensions, QString pluginName, QString description )
{
kdDebug(1433) << "-> generateMimeType mime=" << mime << " ext="<< extensions << endl;
// get directory from mime string
QString dir;
QString name;
int pos = mime.findRev('/');
if ( pos<0 ) {
kdDebug(1433) << "Invalid MIME type " << mime << endl;
kdDebug(1433) << "<- generateMimeType" << endl;
return;
}
dir = KGlobal::dirs()->saveLocation( "mime", mime.left(pos) );
name = mime.mid(pos);
// create mimelnk file
QFile f( dir + name + ".desktop" );
if ( f.open(IO_WriteOnly) ) {
// write .desktop file
QTextStream ts(&f);
ts << "[Desktop Entry]" << endl;
ts << "Type=MimeType" << endl;
ts << "MimeType=" << mime << endl;
ts << "Icon=netscape_doc" << endl;
ts << "Comment=Netscape " << pluginName << endl;
ts << "X-KDE-AutoEmbed=true" << endl;
ts << "X-KDE-nsplugin=true" << endl;
if (!extensions.isEmpty()) {
QStringList exts = QStringList::split(",", extensions);
QStringList patterns;
for (QStringList::Iterator it=exts.begin(); it != exts.end(); ++it)
patterns.append( "*." + (*it).stripWhiteSpace() );
ts << "Patterns=" << patterns.join( ";" ) << endl;
}
if (!description.isEmpty())
ts << "Name=" << description << endl;
else
ts << "Name=" << i18n("Netscape plugin mimeinfo") << endl;
f.close();
}
kdDebug(1433) << "<- generateMimeType" << endl;
}
void registerPlugin( const QString &name, const QString &description,
const QString &file, const QString &mimeInfo )
{
// global stuff
infoConfig->setGroup( QString::null );
int num = infoConfig->readNumEntry( "number", 0 );
infoConfig->writeEntry( "number", num+1 );
// create plugin info
infoConfig->setGroup( QString::number(num) );
infoConfig->writeEntry( "name", name );
infoConfig->writeEntry( "description", description );
infoConfig->writeEntry( "file", file );
infoConfig->writeEntry( "mime", mimeInfo );
}
static void segv_handler(int)
{
_exit(255);
}
int tryCheck(int write_fd, const QString &absFile)
{
KLibrary *_handle = KLibLoader::self()->library( QFile::encodeName(absFile) );
if (!_handle) {
kdDebug(1433) << " - open failed with message " <<
KLibLoader::self()->lastErrorMessage() << ", 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->symbol("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 );
kdDebug() << "name = " << name << endl;
// get name
NPError nperr = func_GetValue( 0, NPPVpluginDescriptionString,
(void*)&buf );
if ( nperr==NPERR_NO_ERROR )
description = QString::fromLatin1( buf );
kdDebug() << "description = " << description << endl;
}
else
kdWarning() << "Plugin doesn't implement NP_GetValue" << endl;
// get mime description function pointer
char* (*func_GetMIMEDescription)() =
(char *(*)())_handle->symbol("NP_GetMIMEDescription");
if ( !func_GetMIMEDescription ) {
kdDebug(1433) << " - no GetMIMEDescription, skipping" << endl;
KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
return 1;
}
// ask for mime information
QString mimeInfo = func_GetMIMEDescription();
if ( mimeInfo.isEmpty() ) {
kdDebug(1433) << " - no mime info returned, skipping" << endl;
KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
return 1;
}
// remove version info, as it is not used at the moment
QRegExp versionRegExp(";version=[^:]*:");
mimeInfo.replace( versionRegExp, ":");
// unload plugin lib
kdDebug(1433) << " - unloading plugin" << endl;
KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
// create a QDataStream for our IPC pipe (to send plugin info back to the parent)
FILE *write_pipe = fdopen(write_fd, "w");
QFile stream_file;
stream_file.open(IO_WriteOnly, write_pipe);
QDataStream stream(&stream_file);
// return the gathered info to the parent
stream << name;
stream << description;
stream << mimeInfo;
return 0;
}
void scanDirectory( QString dir, QStringList &mimeInfoList,
QTextStream &cache )
{
kdDebug(1433) << "-> scanDirectory dir=" << dir << endl;
// iterate over all files
QDir files( dir, QString::null, QDir::Name|QDir::IgnoreCase, QDir::Files );
if ( !files.exists( dir ) ) {
kdDebug(1433) << "No files found" << endl;
kdDebug(1433) << "<- scanDirectory dir=" << dir << endl;
return;
}
for (unsigned int i=0; i<files.count(); i++) {
QString extension;
int j = files[i].findRev('.');
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.absFilePath( files[i] );
kdDebug(1433) << "Checking library " << absFile << endl;
// open the library and ask for the mimetype
kdDebug(1433) << " - opening " << absFile << endl;
cache.device()->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(IO_WriteOnly);
FILE *read_pipe = fdopen(pipes[0], "r");
QFile q_read_pipe;
q_read_pipe.open(IO_ReadOnly, read_pipe);
char *data = (char *)malloc(4096);
if (!data) continue;
// when the child closes, we'll get an EOF (size == 0)
while (int size = q_read_pipe.readBlock(data, 4096)) {
if (size > 0)
m_buffer.writeBlock(data, size);
}
free(data);
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(IO_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 = QStringList::split( ';', mimeInfo );
QStringList::Iterator type;
for ( type=types.begin(); type!=types.end(); ++type ) {
kdDebug(1433) << " - type=" << *type << endl;
name = name.replace( ':', "%3A" );
QString entry = name + ":" + *type;
if ( !mimeInfoList.contains( entry ) ) {
if (!actuallyUsing) {
// note the plugin name
cache << "[" << absFile << "]" << endl;
actuallyUsing = true;
}
// write into type cache
QStringList tokens = QStringList::split(':', *type, TRUE);
QStringList::Iterator token;
token = tokens.begin();
cache << (*token).lower();
++token;
for ( ; token!=tokens.end(); ++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::null, 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.absFilePath(dirs[i]), mimeInfoList, cache );
}
depth--;
kdDebug() << "<- scanDirectory dir=" << dir << endl;
}
void writeServicesFile( QStringList mimeTypes )
{
QString fname = KGlobal::dirs()->saveLocation("services", "")
+ "/nsplugin.desktop";
kdDebug(1433) << "Creating services file " << fname << endl;
QFile f(fname);
if ( f.open(IO_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
kdDebug(1433) << "Failed to open file " << fname << endl;
}
void removeExistingExtensions( QString &extension )
{
QStringList filtered;
QStringList exts = QStringList::split( ",", extension );
for ( QStringList::Iterator it=exts.begin(); it!=exts.end(); ++it ) {
QString ext = (*it).stripWhiteSpace();
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->name()=="application/octet-stream" ||
mime->comment().left(8)=="Netscape" ) {
kdDebug() << "accepted" << endl;
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;
}
static KCmdLineOptions options[] =
{
{ "verbose", I18N_NOOP("Show progress output for GUI"), 0 },
KCmdLineLastOption
};
int main( int argc, char **argv )
{
KAboutData aboutData( "nspluginscan", I18N_NOOP("nspluginscan"),
"0.3", "nspluginscan", KAboutData::License_GPL,
"(c) 2000,2001 by Stefan Schimanski" );
KLocale::setMainCatalogue("nsplugin");
KCmdLineArgs::init( argc, argv, &aboutData );
KCmdLineArgs::addCmdLineOptions( options );
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
showProgress = args->isSet("verbose");
if (showProgress) {
printf("10\n"); fflush(stdout);
}
KApplication app(false, 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
sigaction( SIGCHLD, &act, 0 );
pullInXt();
// set up the paths used to look for plugins
QStringList searchPaths = getSearchPaths();
QStringList mimeInfoList;
infoConfig = new KConfig( KGlobal::dirs()->saveLocation("data", "nsplugins") +
"/pluginsinfo" );
infoConfig->writeEntry( "number", 0 );
// open the cache file for the mime information
QString cacheName = KGlobal::dirs()->saveLocation("data", "nsplugins")+"/cache";
kdDebug(1433) << "Creating MIME cache file " << cacheName << endl;
QFile cachef(cacheName);
if (!cachef.open(IO_WriteOnly))
return -1;
QTextStream cache(&cachef);
if (showProgress) {
printf("20\n"); fflush(stdout);
}
// read in the plugins mime information
kdDebug(1433) << "Scanning directories" << endl;
int count = searchPaths.count();
int i = 0;
for ( QStringList::Iterator it = searchPaths.begin();
it != searchPaths.end(); ++it, ++i)
{
scanDirectory( *it, mimeInfoList, cache );
if (showProgress) {
printf("%d\n", 25 + (50*i) / count ); fflush(stdout);
}
}
if (showProgress) {
printf("75\n"); fflush(stdout);
}
// delete old mime types
kdDebug(1433) << "Removing old mimetypes" << endl;
deletePluginMimeTypes();
if (showProgress) {
printf("80\n"); fflush(stdout);
}
// write mimetype files
kdDebug(1433) << "Creating MIME type descriptions" << endl;
QStringList mimeTypes;
for ( QStringList::Iterator it=mimeInfoList.begin();
it!=mimeInfoList.end(); ++it) {
kdDebug(1433) << "Handling MIME type " << *it << endl;
QStringList info = QStringList::split(":", *it, true);
if ( info.count()==4 ) {
QString pluginName = info[0];
QString type = info[1].lower();
QString extension = info[2];
QString desc = info[3];
// append to global mime type list
if ( !mimeTypes.contains(type) ) {
kdDebug(1433) << " - mimeType=" << type << endl;
mimeTypes.append( type );
// check mimelnk file
QString fname = KGlobal::dirs()->findResource("mime", type+".desktop");
if ( fname.isEmpty() || isPluginMimeType(fname) ) {
kdDebug(1433) << " - creating MIME type description" << endl;
removeExistingExtensions( extension );
generateMimeType( type, extension, pluginName, desc );
} else {
kdDebug(1433) << " - already existant" << endl;
}
}
}
}
if (showProgress) {
printf("85\n"); fflush(stdout);
}
// close files
kdDebug(1433) << "Closing cache file" << endl;
cachef.close();
infoConfig->sync();
delete infoConfig;
// write plugin lib service file
writeServicesFile( mimeTypes );
if (showProgress) {
printf("90\n"); fflush(stdout);
}
DCOPClient *dcc = kapp->dcopClient();
if ( !dcc->isAttached() )
dcc->attach();
// Tel kded to update sycoca database.
dcc->send("kded", "kbuildsycoca", "recreate()", QByteArray());
}