00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "kmimetypefactory.h"
00021 #include "kmimetype.h"
00022 #include "kfoldermimetype.h"
00023 #include <ksycoca.h>
00024 #include <ksycocadict.h>
00025 #include <kshell.h>
00026 #include <kdebug.h>
00027
00028 K_GLOBAL_STATIC(KSycocaFactorySingleton<KMimeTypeFactory>, kMimeTypeFactoryInstance)
00029
00030 KMimeTypeFactory::KMimeTypeFactory()
00031 : KSycocaFactory( KST_KMimeTypeFactory ),
00032 m_fastPatternOffset(0),
00033 m_highWeightPatternOffset(0),
00034 m_lowWeightPatternOffset(0),
00035 m_parentsMapOffset(0),
00036 m_highWeightPatternsLoaded(false),
00037 m_lowWeightPatternsLoaded(false),
00038 m_parentsMapLoaded(false),
00039 m_magicFilesParsed(false)
00040 {
00041 kMimeTypeFactoryInstance->instanceCreated(this);
00042 if (!KSycoca::self()->isBuilding()) {
00043 QDataStream* str = stream();
00044 Q_ASSERT(str);
00045
00046 qint32 i;
00047 (*str) >> i;
00048 m_fastPatternOffset = i;
00049 (*str) >> i;
00050
00051
00052
00053
00054
00055 qint32 n;
00056 (*str) >> n;
00057 QString str1, str2;
00058 for(;n;n--) {
00059 KSycocaEntry::read(*str, str1);
00060 KSycocaEntry::read(*str, str2);
00061 m_aliases.insert(str1, str2);
00062 }
00063
00064 (*str) >> i;
00065 m_highWeightPatternOffset = i;
00066 (*str) >> i;
00067 m_lowWeightPatternOffset = i;
00068 (*str) >> i;
00069 m_parentsMapOffset = i;
00070
00071 const int saveOffset = str->device()->pos();
00072
00073 m_fastPatternDict = new KSycocaDict(str, m_fastPatternOffset);
00074 str->device()->seek(saveOffset);
00075 } else {
00076 m_parentsMapLoaded = true;
00077 }
00078 }
00079
00080 KMimeTypeFactory::~KMimeTypeFactory()
00081 {
00082 if (kMimeTypeFactoryInstance.exists())
00083 kMimeTypeFactoryInstance->instanceDestroyed(this);
00084 delete m_fastPatternDict;
00085 }
00086
00087 KMimeTypeFactory * KMimeTypeFactory::self()
00088 {
00089 return kMimeTypeFactoryInstance->self();
00090 }
00091
00092 KMimeType::Ptr KMimeTypeFactory::findMimeTypeByName(const QString &_name, KMimeType::FindByNameOption options)
00093 {
00094 if (!sycocaDict()) return KMimeType::Ptr();
00095 assert (!KSycoca::self()->isBuilding());
00096
00097 QString name = _name;
00098 if (options & KMimeType::ResolveAliases) {
00099 AliasesMap::const_iterator it = m_aliases.constFind(_name);
00100 if (it != m_aliases.constEnd())
00101 name = *it;
00102 }
00103
00104 int offset = sycocaDict()->find_string( name );
00105 if (!offset) return KMimeType::Ptr();
00106 KMimeType::Ptr newMimeType(createEntry(offset));
00107
00108
00109 if (newMimeType && (newMimeType->name() != name))
00110 {
00111
00112 newMimeType = 0;
00113 }
00114 return newMimeType;
00115 }
00116
00117 bool KMimeTypeFactory::checkMimeTypes()
00118 {
00119 QDataStream *str = KSycoca::self()->findFactory( factoryId() );
00120 if (!str) return false;
00121
00122
00123 return !isEmpty();
00124 }
00125
00126 KMimeType * KMimeTypeFactory::createEntry(int offset) const
00127 {
00128 KMimeType *newEntry = 0;
00129 KSycocaType type;
00130 QDataStream *str = KSycoca::self()->findEntry(offset, type);
00131 if (!str) return 0;
00132
00133 switch(type)
00134 {
00135 case KST_KMimeType:
00136 case KST_KDEDesktopMimeType:
00137 newEntry = new KMimeType(*str, offset);
00138 break;
00139 case KST_KFolderMimeType:
00140 newEntry = new KFolderMimeType(*str, offset);
00141 break;
00142
00143 default:
00144 kError(7011) << QString("KMimeTypeFactory: unexpected object entry in KSycoca database (type = %1)").arg((int)type) << endl;
00145 break;
00146 }
00147 if (newEntry && !newEntry->isValid())
00148 {
00149 kError(7011) << "KMimeTypeFactory: corrupt object in KSycoca database!\n" << endl;
00150 delete newEntry;
00151 newEntry = 0;
00152 }
00153 return newEntry;
00154 }
00155
00156
00157 QString KMimeTypeFactory::resolveAlias(const QString& mime) const
00158 {
00159 return m_aliases.value(mime);
00160 }
00161
00162 QList<KMimeType::Ptr> KMimeTypeFactory::findFromFileName( const QString &filename, QString *matchingExtension )
00163 {
00164
00165 if (!stream()) return QList<KMimeType::Ptr>();
00166
00167
00168
00169
00170
00171 QList<KMimeType::Ptr> mimeList = findFromFileNameHelper(filename, matchingExtension);
00172 if (mimeList.isEmpty()) {
00173 const QString lowerCase = filename.toLower();
00174 if (lowerCase != filename)
00175 mimeList = findFromFileNameHelper(lowerCase, matchingExtension);
00176 }
00177 return mimeList;
00178 }
00179
00180 QList<KMimeType::Ptr> KMimeTypeFactory::findFromFastPatternDict(const QString &extension)
00181 {
00182 QList<KMimeType::Ptr> mimeList;
00183 if (!m_fastPatternDict) return mimeList;
00184
00185
00186 const QList<int> offsetList = m_fastPatternDict->findMultiString(extension);
00187 if (offsetList.isEmpty()) return mimeList;
00188 const QString expectedPattern = "*."+extension;
00189 foreach(int offset, offsetList) {
00190 KMimeType::Ptr newMimeType(createEntry(offset));
00191
00192 if (newMimeType && newMimeType->patterns().contains(expectedPattern)) {
00193 mimeList.append(newMimeType);
00194 }
00195 }
00196 return mimeList;
00197 }
00198
00199 bool KMimeTypeFactory::matchFileName( const QString &filename, const QString &pattern )
00200 {
00201 const int pattern_len = pattern.length();
00202 if (!pattern_len)
00203 return false;
00204 const int len = filename.length();
00205
00206 const int starCount = pattern.count('*');
00207
00208
00209 if (pattern[0] == '*' && pattern.indexOf('[') == -1 && starCount == 1)
00210 {
00211 if ( len + 1 < pattern_len ) return false;
00212
00213 const QChar *c1 = pattern.unicode() + pattern_len - 1;
00214 const QChar *c2 = filename.unicode() + len - 1;
00215 int cnt = 1;
00216 while (cnt < pattern_len && *c1-- == *c2--)
00217 ++cnt;
00218 return cnt == pattern_len;
00219 }
00220
00221
00222 if (starCount == 1 && pattern[pattern_len - 1] == '*') {
00223 if ( len + 1 < pattern_len ) return false;
00224 if (pattern[0] == '*')
00225 return filename.indexOf(pattern.mid(1, pattern_len - 2)) != -1;
00226
00227 const QChar *c1 = pattern.unicode();
00228 const QChar *c2 = filename.unicode();
00229 int cnt = 1;
00230 while (cnt < pattern_len && *c1++ == *c2++)
00231 ++cnt;
00232 return cnt == pattern_len;
00233 }
00234
00235
00236 if (pattern.indexOf('[') == -1 && starCount == 0 && pattern.indexOf('?'))
00237 return (pattern == filename);
00238
00239
00240 QRegExp rx(pattern);
00241 rx.setPatternSyntax(QRegExp::Wildcard);
00242 return rx.exactMatch(filename);
00243 }
00244
00245 void KMimeTypeFactory::findFromOtherPatternList(QList<KMimeType::Ptr>& matchingMimeTypes,
00246 const QString &fileName,
00247 QString& foundExt,
00248 bool highWeight)
00249 {
00250 OtherPatternList& patternList = highWeight ? m_highWeightPatterns : m_lowWeightPatterns;
00251 bool& loaded = highWeight ? m_highWeightPatternsLoaded : m_lowWeightPatternsLoaded;
00252 if ( !loaded ) {
00253 loaded = true;
00254
00255 QDataStream* str = stream();
00256 str->device()->seek( highWeight ? m_highWeightPatternOffset : m_lowWeightPatternOffset );
00257
00258 QString pattern;
00259 qint32 mimetypeOffset;
00260 qint32 weight;
00261 Q_FOREVER {
00262 KSycocaEntry::read(*str, pattern);
00263 if (pattern.isEmpty())
00264 break;
00265 (*str) >> mimetypeOffset;
00266 (*str) >> weight;
00267 patternList.push_back(OtherPattern(pattern, mimetypeOffset, weight));
00268 }
00269 }
00270
00271 int matchingPatternLength = 0;
00272 qint32 lastMatchedWeight = 0;
00273 if (!highWeight && !matchingMimeTypes.isEmpty()) {
00274
00275 matchingPatternLength = foundExt.length() + 2;
00276 lastMatchedWeight = 50;
00277 }
00278 OtherPatternList::const_iterator it = patternList.constBegin();
00279 const OtherPatternList::const_iterator end = patternList.constEnd();
00280 for ( ; it != end; ++it ) {
00281 const OtherPattern op = *it;
00282 if ( matchFileName( fileName, op.pattern ) ) {
00283
00284 if (op.weight < lastMatchedWeight)
00285 break;
00286 if (lastMatchedWeight > 0 && op.weight > lastMatchedWeight)
00287 kWarning(7009) << "Assumption failed; globs2 weights not sorted correctly"
00288 << op.weight << ">" << lastMatchedWeight;
00289
00290 if (op.pattern.length() < matchingPatternLength) {
00291 continue;
00292 } else if (op.pattern.length() > matchingPatternLength) {
00293
00294 matchingMimeTypes.clear();
00295
00296 matchingPatternLength = op.pattern.length();
00297 }
00298 KMimeType *newMimeType = createEntry( op.offset );
00299 assert (newMimeType && newMimeType->isType( KST_KMimeType ));
00300 matchingMimeTypes.push_back( KMimeType::Ptr( newMimeType ) );
00301 if (op.pattern.startsWith("*."))
00302 foundExt = op.pattern.mid(2);
00303 }
00304 }
00305 }
00306
00307 QList<KMimeType::Ptr> KMimeTypeFactory::findFromFileNameHelper(const QString &fileName, QString *pMatchingExtension)
00308 {
00309
00310 QList<KMimeType::Ptr> matchingMimeTypes;
00311 QString foundExt;
00312 findFromOtherPatternList(matchingMimeTypes, fileName, foundExt, true);
00313 if (matchingMimeTypes.isEmpty()) {
00314
00315
00316
00317 const int lastDot = fileName.lastIndexOf('.');
00318 if (lastDot != -1) {
00319 const int ext_len = fileName.length() - lastDot - 1;
00320 const QString simpleExtension = fileName.right( ext_len );
00321
00322 matchingMimeTypes = findFromFastPatternDict(simpleExtension);
00323 if (!matchingMimeTypes.isEmpty()) {
00324 foundExt = simpleExtension;
00325
00326
00327 }
00328 }
00329
00330
00331 findFromOtherPatternList(matchingMimeTypes, fileName, foundExt, false);
00332 }
00333 if (pMatchingExtension)
00334 *pMatchingExtension = foundExt;
00335 return matchingMimeTypes;
00336 }
00337
00338
00339 KMimeType::Ptr KMimeTypeFactory::findFromContent(QIODevice* device, WhichPriority whichPriority, int* accuracy, QByteArray& beginning)
00340 {
00341 Q_ASSERT(device->isOpen());
00342 if (device->size() == 0) {
00343 if (accuracy)
00344 *accuracy = 100;
00345 return findMimeTypeByName("application/x-zerosize");
00346 }
00347
00348 if (!m_magicFilesParsed) {
00349 parseMagic();
00350 m_magicFilesParsed = true;
00351 }
00352
00353 Q_FOREACH ( const KMimeMagicRule& rule, m_magicRules ) {
00354
00355
00356 if ( ( whichPriority == AllRules ) ||
00357 ( (rule.priority() >= 80) == (whichPriority == HighPriorityRules) ) ) {
00358 if (rule.match(device, beginning)) {
00359 if (accuracy)
00360 *accuracy = rule.priority();
00361 return findMimeTypeByName(rule.mimetype());
00362 }
00363 }
00364
00365 if (whichPriority == HighPriorityRules && rule.priority() < 80)
00366 break;
00367 }
00368
00369
00370 if (whichPriority != HighPriorityRules) {
00371
00372 if (!KMimeType::isBufferBinaryData(beginning)) {
00373 if (accuracy)
00374 *accuracy = 5;
00375 return findMimeTypeByName("text/plain");
00376 }
00377 if (accuracy)
00378 *accuracy = 0;
00379 return KMimeType::defaultMimeTypePtr();
00380 }
00381
00382 return KMimeType::Ptr();
00383 }
00384
00385 KMimeType::List KMimeTypeFactory::allMimeTypes()
00386 {
00387 KMimeType::List result;
00388 const KSycocaEntry::List list = allEntries();
00389 for( KSycocaEntry::List::ConstIterator it = list.begin();
00390 it != list.end();
00391 ++it)
00392 {
00393 Q_ASSERT( (*it)->isType( KST_KMimeType ) );
00394 result.append( KMimeType::Ptr::staticCast( *it ) );
00395 }
00396 return result;
00397 }
00398
00399 QStringList KMimeTypeFactory::parents(const QString& mime)
00400 {
00401 if (!m_parentsMapLoaded) {
00402 m_parentsMapLoaded = true;
00403 Q_ASSERT(m_parents.isEmpty());
00404 QDataStream* str = stream();
00405 str->device()->seek(m_parentsMapOffset);
00406 qint32 n;
00407 (*str) >> n;
00408 QString str1, str2;
00409 for(;n;n--) {
00410 KSycocaEntry::read(*str, str1);
00411 KSycocaEntry::read(*str, str2);
00412 m_parents.insert(str1, str2.split('|'));
00413 }
00414
00415 }
00416 return m_parents.value(mime);
00417 }
00418
00419 #include <arpa/inet.h>
00420 #include <kstandarddirs.h>
00421 #include <QFile>
00422
00423
00424 static bool mimeMagicRuleCompare(const KMimeMagicRule& lhs, const KMimeMagicRule& rhs) {
00425 return lhs.priority() > rhs.priority();
00426 }
00427
00428
00429 void KMimeTypeFactory::parseMagic()
00430 {
00431 const QStringList magicFiles = KGlobal::dirs()->findAllResources("xdgdata-mime", "magic");
00432
00433 QListIterator<QString> magicIter( magicFiles );
00434 magicIter.toBack();
00435 while (magicIter.hasPrevious()) {
00436 const QString fileName = magicIter.previous();
00437 QFile magicFile(fileName);
00438 kDebug(7009) << "Now parsing " << fileName;
00439 if (magicFile.open(QIODevice::ReadOnly))
00440 m_magicRules += parseMagicFile(&magicFile, fileName);
00441 }
00442 qSort(m_magicRules.begin(), m_magicRules.end(), mimeMagicRuleCompare);
00443 }
00444
00445 static char readNumber(qint64& value, QIODevice* file)
00446 {
00447 char ch;
00448 while (file->getChar(&ch)) {
00449 if (ch < '0' || ch > '9')
00450 return ch;
00451 value = 10 * value + ch - '0';
00452 }
00453
00454 return '\0';
00455 }
00456
00457
00458 #define MAKE_LITTLE_ENDIAN16(val) val = (quint16)(((quint16)(val) << 8)|((quint16)(val) >> 8))
00459
00460 #define MAKE_LITTLE_ENDIAN32(val) \
00461 val = (((quint32)(val) & 0xFF000000U) >> 24) | \
00462 (((quint32)(val) & 0x00FF0000U) >> 8) | \
00463 (((quint32)(val) & 0x0000FF00U) << 8) | \
00464 (((quint32)(val) & 0x000000FFU) << 24)
00465
00466 QList<KMimeMagicRule> KMimeTypeFactory::parseMagicFile(QIODevice* file, const QString& fileName) const
00467 {
00468 QList<KMimeMagicRule> rules;
00469 QByteArray header = file->read(12);
00470 if (header != QByteArray::fromRawData("MIME-Magic\0\n", 12)) {
00471 kWarning(7009) << "Invalid magic file " << fileName << " starts with " << header;
00472 return rules;
00473 }
00474 QList<KMimeMagicMatch> matches;
00475 int priority = 0;
00476 QString mimeTypeName;
00477
00478 Q_FOREVER {
00479 char ch = '\0';
00480 bool chOk = file->getChar(&ch);
00481
00482 if (!chOk || ch == '[') {
00483
00484 if (!mimeTypeName.isEmpty()) {
00485 rules.append(KMimeMagicRule(mimeTypeName, priority, matches));
00486 matches.clear();
00487 mimeTypeName.clear();
00488 }
00489 if (file->atEnd())
00490 break;
00491
00492
00493 const QString line = file->readLine();
00494 const int pos = line.indexOf(':');
00495 if (pos == -1) {
00496 kWarning(7009) << "Syntax error in " << mimeTypeName
00497 << " ':' not present in section name" << endl;
00498 break;
00499 }
00500 priority = line.left(pos).toInt();
00501 mimeTypeName = line.mid(pos+1);
00502 mimeTypeName = mimeTypeName.left(mimeTypeName.length()-2);
00503
00504
00505 } else {
00506
00507
00508
00509 qint64 indent = 0;
00510 if (ch != '>') {
00511 indent = ch - '0';
00512 ch = readNumber(indent, file);
00513 if (ch != '>') {
00514 kWarning(7009) << "Invalid magic file " << fileName << " '>' not found, got " << ch << " at pos " << file->pos();
00515 break;
00516 }
00517 }
00518
00519 KMimeMagicMatch match;
00520 match.m_rangeStart = 0;
00521 ch = readNumber(match.m_rangeStart, file);
00522 if (ch != '=') {
00523 kWarning(7009) << "Invalid magic file " << fileName << " '=' not found";
00524 break;
00525 }
00526
00527 char lengthBuffer[2];
00528 if (file->read(lengthBuffer, 2) != 2)
00529 break;
00530 const short valueLength = ntohs(*(short*)lengthBuffer);
00531
00532
00533
00534 match.m_data.resize(valueLength);
00535 if (file->read(match.m_data.data(), valueLength) != valueLength)
00536 break;
00537
00538 match.m_rangeLength = 1;
00539 bool invalidLine = false;
00540
00541 if (!file->getChar(&ch))
00542 break;
00543 qint64 wordSize = 1;
00544
00545 Q_FOREVER {
00546
00547 switch (ch) {
00548 case '\n':
00549 break;
00550 case '&':
00551 match.m_mask.resize(valueLength);
00552 if (file->read(match.m_mask.data(), valueLength) != valueLength)
00553 invalidLine = true;
00554 if (!file->getChar(&ch))
00555 invalidLine = true;
00556 break;
00557 case '~': {
00558 wordSize = 0;
00559 ch = readNumber(wordSize, file);
00560
00561 break;
00562 }
00563 case '+':
00564
00565 match.m_rangeLength = 0;
00566 ch = readNumber(match.m_rangeLength, file);
00567 if (ch == '\n')
00568 break;
00569
00570 default:
00571
00572
00573
00574
00575 while (ch != '\n' && !file->atEnd()) {
00576 file->getChar(&ch);
00577 }
00578 invalidLine = true;
00579 kDebug(7009) << "invalid line - garbage found - ch=" << ch;
00580 break;
00581 }
00582 if (ch == '\n' || invalidLine)
00583 break;
00584 }
00585 if (!invalidLine) {
00586
00587 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
00588 if (wordSize > 1) {
00589
00590 if ((wordSize != 2 && wordSize != 4) || (valueLength % wordSize != 0))
00591 continue;
00592 char* data = match.m_data.data();
00593 char* mask = match.m_mask.data();
00594 for (int i = 0; i < valueLength; i += wordSize) {
00595 if (wordSize == 2)
00596 MAKE_LITTLE_ENDIAN16( *((quint16 *) data + i) );
00597 else if (wordSize == 4)
00598 MAKE_LITTLE_ENDIAN32( *((quint32 *) data + i) );
00599 if (!match.m_mask.isEmpty()) {
00600 if (wordSize == 2)
00601 MAKE_LITTLE_ENDIAN16( *((quint16 *) mask + i) );
00602 else if (wordSize == 4)
00603 MAKE_LITTLE_ENDIAN32( *((quint32 *) mask + i) );
00604 }
00605 }
00606
00607 }
00608 #endif
00609
00610 if (indent == 0) {
00611 matches.append(match);
00612 } else {
00613 KMimeMagicMatch* m = &matches.last();
00614 Q_ASSERT(m);
00615 for (int i = 1 ; i < indent; ++i) {
00616 m = &m->m_subMatches.last();
00617 Q_ASSERT(m);
00618 }
00619 m->m_subMatches.append(match);
00620 }
00621 }
00622 }
00623 }
00624 return rules;
00625 }