/* This application scans for Netscape plugins and create a cache and the necessary mimelnk and service files. Copyright (c) 2000 Matthias Hoelzer-Kluepfel 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sdk/npupp.h" #include #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( TQString fname ) { KDesktopFile cfg( fname, true ); cfg.setDesktopGroup(); return cfg.hasKey( "X-KDE-nsplugin" ); } void deletePluginMimeTypes() { // iterate through local mime type directories TQString dir = KGlobal::dirs()->saveLocation( "mime" ); kdDebug(1433) << "Removing nsplugin MIME types in " << dir << endl; TQDir dirs( dir, TQString::null, TQDir::Name|TQDir::IgnoreCase, TQDir::Dirs ); if ( !dirs.exists() ) { kdDebug(1433) << "Directory not found" << endl; return; } for ( unsigned int i=0; i generateMimeType mime=" << mime << " ext="<< extensions << endl; // get directory from mime string TQString dir; TQString 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 TQFile f( dir + name + ".desktop" ); if ( f.open(IO_WriteOnly) ) { // write .desktop file TQTextStream 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()) { TQStringList exts = TQStringList::split(",", extensions); TQStringList patterns; for (TQStringList::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 TQString &name, const TQString &description, const TQString &file, const TQString &mimeInfo ) { // global stuff infoConfig->setGroup( TQString::null ); int num = infoConfig->readNumEntry( "number", 0 ); infoConfig->writeEntry( "number", num+1 ); // create plugin info infoConfig->setGroup( TQString::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 TQString &absFile) { KLibrary *_handle = KLibLoader::self()->library( TQFile::encodeName(absFile) ); if (!_handle) { kdDebug(1433) << " - open failed with message " << KLibLoader::self()->lastErrorMessage() << ", skipping " << endl; return 1; } // ask for name and description TQString name = i18n("Unnamed plugin"); TQString 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 = TQString::tqfromLatin1( buf ); kdDebug() << "name = " << name << endl; // get name NPError nperr = func_GetValue( 0, NPPVpluginDescriptionString, (void*)&buf ); if ( nperr==NPERR_NO_ERROR ) description = TQString::tqfromLatin1( 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( TQFile::encodeName(absFile) ); return 1; } // ask for mime information TQString mimeInfo = func_GetMIMEDescription(); if ( mimeInfo.isEmpty() ) { kdDebug(1433) << " - no mime info returned, skipping" << endl; KLibLoader::self()->unloadLibrary( TQFile::encodeName(absFile) ); return 1; } // remove version info, as it is not used at the moment TQRegExp versionRegExp(";version=[^:]*:"); mimeInfo.tqreplace( versionRegExp, ":"); // unload plugin lib kdDebug(1433) << " - unloading plugin" << endl; KLibLoader::self()->unloadLibrary( TQFile::encodeName(absFile) ); // create a TQDataStream for our IPC pipe (to send plugin info back to the parent) FILE *write_pipe = fdopen(write_fd, "w"); TQFile stream_file; stream_file.open(IO_WriteOnly, write_pipe); TQDataStream stream(&stream_file); // return the gathered info to the parent stream << name; stream << description; stream << mimeInfo; return 0; } void scanDirectory( TQString dir, TQStringList &mimeInfoList, TQTextStream &cache ) { kdDebug(1433) << "-> scanDirectory dir=" << dir << endl; // iterate over all files TQDir files( dir, TQString::null, TQDir::Name|TQDir::IgnoreCase, TQDir::Files ); if ( !files.exists( dir ) ) { kdDebug(1433) << "No files found" << endl; kdDebug(1433) << "<- scanDirectory dir=" << dir << endl; return; } for (unsigned int i=0; i 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 TQString 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]); TQBuffer m_buffer; m_buffer.open(IO_WriteOnly); FILE *read_pipe = fdopen(pipes[0], "r"); TQFile 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 TQDataStream for our buffer TQDataStream stream(&m_buffer); if (stream.atEnd()) continue; TQString name, description, mimeInfo; stream >> name; stream >> description; stream >> mimeInfo; bool actuallyUsing = false; // get mime types from string TQStringList types = TQStringList::split( ';', mimeInfo ); TQStringList::Iterator type; for ( type=types.begin(); type!=types.end(); ++type ) { kdDebug(1433) << " - type=" << *type << endl; name = name.tqreplace( ':', "%3A" ); TQString entry = name + ":" + *type; if ( !mimeInfoList.tqcontains( entry ) ) { if (!actuallyUsing) { // note the plugin name cache << "[" << absFile << "]" << endl; actuallyUsing = true; } // write into type cache TQStringList tokens = TQStringList::split(':', *type, TRUE); TQStringList::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). TQDir dirs( dir, TQString::null, TQDir::Name|TQDir::IgnoreCase, TQDir::Dirs ); if ( !dirs.exists() ) return; static int depth = 0; // avoid recursion because of symlink circles depth++; for ( unsigned int i=0; i 0) ts << "MimeType=" << mimeTypes.join(";") << endl; f.close(); } else kdDebug(1433) << "Failed to open file " << fname << endl; } void removeExistingExtensions( TQString &extension ) { TQStringList filtered; TQStringList exts = TQStringList::split( ",", extension ); for ( TQStringList::Iterator it=exts.begin(); it!=exts.end(); ++it ) { TQString 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 TQStringList searchPaths = getSearchPaths(); TQStringList mimeInfoList; infoConfig = new KConfig( KGlobal::dirs()->saveLocation("data", "nsplugins") + "/pluginsinfo" ); infoConfig->writeEntry( "number", 0 ); // open the cache file for the mime information TQString cacheName = KGlobal::dirs()->saveLocation("data", "nsplugins")+"/cache"; kdDebug(1433) << "Creating MIME cache file " << cacheName << endl; TQFile cachef(cacheName); if (!cachef.open(IO_WriteOnly)) return -1; TQTextStream 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 ( TQStringList::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; TQStringList mimeTypes; for ( TQStringList::Iterator it=mimeInfoList.begin(); it!=mimeInfoList.end(); ++it) { kdDebug(1433) << "Handling MIME type " << *it << endl; TQStringList info = TQStringList::split(":", *it, true); if ( info.count()==4 ) { TQString pluginName = info[0]; TQString type = info[1].lower(); TQString extension = info[2]; TQString desc = info[3]; // append to global mime type list if ( !mimeTypes.tqcontains(type) ) { kdDebug(1433) << " - mimeType=" << type << endl; mimeTypes.append( type ); // check mimelnk file TQString 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()", TQByteArray()); }