diff options
Diffstat (limited to 'nsplugins/pluginscan.cpp')
-rw-r--r-- | nsplugins/pluginscan.cpp | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/nsplugins/pluginscan.cpp b/nsplugins/pluginscan.cpp new file mode 100644 index 000000000..9d76d1913 --- /dev/null +++ b/nsplugins/pluginscan.cpp @@ -0,0 +1,654 @@ +/* + + 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 <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 ); +} + +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]); + _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()); +} |