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