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