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>
38 #include <QTextStream>
42 #include <QtDBus/QtDBus>
44 #include <kapplication.h>
47 #include <kstandarddirs.h>
48 #include <klibloader.h>
50 #include <kconfiggroup.h>
52 #include <kdesktopfile.h>
53 #include <kservicetype.h>
54 #include <kmimetype.h>
55 #include <kcmdlineargs.h>
56 #include <kaboutdata.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))
71 #define KDE_GNUC_PREREQ(maj,min) 0
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
)
85 extern "C" void* __builtin_vec_new(size_t s
)
87 return operator new[](s
);
90 extern "C" void __builtin_vec_delete(void* p
)
95 extern "C" void __pure_virtual()
101 KConfig
*infoConfig
= 0;
103 static const char s_mimeTypeMarker
[] = "MimeType generated by nspluginscan";
106 static bool isPluginMimeType( const QString
&fname
)
109 if (file
.open(QIODevice::ReadOnly
)) {
110 const QByteArray firstLine
= file
.readLine();
111 if (!firstLine
.startsWith("<?xml")) {
112 kWarning() << "Malformed XML file:" << fname
;
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
119 return secondLine
.startsWith(s_mimeTypeMarker
) || firstLine
.contains(s_mimeTypeMarker
);
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";
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
;
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
<< ')';
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('/');
167 kDebug(1433) << "Invalid MIME type " << mime
;
168 kDebug(1433) << "<- generateMimeType";
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
);
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
)
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)
226 static int tryCheck(int write_fd
, const QString
&absFile
)
228 KLibrary
*_handle
= KLibLoader::self()->library( QFile::encodeName(absFile
) );
230 kDebug(1433) << " - open failed with message " <<
231 KLibLoader::self()->lastErrorMessage() << ", skipping " << endl
;
235 // ask for name and description
236 QString name
= i18n("Unnamed plugin");
239 NPError (*func_GetValue
)(void *, NPPVariable
, void *) =
240 (NPError(*)(void *, NPPVariable
, void *))
241 _handle
->resolveFunction("NP_GetValue");
242 if ( func_GetValue
) {
246 NPError err
= func_GetValue( 0, NPPVpluginNameString
,
248 if ( err
==NPERR_NO_ERROR
)
249 name
= QString::fromLatin1( buf
);
250 kDebug() << "name = " << name
;
253 NPError nperr
= func_GetValue( 0, NPPVpluginDescriptionString
,
255 if ( nperr
==NPERR_NO_ERROR
)
256 description
= QString::fromLatin1( buf
);
257 kDebug() << "description = " << description
;
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
) );
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
) );
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
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)
292 stream_file
.open(write_fd
, QIODevice::WriteOnly
);
293 QDataStream
stream(&stream_file
);
295 // return the gathered info to the parent
297 stream
<< description
;
303 void scanDirectory( const QString
&dir
, QStringList
&mimeInfoList
,
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
;
316 for (unsigned int i
=0; i
<files
.count(); i
++) {
318 int j
= files
[i
].lastIndexOf('.');
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
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
;
347 // fork, so that a crash in the plugin won't stop the scanning of other plugins
349 if (pipe(pipes
) != 0) continue;
351 int loader_pid
= fork();
353 if (loader_pid
== -1) {
356 } else if (loader_pid
== 0) {
359 KCrash::setCrashHandler(segv_handler
);
360 _exit(tryCheck(pipes
[1], absFile
));
365 m_buffer
.open(QIODevice::WriteOnly
);
368 q_read_pipe
.open(pipes
[0], QIODevice::ReadOnly
);
370 char *data
= (char *)malloc(4096);
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
);
380 close(pipes
[0]); // we no longer need the pipe's reading end
382 // close the buffer and open for reading (from the start)
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
;
393 stream
>> description
;
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();
420 for ( ; token
!=tokens
.constEnd(); ++token
)
421 cache
<< ":" << *token
;
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() )
441 static int depth
= 0; // avoid recursion because of symlink circles
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
);
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
;
460 if ( f
.open(QIODevice::WriteOnly
) ) {
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
;
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 *.*!
492 KMimeType::Ptr mime
= KMimeType::findByUrl( KUrl("file:///foo."+ext
),
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)
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");
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)
551 act
.sa_flags
|= SA_RESTART
;
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") +
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
))
572 QTextStream
cache(&cachef
);
574 printf("20\n"); fflush(stdout
);
577 // read in the plugins mime information
578 kDebug(1433) << "Scanning directories";
579 int count
= searchPaths
.count();
581 for ( QStringList::const_iterator it
= searchPaths
.constBegin();
582 it
!= searchPaths
.constEnd(); ++it
, ++i
)
586 scanDirectory( *it
, mimeInfoList
, cache
);
588 printf("%d\n", 25 + (50*i
) / count
); fflush(stdout
);
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();
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;
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
657 printf("85\n"); fflush(stdout
);
661 kDebug(1433) << "Closing cache file";
667 // write plugin lib service file
668 writeServicesFile( mimeTypes
);
670 printf("90\n"); fflush(stdout
);
673 // Tell kded to update sycoca database.
674 QDBusInterface
kbuildsycoca("org.kde.kded", "/kbuildsycoca",
676 if (kbuildsycoca
.isValid())
677 kbuildsycoca
.call("recreate");