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 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
)
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)
225 static int tryCheck(int write_fd
, const QString
&absFile
)
227 KLibrary
*_handle
= KLibLoader::self()->library( QFile::encodeName(absFile
) );
229 kDebug(1433) << " - open failed with message " <<
230 KLibLoader::self()->lastErrorMessage() << ", skipping " << endl
;
234 // ask for name and description
235 QString name
= i18n("Unnamed plugin");
238 NPError (*func_GetValue
)(void *, NPPVariable
, void *) =
239 (NPError(*)(void *, NPPVariable
, void *))
240 _handle
->resolveFunction("NP_GetValue");
241 if ( func_GetValue
) {
245 NPError err
= func_GetValue( 0, NPPVpluginNameString
,
247 if ( err
==NPERR_NO_ERROR
)
248 name
= QString::fromLatin1( buf
);
249 kDebug() << "name = " << name
;
252 NPError nperr
= func_GetValue( 0, NPPVpluginDescriptionString
,
254 if ( nperr
==NPERR_NO_ERROR
)
255 description
= QString::fromLatin1( buf
);
256 kDebug() << "description = " << description
;
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
) );
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
) );
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
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)
291 stream_file
.open(write_fd
, QIODevice::WriteOnly
);
292 QDataStream
stream(&stream_file
);
294 // return the gathered info to the parent
296 stream
<< description
;
302 void scanDirectory( const QString
&dir
, QStringList
&mimeInfoList
,
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
;
315 for (unsigned int i
=0; i
<files
.count(); i
++) {
317 int j
= files
[i
].lastIndexOf('.');
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
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
;
346 // fork, so that a crash in the plugin won't stop the scanning of other plugins
348 if (pipe(pipes
) != 0) continue;
350 int loader_pid
= fork();
352 if (loader_pid
== -1) {
355 } else if (loader_pid
== 0) {
358 KCrash::setCrashHandler(segv_handler
);
359 _exit(tryCheck(pipes
[1], absFile
));
364 m_buffer
.open(QIODevice::WriteOnly
);
367 q_read_pipe
.open(pipes
[0], QIODevice::ReadOnly
);
369 char *data
= (char *)malloc(4096);
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
);
379 close(pipes
[0]); // we no longer need the pipe's reading end
381 // close the buffer and open for reading (from the start)
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
;
392 stream
>> description
;
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();
419 for ( ; token
!=tokens
.constEnd(); ++token
)
420 cache
<< ":" << *token
;
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() )
440 static int depth
= 0; // avoid recursion because of symlink circles
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
);
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
;
459 if ( f
.open(QIODevice::WriteOnly
) ) {
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
;
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 *.*!
491 KMimeType::Ptr mime
= KMimeType::findByUrl( KUrl("file:///foo."+ext
),
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)
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");
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)
550 act
.sa_flags
|= SA_RESTART
;
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") +
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
))
571 QTextStream
cache(&cachef
);
573 printf("20\n"); fflush(stdout
);
576 // read in the plugins mime information
577 kDebug(1433) << "Scanning directories" << searchPaths
;
578 int count
= searchPaths
.count();
580 for ( QStringList::const_iterator it
= searchPaths
.constBegin();
581 it
!= searchPaths
.constEnd(); ++it
, ++i
)
585 scanDirectory( *it
, mimeInfoList
, cache
);
587 printf("%d\n", 25 + (50*i
) / count
); fflush(stdout
);
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();
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;
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
656 printf("85\n"); fflush(stdout
);
660 kDebug(1433) << "Closing cache file";
666 // write plugin lib service file
667 writeServicesFile( mimeTypes
);
669 printf("90\n"); fflush(stdout
);
672 // Tell kded to update sycoca database.
673 QDBusInterface
kbuildsycoca("org.kde.kded", "/kbuildsycoca",
675 if (kbuildsycoca
.isValid())
676 kbuildsycoca
.call("recreate");