delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / apps / nsplugins / pluginscan.cpp
blobe33556a9104ce244b7c2c42a1b518dc80b06681b
1 /*
3 This application scans for Netscape plugins and create a cache and
4 the necessary mime and service files.
7 Copyright (c) 2000 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
8 Stefan Schimanski <1Stein@gmx.de>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include <mimetypewriter.h>
27 #include <config-apps.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <unistd.h>
36 #include <QDir>
37 #include <QFile>
38 #include <QTextStream>
39 #include <QRegExp>
40 #include <QBuffer>
42 #include <QtDBus/QtDBus>
44 #include <kapplication.h>
45 #include <kdebug.h>
46 #include <kglobal.h>
47 #include <kstandarddirs.h>
48 #include <klibloader.h>
49 #include <kconfig.h>
50 #include <kconfiggroup.h>
51 #include <kcrash.h>
52 #include <kdesktopfile.h>
53 #include <kservicetype.h>
54 #include <kmimetype.h>
55 #include <kcmdlineargs.h>
56 #include <kaboutdata.h>
57 #include <klocale.h>
59 #include "sdk/npupp.h"
61 #include "plugin_paths.h"
63 static int showProgress=0;
65 // provide these symbols when compiling with gcc 3.x
67 #if defined(__GNUC__) && defined(__GNUC_MINOR__)
68 #define KDE_GNUC_PREREQ(maj,min) \
69 ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
70 #else
71 #define KDE_GNUC_PREREQ(maj,min) 0
72 #endif
74 #if defined(__GNUC__) && KDE_GNUC_PREREQ(3,0)
75 extern "C" void* __builtin_new(size_t s)
77 return operator new(s);
80 extern "C" void __builtin_delete(void* p)
82 operator delete(p);
85 extern "C" void* __builtin_vec_new(size_t s)
87 return operator new[](s);
90 extern "C" void __builtin_vec_delete(void* p)
92 operator delete[](p);
95 extern "C" void __pure_virtual()
97 abort();
99 #endif
101 KConfig *infoConfig = 0;
103 static const char s_mimeTypeMarker[] = "MimeType generated by nspluginscan";
106 static bool isPluginMimeType( const QString &fname )
108 QFile file(fname);
109 if (file.open(QIODevice::ReadOnly)) {
110 const QByteArray firstLine = file.readLine();
111 if (!firstLine.startsWith("<?xml")) {
112 kWarning() << "Malformed XML file:" << fname;
113 return false;
115 const QByteArray secondLine = file.readLine();
116 // In Qt-4.3.3 the newlines are missing around the comment
117 // so the comment marker is part of the first line instead of the second one
118 // So we grep both.
119 return secondLine.startsWith(s_mimeTypeMarker) || firstLine.contains(s_mimeTypeMarker);
121 return false;
125 static QStringList deletePluginMimeTypes()
127 QStringList removedMimes;
129 // get local mime type directory
130 const QString dirPath = KGlobal::dirs()->saveLocation( "xdgdata-mime", "packages" );
131 kDebug(1433) << "Removing nsplugin MIME types in " << dirPath;
132 QDir dir( dirPath, QString(), QDir::Name|QDir::IgnoreCase, QDir::Files );
133 if ( !dir.exists() ) {
134 kDebug(1433) << "Local mime directory not found, nothing to remove";
135 return removedMimes;
138 // check all user mime types for our own marker
139 kDebug(1433) << " - Looking in " << dirPath;
140 for (unsigned int i=0; i<dir.count(); i++) {
142 // check mimetype file
143 const QString file = dir[i];
144 kDebug(1433) << " - Checking" << file;
145 if ( isPluginMimeType(dir.absoluteFilePath(file)) ) {
146 kDebug(1433) << " - Removing" << file;
147 dir.remove( file );
148 QString mimeType = file;
149 mimeType.replace('-', '/');
150 Q_ASSERT(mimeType.endsWith(".xml"));
151 mimeType.truncate(mimeType.length()-4);
152 removedMimes.append(mimeType);
153 kDebug(1433) << " - Removing" << file << '(' << mimeType << ')';
156 return removedMimes;
160 static void generateMimeType( const QString &mime, const QString &extensions, const QString &pluginName, const QString &description )
162 kDebug(1433) << "-> generateMimeType mime=" << mime << " ext="<< extensions;
164 // get directory from mime string
165 int pos = mime.lastIndexOf('/');
166 if ( pos<0 ) {
167 kDebug(1433) << "Invalid MIME type " << mime;
168 kDebug(1433) << "<- generateMimeType";
169 return;
172 // create mime type definition file
173 MimeTypeWriter mimeTypeWriter(mime);
174 mimeTypeWriter.setMarker(QString::fromLatin1(s_mimeTypeMarker));
175 if (!description.isEmpty()) {
176 mimeTypeWriter.setComment(description);
177 } else {
178 // TODO remove mimeinfo here, after message freeze
179 mimeTypeWriter.setComment(i18n("Netscape plugin mimeinfo") + ' ' + pluginName);
182 // Maybe we should do it only if the icon named after the mimetype doesn't exist on the system...
183 // but this is quite unlikely, why would have the icon and not the mimetype.
184 mimeTypeWriter.setIconName("x-kde-nsplugin-generated");
186 if (!extensions.isEmpty()) {
187 const QStringList exts = extensions.split(',');
188 QStringList patterns;
189 for (QStringList::const_iterator it=exts.constBegin(); it != exts.constEnd(); ++it)
190 patterns.append( "*." + (*it).trimmed() );
191 mimeTypeWriter.setPatterns(patterns);
194 mimeTypeWriter.write();
196 // In KDE 3 we wrote X-KDE-AutoEmbed=true into the mimetype desktop file.
197 // In KDE 4 this has moved to a filetypesrc config file.
198 KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
199 fileTypesConfig->group("EmbedSettings").writeEntry("embed-" + mime, true);
201 kDebug(1433) << "<- generateMimeType";
205 void registerPlugin( const QString &name, const QString &description,
206 const QString &file, const QString &mimeInfo )
208 // global stuff
209 KConfigGroup cg( infoConfig, QString() );
210 int num = cg.readEntry( "number", 0 );
211 cg.writeEntry( "number", num+1 );
213 cg = KConfigGroup(infoConfig,QString::number(num));
214 // create plugin info
215 cg.writeEntry( "name", name );
216 cg.writeEntry( "description", description );
217 cg.writeEntry( "file", file );
218 cg.writeEntry( "mime", mimeInfo );
221 static void segv_handler(int)
223 _exit(255);
226 static int tryCheck(int write_fd, const QString &absFile)
228 KLibrary *_handle = KLibLoader::self()->library( QFile::encodeName(absFile) );
229 if (!_handle) {
230 kDebug(1433) << " - open failed with message " <<
231 KLibLoader::self()->lastErrorMessage() << ", skipping " << endl;
232 return 1;
235 // ask for name and description
236 QString name = i18n("Unnamed plugin");
237 QString description;
239 NPError (*func_GetValue)(void *, NPPVariable, void *) =
240 (NPError(*)(void *, NPPVariable, void *))
241 _handle->resolveFunction("NP_GetValue");
242 if ( func_GetValue ) {
244 // get name
245 char *buf = 0;
246 NPError err = func_GetValue( 0, NPPVpluginNameString,
247 (void*)&buf );
248 if ( err==NPERR_NO_ERROR )
249 name = QString::fromLatin1( buf );
250 kDebug() << "name = " << name;
252 // get name
253 NPError nperr = func_GetValue( 0, NPPVpluginDescriptionString,
254 (void*)&buf );
255 if ( nperr==NPERR_NO_ERROR )
256 description = QString::fromLatin1( buf );
257 kDebug() << "description = " << description;
259 else
260 kWarning() << "Plugin doesn't implement NP_GetValue" ;
262 // get mime description function pointer
263 char* (*func_GetMIMEDescription)() =
264 (char *(*)())_handle->resolveFunction("NP_GetMIMEDescription");
265 if ( !func_GetMIMEDescription ) {
266 kDebug(1433) << " - no GetMIMEDescription, skipping";
267 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
268 return 1;
271 // ask for mime information
272 QString mimeInfo = func_GetMIMEDescription();
273 if ( mimeInfo.isEmpty() ) {
274 kDebug(1433) << " - no mime info returned, skipping";
275 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
276 return 1;
279 // remove version info, as it is not used at the moment
280 QRegExp versionRegExp(";version=[^:]*:");
281 mimeInfo.replace( versionRegExp, ":");
282 if (!mimeInfo.isEmpty() && !mimeInfo.endsWith(';')) {
283 mimeInfo += ';'; // XDG compliance
286 // unload plugin lib
287 kDebug(1433) << " - unloading plugin";
288 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
290 // create a QDataStream for our IPC pipe (to send plugin info back to the parent)
291 QFile stream_file;
292 stream_file.open(write_fd, QIODevice::WriteOnly);
293 QDataStream stream(&stream_file);
295 // return the gathered info to the parent
296 stream << name;
297 stream << description;
298 stream << mimeInfo;
300 return 0;
303 void scanDirectory( const QString &dir, QStringList &mimeInfoList,
304 QTextStream &cache )
306 kDebug(1433) << "-> scanDirectory dir=" << dir;
308 // iterate over all files
309 QDir files( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Files );
310 if ( !files.exists( dir ) ) {
311 kDebug(1433) << "No files found";
312 kDebug(1433) << "<- scanDirectory dir=" << dir;
313 return;
316 for (unsigned int i=0; i<files.count(); i++) {
317 QString extension;
318 int j = files[i].lastIndexOf('.');
319 if (j > 0)
320 extension = files[i].mid(j+1);
322 // ignore crashing libs
323 if ( files[i]=="librvplayer.so" || // RealPlayer 5
324 files[i]=="libnullplugin.so" || // Netscape Default Plugin
325 files[i]=="cult3dplugin.so" || // Cult 3d plugin
326 extension == "jar" || // Java archive
327 extension == "zip" || // Zip file (for classes)
328 extension == "class" || // Java class
329 extension == "png" || // PNG Image
330 extension == "jpg" || // JPEG image
331 extension == "gif" || // GIF image
332 extension == "bak" || // .so.bak-up files
333 extension == "tmp" || // tmp files
334 extension == "xpt" || // XPConnect
335 extension.startsWith("htm") // HTML
337 continue;
339 // get absolute file path
340 QString absFile = files.absoluteFilePath( files[i] );
341 kDebug(1433) << "Checking library " << absFile;
343 // open the library and ask for the mimetype
344 kDebug(1433) << " - opening " << absFile;
346 cache.flush();
347 // fork, so that a crash in the plugin won't stop the scanning of other plugins
348 int pipes[2];
349 if (pipe(pipes) != 0) continue;
351 int loader_pid = fork();
353 if (loader_pid == -1) {
354 // unable to fork
355 continue;
356 } else if (loader_pid == 0) {
357 // inside the child
358 close(pipes[0]);
359 KCrash::setCrashHandler(segv_handler);
360 _exit(tryCheck(pipes[1], absFile));
361 } else {
362 close(pipes[1]);
364 QBuffer m_buffer;
365 m_buffer.open(QIODevice::WriteOnly);
367 QFile q_read_pipe;
368 q_read_pipe.open(pipes[0], QIODevice::ReadOnly);
370 char *data = (char *)malloc(4096);
371 if (!data) continue;
372 int size;
374 // when the child closes, we'll get an EOF (size == 0)
375 while ((size = q_read_pipe.read(data, 4096)) > 0)
376 m_buffer.write(data, size);
377 free(data);
379 q_read_pipe.close();
380 close(pipes[0]); // we no longer need the pipe's reading end
382 // close the buffer and open for reading (from the start)
383 m_buffer.close();
384 m_buffer.open(QIODevice::ReadOnly);
386 // create a QDataStream for our buffer
387 QDataStream stream(&m_buffer);
389 if (stream.atEnd()) continue;
391 QString name, description, mimeInfo;
392 stream >> name;
393 stream >> description;
394 stream >> mimeInfo;
396 bool actuallyUsing = false;
398 // get mime types from string
399 QStringList types = mimeInfo.split( ';' );
400 QStringList::const_iterator type;
401 for ( type=types.constBegin(); type!=types.constEnd(); ++type ) {
403 kDebug(1433) << " - type=" << *type;
404 name = name.replace( ':', "%3A" );
406 QString entry = name + ':' + (*type).trimmed();
407 if ( !mimeInfoList.contains( entry ) ) {
408 if (!actuallyUsing) {
409 // note the plugin name
410 cache << "[" << absFile << "]" << endl;
411 actuallyUsing = true;
414 // write into type cache
415 QStringList tokens = (*type).split(':', QString::KeepEmptyParts);
416 QStringList::const_iterator token;
417 token = tokens.constBegin();
418 cache << (*token).toLower();
419 ++token;
420 for ( ; token!=tokens.constEnd(); ++token )
421 cache << ":" << *token;
422 cache << endl;
424 // append type to MIME type list
425 mimeInfoList.append( entry );
429 // register plugin for javascript
430 registerPlugin( name, description, files[i], mimeInfo );
434 // iterate over all sub directories
435 // NOTE: Mozilla doesn't iterate over subdirectories of the plugin dir.
436 // We still do (as Netscape 4 did).
437 QDir dirs( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Dirs );
438 if ( !dirs.exists() )
439 return;
441 static int depth = 0; // avoid recursion because of symlink circles
442 depth++;
443 for ( unsigned int i=0; i<dirs.count(); i++ ) {
444 if ( depth<8 && !dirs[i].contains(".") )
445 scanDirectory( dirs.absoluteFilePath(dirs[i]), mimeInfoList, cache );
447 depth--;
449 kDebug() << "<- scanDirectory dir=" << dir;
453 void writeServicesFile( const QStringList &mimeTypes )
455 QString fname = KGlobal::dirs()->saveLocation("services", "")
456 + "/nsplugin.desktop";
457 kDebug(1433) << "Creating services file " << fname;
459 QFile f(fname);
460 if ( f.open(QIODevice::WriteOnly) ) {
462 QTextStream ts(&f);
464 ts << "[Desktop Entry]" << endl;
465 ts << "Name=" << i18n("Netscape plugin viewer") << endl;
466 ts << "Type=Service" << endl;
467 ts << "Icon=netscape" << endl;
468 ts << "Comment=" << i18n("Netscape plugin viewer") << endl;
469 ts << "X-KDE-Library=libnsplugin" << endl;
470 ts << "InitialPreference=0" << endl;
471 ts << "ServiceTypes=KParts/ReadOnlyPart,Browser/View" << endl;
472 ts << "X-KDE-BrowserView-PluginsInfo=nsplugins/pluginsinfo" << endl;
474 if (mimeTypes.count() > 0)
475 ts << "MimeType=" << mimeTypes.join(";") << ";" << endl;
477 f.close();
478 } else
479 kDebug(1433) << "Failed to open file " << fname;
483 void removeExistingExtensions( QString &extension )
485 QStringList filtered;
486 const QStringList exts = extension.split( ',' );
487 for ( QStringList::const_iterator it=exts.constBegin(); it!=exts.constEnd(); ++it ) {
488 QString ext = (*it).trimmed();
489 if ( ext == "*" ) // some plugins have that, but we don't want to associate a mimetype with *.*!
490 continue;
492 KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("file:///foo."+ext ),
493 0, true, true );
494 if( mime->name()=="application/octet-stream" ||
495 mime->comment().left(8)=="Netscape" ) {
496 kDebug() << "accepted";
497 filtered.append( ext );
501 extension = filtered.join( "," );
504 void sigChildHandler(int)
506 // since waitpid and write change errno, we have to save it and restore it
507 // (Richard Stevens, Advanced programming in the Unix Environment)
508 int saved_errno = errno;
510 while (waitpid(-1, 0, WNOHANG) == 0)
513 errno = saved_errno;
517 int main( int argc, char **argv )
519 KAboutData aboutData( "nspluginscan", "nsplugin", ki18n("nspluginscan"),
520 "0.3", ki18n("nspluginscan"), KAboutData::License_GPL,
521 ki18n("(c) 2000,2001 by Stefan Schimanski") );
523 KCmdLineArgs::init( argc, argv, &aboutData );
525 KCmdLineOptions options;
526 options.add("verbose", ki18n("Show progress output for GUI"));
527 KCmdLineArgs::addCmdLineOptions( options );
528 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
530 showProgress = args->isSet("verbose");
531 if (showProgress) {
532 printf("10\n"); fflush(stdout);
535 KApplication app(false);
537 // Set up SIGCHLD handler
538 struct sigaction act;
539 act.sa_handler=sigChildHandler;
540 sigemptyset(&(act.sa_mask));
541 sigaddset(&(act.sa_mask), SIGCHLD);
542 // Make sure we don't block this signal. gdb tends to do that :-(
543 sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0);
545 act.sa_flags = SA_NOCLDSTOP;
547 // CC: take care of SunOS which automatically restarts interrupted system
548 // calls (and thus does not have SA_RESTART)
550 #ifdef SA_RESTART
551 act.sa_flags |= SA_RESTART;
552 #endif
554 struct sigaction oldact;
555 sigaction( SIGCHLD, &act, &oldact );
558 // set up the paths used to look for plugins
559 QStringList searchPaths = getSearchPaths();
560 QStringList mimeInfoList;
562 infoConfig = new KConfig( KGlobal::dirs()->saveLocation("data", "nsplugins") +
563 "/pluginsinfo" );
564 infoConfig->group("<default>").writeEntry( "number", 0 );
566 // open the cache file for the mime information
567 QString cacheName = KGlobal::dirs()->saveLocation("data", "nsplugins")+"/cache";
568 kDebug(1433) << "Creating MIME cache file " << cacheName;
569 QFile cachef(cacheName);
570 if (!cachef.open(QIODevice::WriteOnly))
571 return -1;
572 QTextStream cache(&cachef);
573 if (showProgress) {
574 printf("20\n"); fflush(stdout);
577 // read in the plugins mime information
578 kDebug(1433) << "Scanning directories";
579 int count = searchPaths.count();
580 int i = 0;
581 for ( QStringList::const_iterator it = searchPaths.constBegin();
582 it != searchPaths.constEnd(); ++it, ++i)
584 if ((*it).isEmpty())
585 continue;
586 scanDirectory( *it, mimeInfoList, cache );
587 if (showProgress) {
588 printf("%d\n", 25 + (50*i) / count ); fflush(stdout);
592 if (showProgress) {
593 printf("75\n"); fflush(stdout);
596 // We're done with forking,
597 // KProcess needs SIGCHLD to be reset to what it was initially
598 sigaction( SIGCHLD, &oldact, 0 );
600 // delete old mime types
601 kDebug(1433) << "Removing old mimetypes";
602 const QStringList oldMimes = deletePluginMimeTypes();
603 bool mimeTypesChanged = !oldMimes.isEmpty();
605 if (showProgress) {
606 printf("80\n"); fflush(stdout);
609 // write mimetype files
610 kDebug(1433) << "Creating MIME type descriptions";
611 QStringList mimeTypes;
612 for ( QStringList::const_iterator it=mimeInfoList.constBegin();
613 it!=mimeInfoList.constEnd(); ++it) {
615 kDebug(1433) << "Handling MIME type " << *it;
617 QStringList info = (*it).split(':', QString::KeepEmptyParts);
618 if ( info.count()==4 ) {
619 QString pluginName = info[0];
620 QString type = info[1].toLower();
621 QString extension = info[2];
622 QString desc = info[3];
624 // append to global mime type list
625 if ( !mimeTypes.contains(type) ) {
626 kDebug(1433) << " - mimeType=" << type;
627 mimeTypes.append( type );
629 // write or update mime type file, if
630 // 1) it doesn't exist in ksycoca (meaning we never heard of it)
631 // 2) or we just deleted it [it's still in ksycoca though]
632 // This prevents noticing that a shared-mime-info upgrade brought
633 // us a mimetype we needed; but doing this right requires launching
634 // kbuildsycoca4 after removing mimetypes above, and that's really slow
635 bool mustWriteMimeType = KMimeType::mimeType(type).isNull();
636 if (!mustWriteMimeType)
637 mustWriteMimeType = oldMimes.contains(type);
638 if ( mustWriteMimeType ) {
639 kDebug(1433) << " - creating MIME type description";
640 removeExistingExtensions( extension );
641 generateMimeType( type, extension, pluginName, desc );
642 mimeTypesChanged = true;
643 } else {
644 kDebug(1433) << " - already exists";
650 // done with new mimetypes, run update-mime-database
651 if (mimeTypesChanged) {
652 MimeTypeWriter::runUpdateMimeDatabase();
653 // note that we'll run kbuildsycoca below anyway
656 if (showProgress) {
657 printf("85\n"); fflush(stdout);
660 // close files
661 kDebug(1433) << "Closing cache file";
662 cachef.close();
664 infoConfig->sync();
665 delete infoConfig;
667 // write plugin lib service file
668 writeServicesFile( mimeTypes );
669 if (showProgress) {
670 printf("90\n"); fflush(stdout);
673 // Tell kded to update sycoca database.
674 QDBusInterface kbuildsycoca("org.kde.kded", "/kbuildsycoca",
675 "org.kde.kded");
676 if (kbuildsycoca.isValid())
677 kbuildsycoca.call("recreate");