better text for the button. thanks celeste
[kdebase.git] / apps / nsplugins / pluginscan.cpp
blob3c53e2a70a02a5fc0024b78e3edc02a8db924d47
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 mimeTypeWriter.setComment(i18nc("Name of the netscape-plugin which generated this mimetype", "Netscape plugin %1", pluginName));
181 // Maybe we should do it only if the icon named after the mimetype doesn't exist on the system...
182 // but this is quite unlikely, why would have the icon and not the mimetype.
183 mimeTypeWriter.setIconName("x-kde-nsplugin-generated");
185 if (!extensions.isEmpty()) {
186 const QStringList exts = extensions.split(',');
187 QStringList patterns;
188 for (QStringList::const_iterator it=exts.constBegin(); it != exts.constEnd(); ++it)
189 patterns.append( "*." + (*it).trimmed() );
190 mimeTypeWriter.setPatterns(patterns);
193 mimeTypeWriter.write();
195 // In KDE 3 we wrote X-KDE-AutoEmbed=true into the mimetype desktop file.
196 // In KDE 4 this has moved to a filetypesrc config file.
197 KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
198 fileTypesConfig->group("EmbedSettings").writeEntry("embed-" + mime, true);
200 kDebug(1433) << "<- generateMimeType";
204 void registerPlugin( const QString &name, const QString &description,
205 const QString &file, const QString &mimeInfo )
207 // global stuff
208 KConfigGroup cg( infoConfig, QString() );
209 int num = cg.readEntry( "number", 0 );
210 cg.writeEntry( "number", num+1 );
212 cg = KConfigGroup(infoConfig,QString::number(num));
213 // create plugin info
214 cg.writeEntry( "name", name );
215 cg.writeEntry( "description", description );
216 cg.writeEntry( "file", file );
217 cg.writeEntry( "mime", mimeInfo );
220 static void segv_handler(int)
222 _exit(255);
225 static int tryCheck(int write_fd, const QString &absFile)
227 KLibrary *_handle = KLibLoader::self()->library( QFile::encodeName(absFile) );
228 if (!_handle) {
229 kDebug(1433) << " - open failed with message " <<
230 KLibLoader::self()->lastErrorMessage() << ", skipping " << endl;
231 return 1;
234 // ask for name and description
235 QString name = i18n("Unnamed plugin");
236 QString description;
238 NPError (*func_GetValue)(void *, NPPVariable, void *) =
239 (NPError(*)(void *, NPPVariable, void *))
240 _handle->resolveFunction("NP_GetValue");
241 if ( func_GetValue ) {
243 // get name
244 char *buf = 0;
245 NPError err = func_GetValue( 0, NPPVpluginNameString,
246 (void*)&buf );
247 if ( err==NPERR_NO_ERROR )
248 name = QString::fromLatin1( buf );
249 kDebug() << "name = " << name;
251 // get name
252 NPError nperr = func_GetValue( 0, NPPVpluginDescriptionString,
253 (void*)&buf );
254 if ( nperr==NPERR_NO_ERROR )
255 description = QString::fromLatin1( buf );
256 kDebug() << "description = " << description;
258 else
259 kWarning() << "Plugin doesn't implement NP_GetValue" ;
261 // get mime description function pointer
262 char* (*func_GetMIMEDescription)() =
263 (char *(*)())_handle->resolveFunction("NP_GetMIMEDescription");
264 if ( !func_GetMIMEDescription ) {
265 kDebug(1433) << " - no GetMIMEDescription, skipping";
266 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
267 return 1;
270 // ask for mime information
271 QString mimeInfo = func_GetMIMEDescription();
272 if ( mimeInfo.isEmpty() ) {
273 kDebug(1433) << " - no mime info returned, skipping";
274 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
275 return 1;
278 // remove version info, as it is not used at the moment
279 QRegExp versionRegExp(";version=[^:]*:");
280 mimeInfo.replace( versionRegExp, ":");
281 if (!mimeInfo.isEmpty() && !mimeInfo.endsWith(';')) {
282 mimeInfo += ';'; // XDG compliance
285 // unload plugin lib
286 kDebug(1433) << " - unloading plugin";
287 KLibLoader::self()->unloadLibrary( QFile::encodeName(absFile) );
289 // create a QDataStream for our IPC pipe (to send plugin info back to the parent)
290 QFile stream_file;
291 stream_file.open(write_fd, QIODevice::WriteOnly);
292 QDataStream stream(&stream_file);
294 // return the gathered info to the parent
295 stream << name;
296 stream << description;
297 stream << mimeInfo;
299 return 0;
302 void scanDirectory( const QString &dir, QStringList &mimeInfoList,
303 QTextStream &cache )
305 kDebug(1433) << "-> scanDirectory dir=" << dir;
307 // iterate over all files
308 QDir files( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Files );
309 if ( !files.exists( dir ) ) {
310 kDebug(1433) << "No files found";
311 kDebug(1433) << "<- scanDirectory dir=" << dir;
312 return;
315 for (unsigned int i=0; i<files.count(); i++) {
316 QString extension;
317 int j = files[i].lastIndexOf('.');
318 if (j > 0)
319 extension = files[i].mid(j+1);
321 // ignore crashing libs
322 if ( files[i]=="librvplayer.so" || // RealPlayer 5
323 files[i]=="libnullplugin.so" || // Netscape Default Plugin
324 files[i]=="cult3dplugin.so" || // Cult 3d plugin
325 extension == "jar" || // Java archive
326 extension == "zip" || // Zip file (for classes)
327 extension == "class" || // Java class
328 extension == "png" || // PNG Image
329 extension == "jpg" || // JPEG image
330 extension == "gif" || // GIF image
331 extension == "bak" || // .so.bak-up files
332 extension == "tmp" || // tmp files
333 extension == "xpt" || // XPConnect
334 extension.startsWith("htm") // HTML
336 continue;
338 // get absolute file path
339 QString absFile = files.absoluteFilePath( files[i] );
340 kDebug(1433) << "Checking library " << absFile;
342 // open the library and ask for the mimetype
343 kDebug(1433) << " - opening " << absFile;
345 cache.flush();
346 // fork, so that a crash in the plugin won't stop the scanning of other plugins
347 int pipes[2];
348 if (pipe(pipes) != 0) continue;
350 int loader_pid = fork();
352 if (loader_pid == -1) {
353 // unable to fork
354 continue;
355 } else if (loader_pid == 0) {
356 // inside the child
357 close(pipes[0]);
358 KCrash::setCrashHandler(segv_handler);
359 _exit(tryCheck(pipes[1], absFile));
360 } else {
361 close(pipes[1]);
363 QBuffer m_buffer;
364 m_buffer.open(QIODevice::WriteOnly);
366 QFile q_read_pipe;
367 q_read_pipe.open(pipes[0], QIODevice::ReadOnly);
369 char *data = (char *)malloc(4096);
370 if (!data) continue;
371 int size;
373 // when the child closes, we'll get an EOF (size == 0)
374 while ((size = q_read_pipe.read(data, 4096)) > 0)
375 m_buffer.write(data, size);
376 free(data);
378 q_read_pipe.close();
379 close(pipes[0]); // we no longer need the pipe's reading end
381 // close the buffer and open for reading (from the start)
382 m_buffer.close();
383 m_buffer.open(QIODevice::ReadOnly);
385 // create a QDataStream for our buffer
386 QDataStream stream(&m_buffer);
388 if (stream.atEnd()) continue;
390 QString name, description, mimeInfo;
391 stream >> name;
392 stream >> description;
393 stream >> mimeInfo;
395 bool actuallyUsing = false;
397 // get mime types from string
398 QStringList types = mimeInfo.split( ';' );
399 QStringList::const_iterator type;
400 for ( type=types.constBegin(); type!=types.constEnd(); ++type ) {
402 kDebug(1433) << " - type=" << *type;
403 name = name.replace( ':', "%3A" );
405 QString entry = name + ':' + (*type).trimmed();
406 if ( !mimeInfoList.contains( entry ) ) {
407 if (!actuallyUsing) {
408 // note the plugin name
409 cache << "[" << absFile << "]" << endl;
410 actuallyUsing = true;
413 // write into type cache
414 QStringList tokens = (*type).split(':', QString::KeepEmptyParts);
415 QStringList::const_iterator token;
416 token = tokens.constBegin();
417 cache << (*token).toLower();
418 ++token;
419 for ( ; token!=tokens.constEnd(); ++token )
420 cache << ":" << *token;
421 cache << endl;
423 // append type to MIME type list
424 mimeInfoList.append( entry );
428 // register plugin for javascript
429 registerPlugin( name, description, files[i], mimeInfo );
433 // iterate over all sub directories
434 // NOTE: Mozilla doesn't iterate over subdirectories of the plugin dir.
435 // We still do (as Netscape 4 did).
436 QDir dirs( dir, QString(), QDir::Name|QDir::IgnoreCase, QDir::Dirs );
437 if ( !dirs.exists() )
438 return;
440 static int depth = 0; // avoid recursion because of symlink circles
441 depth++;
442 for ( unsigned int i=0; i<dirs.count(); i++ ) {
443 if ( depth<8 && !dirs[i].contains(".") )
444 scanDirectory( dirs.absoluteFilePath(dirs[i]), mimeInfoList, cache );
446 depth--;
448 kDebug() << "<- scanDirectory dir=" << dir;
452 void writeServicesFile( const QStringList &mimeTypes )
454 QString fname = KGlobal::dirs()->saveLocation("services", "")
455 + "/nsplugin.desktop";
456 kDebug(1433) << "Creating services file " << fname;
458 QFile f(fname);
459 if ( f.open(QIODevice::WriteOnly) ) {
461 QTextStream ts(&f);
463 ts << "[Desktop Entry]" << endl;
464 ts << "Name=" << i18n("Netscape plugin viewer") << endl;
465 ts << "Type=Service" << endl;
466 ts << "Icon=netscape" << endl;
467 ts << "Comment=" << i18n("Netscape plugin viewer") << endl;
468 ts << "X-KDE-Library=libnsplugin" << endl;
469 ts << "InitialPreference=0" << endl;
470 ts << "ServiceTypes=KParts/ReadOnlyPart,Browser/View" << endl;
471 ts << "X-KDE-BrowserView-PluginsInfo=nsplugins/pluginsinfo" << endl;
473 if (mimeTypes.count() > 0)
474 ts << "MimeType=" << mimeTypes.join(";") << ";" << endl;
476 f.close();
477 } else
478 kDebug(1433) << "Failed to open file " << fname;
482 void removeExistingExtensions( QString &extension )
484 QStringList filtered;
485 const QStringList exts = extension.split( ',' );
486 for ( QStringList::const_iterator it=exts.constBegin(); it!=exts.constEnd(); ++it ) {
487 QString ext = (*it).trimmed();
488 if ( ext == "*" ) // some plugins have that, but we don't want to associate a mimetype with *.*!
489 continue;
491 KMimeType::Ptr mime = KMimeType::findByUrl( KUrl("file:///foo."+ext ),
492 0, true, true );
493 if( mime->name()=="application/octet-stream" ||
494 mime->comment().left(8)=="Netscape" ) {
495 kDebug() << "accepted";
496 filtered.append( ext );
500 extension = filtered.join( "," );
503 void sigChildHandler(int)
505 // since waitpid and write change errno, we have to save it and restore it
506 // (Richard Stevens, Advanced programming in the Unix Environment)
507 int saved_errno = errno;
509 while (waitpid(-1, 0, WNOHANG) == 0)
512 errno = saved_errno;
516 int main( int argc, char **argv )
518 KAboutData aboutData( "nspluginscan", "nsplugin", ki18n("nspluginscan"),
519 "0.3", ki18n("nspluginscan"), KAboutData::License_GPL,
520 ki18n("(c) 2000,2001 by Stefan Schimanski") );
522 KCmdLineArgs::init( argc, argv, &aboutData );
524 KCmdLineOptions options;
525 options.add("verbose", ki18n("Show progress output for GUI"));
526 KCmdLineArgs::addCmdLineOptions( options );
527 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
529 showProgress = args->isSet("verbose");
530 if (showProgress) {
531 printf("10\n"); fflush(stdout);
534 KApplication app(false);
536 // Set up SIGCHLD handler
537 struct sigaction act;
538 act.sa_handler=sigChildHandler;
539 sigemptyset(&(act.sa_mask));
540 sigaddset(&(act.sa_mask), SIGCHLD);
541 // Make sure we don't block this signal. gdb tends to do that :-(
542 sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0);
544 act.sa_flags = SA_NOCLDSTOP;
546 // CC: take care of SunOS which automatically restarts interrupted system
547 // calls (and thus does not have SA_RESTART)
549 #ifdef SA_RESTART
550 act.sa_flags |= SA_RESTART;
551 #endif
553 struct sigaction oldact;
554 sigaction( SIGCHLD, &act, &oldact );
557 // set up the paths used to look for plugins
558 QStringList searchPaths = getSearchPaths();
559 QStringList mimeInfoList;
561 infoConfig = new KConfig( KGlobal::dirs()->saveLocation("data", "nsplugins") +
562 "/pluginsinfo" );
563 infoConfig->group("<default>").writeEntry( "number", 0 );
565 // open the cache file for the mime information
566 QString cacheName = KGlobal::dirs()->saveLocation("data", "nsplugins")+"/cache";
567 kDebug(1433) << "Creating MIME cache file " << cacheName;
568 QFile cachef(cacheName);
569 if (!cachef.open(QIODevice::WriteOnly))
570 return -1;
571 QTextStream cache(&cachef);
572 if (showProgress) {
573 printf("20\n"); fflush(stdout);
576 // read in the plugins mime information
577 kDebug(1433) << "Scanning directories" << searchPaths;
578 int count = searchPaths.count();
579 int i = 0;
580 for ( QStringList::const_iterator it = searchPaths.constBegin();
581 it != searchPaths.constEnd(); ++it, ++i)
583 if ((*it).isEmpty())
584 continue;
585 scanDirectory( *it, mimeInfoList, cache );
586 if (showProgress) {
587 printf("%d\n", 25 + (50*i) / count ); fflush(stdout);
591 if (showProgress) {
592 printf("75\n"); fflush(stdout);
595 // We're done with forking,
596 // KProcess needs SIGCHLD to be reset to what it was initially
597 sigaction( SIGCHLD, &oldact, 0 );
599 // delete old mime types
600 kDebug(1433) << "Removing old mimetypes";
601 const QStringList oldMimes = deletePluginMimeTypes();
602 bool mimeTypesChanged = !oldMimes.isEmpty();
604 if (showProgress) {
605 printf("80\n"); fflush(stdout);
608 // write mimetype files
609 kDebug(1433) << "Creating MIME type descriptions";
610 QStringList mimeTypes;
611 for ( QStringList::const_iterator it=mimeInfoList.constBegin();
612 it!=mimeInfoList.constEnd(); ++it) {
614 kDebug(1433) << "Handling MIME type " << *it;
616 QStringList info = (*it).split(':', QString::KeepEmptyParts);
617 if ( info.count()==4 ) {
618 QString pluginName = info[0];
619 QString type = info[1].toLower();
620 QString extension = info[2];
621 QString desc = info[3];
623 // append to global mime type list
624 if ( !mimeTypes.contains(type) ) {
625 kDebug(1433) << " - mimeType=" << type;
626 mimeTypes.append( type );
628 // write or update mime type file, if
629 // 1) it doesn't exist in ksycoca (meaning we never heard of it)
630 // 2) or we just deleted it [it's still in ksycoca though]
631 // This prevents noticing that a shared-mime-info upgrade brought
632 // us a mimetype we needed; but doing this right requires launching
633 // kbuildsycoca4 after removing mimetypes above, and that's really slow
634 bool mustWriteMimeType = KMimeType::mimeType(type).isNull();
635 if (!mustWriteMimeType)
636 mustWriteMimeType = oldMimes.contains(type);
637 if ( mustWriteMimeType ) {
638 kDebug(1433) << " - creating MIME type description";
639 removeExistingExtensions( extension );
640 generateMimeType( type, extension, pluginName, desc );
641 mimeTypesChanged = true;
642 } else {
643 kDebug(1433) << " - already exists";
649 // done with new mimetypes, run update-mime-database
650 if (mimeTypesChanged) {
651 MimeTypeWriter::runUpdateMimeDatabase();
652 // note that we'll run kbuildsycoca below anyway
655 if (showProgress) {
656 printf("85\n"); fflush(stdout);
659 // close files
660 kDebug(1433) << "Closing cache file";
661 cachef.close();
663 infoConfig->sync();
664 delete infoConfig;
666 // write plugin lib service file
667 writeServicesFile( mimeTypes );
668 if (showProgress) {
669 printf("90\n"); fflush(stdout);
672 // Tell kded to update sycoca database.
673 QDBusInterface kbuildsycoca("org.kde.kded", "/kbuildsycoca",
674 "org.kde.kded");
675 if (kbuildsycoca.isValid())
676 kbuildsycoca.call("recreate");