kmimemagic.cpp
00001 /* This file is part of the TDE libraries 00002 Copyright (C) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net> 00003 00004 Small portions (the original KDE interface and utime code) are: 00005 Copyright (C) 2000 Fritz Elfert <fritz@kde.org> 00006 Copyright (C) 2004 Allan Sandfeld Jensen <kde@carewolf.com> 00007 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Library General Public 00010 License version 2 as published by the Free Software Foundation. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "config.h" 00024 #include "kmimemagic.h" 00025 #include <kdebug.h> 00026 #include <tdeapplication.h> 00027 #include <tqfile.h> 00028 #include <ksimpleconfig.h> 00029 #include <kstandarddirs.h> 00030 #include <kstaticdeleter.h> 00031 #include <klargefile.h> 00032 #include <assert.h> 00033 00034 #include <magic.h> 00035 00036 #ifndef MAGIC_MIME_TYPE 00037 #define MAGIC_MIME_TYPE MAGIC_MIME 00038 #endif 00039 00040 // Taken from file/file.h 00041 // Keep in sync with that header! 00042 #define FILE_LOAD 0 00043 00044 static void process(struct config_rec* conf, const TQString &); 00045 00046 KMimeMagic* KMimeMagic::s_pSelf; 00047 static KStaticDeleter<KMimeMagic> kmimemagicsd; 00048 00049 KMimeMagic* KMimeMagic::self() { 00050 if( !s_pSelf ) { 00051 initStatic(); 00052 } 00053 return s_pSelf; 00054 } 00055 00056 void KMimeMagic::initStatic() { 00057 s_pSelf = kmimemagicsd.setObject( s_pSelf, new KMimeMagic() ); 00058 s_pSelf->setFollowLinks( true ); 00059 } 00060 00061 #include <stdio.h> 00062 #include <unistd.h> 00063 #include <stdlib.h> 00064 #include <sys/wait.h> 00065 #include <sys/types.h> 00066 #include <sys/stat.h> 00067 #include <fcntl.h> 00068 #include <errno.h> 00069 #include <ctype.h> 00070 #include <time.h> 00071 #include <utime.h> 00072 #include <stdarg.h> 00073 #include <tqregexp.h> 00074 #include <tqstring.h> 00075 00076 #define MIME_INODE_DIR "inode/directory" 00077 #define MIME_INODE_CDEV "inode/chardevice" 00078 #define MIME_INODE_BDEV "inode/blockdevice" 00079 #define MIME_INODE_FIFO "inode/fifo" 00080 #define MIME_INODE_LINK "inode/link" 00081 #define MIME_INODE_SOCK "inode/socket" 00082 #define MIME_BINARY_UNREADABLE "application/x-unreadable" 00083 #define MIME_BINARY_ZEROSIZE "application/x-zerosize" 00084 00095 class KMimeMagicUtimeConf { 00096 public: 00097 KMimeMagicUtimeConf() { 00098 tmpDirs << TQString::fromLatin1("/tmp"); // default value 00099 00100 // The trick is that we also don't want the user to override globally set 00101 // directories. So we have to misuse TDEStandardDirs :} 00102 TQStringList confDirs = TDEGlobal::dirs()->resourceDirs( "config" ); 00103 if ( !confDirs.isEmpty() ) { 00104 TQString globalConf = confDirs.last() + "kmimemagicrc"; 00105 if ( TQFile::exists( globalConf ) ) { 00106 KSimpleConfig cfg( globalConf ); 00107 cfg.setGroup( "Settings" ); 00108 tmpDirs = cfg.readListEntry( "atimeDirs" ); 00109 } 00110 if ( confDirs.count() > 1 ) { 00111 TQString localConf = confDirs.first() + "kmimemagicrc"; 00112 if ( TQFile::exists( localConf ) ) { 00113 KSimpleConfig cfg( localConf ); 00114 cfg.setGroup( "Settings" ); 00115 tmpDirs += cfg.readListEntry( "atimeDirs" ); 00116 } 00117 } 00118 for ( TQStringList::Iterator it = tmpDirs.begin() ; it != tmpDirs.end() ; ++it ) { 00119 TQString dir = *it; 00120 if ( !dir.isEmpty() && dir[ dir.length()-1 ] != '/' ) { 00121 (*it) += '/'; 00122 } 00123 } 00124 } 00125 #if 0 00126 // debug code 00127 for ( TQStringList::Iterator it = tmpDirs.begin() ; it != tmpDirs.end() ; ++it ) { 00128 kdDebug(7018) << " atimeDir: " << *it << endl; 00129 } 00130 #endif 00131 } 00132 00133 bool restoreAccessTime( const TQString & file ) const { 00134 TQString dir = file.left( file.findRev( '/' ) ); 00135 bool res = tmpDirs.contains( dir ); 00136 //kdDebug(7018) << "restoreAccessTime " << file << " dir=" << dir << " result=" << res << endl; 00137 return res; 00138 } 00139 TQStringList tmpDirs; 00140 }; 00141 00142 TQString fixupMagicOutput(TQString &mime) { 00143 if (mime == "inode/x-empty") { 00144 return MIME_BINARY_ZEROSIZE; 00145 } 00146 else if (mime.contains("no read permission")) { 00147 return MIME_BINARY_UNREADABLE; 00148 } 00149 else { 00150 return mime; 00151 } 00152 } 00153 00154 /* current config */ 00155 struct config_rec { 00156 bool followLinks; 00157 TQString resultBuf; 00158 int accuracy; 00159 00160 magic_t magic; 00161 TQStringList databases; 00162 00163 KMimeMagicUtimeConf * utimeConf; 00164 }; 00165 00166 /* 00167 * apprentice - load configuration from the magic file. 00168 */ 00169 int KMimeMagic::apprentice( const TQString& magicfile ) { 00170 TQString maindatabase = magicfile; 00171 if (maindatabase == "") { 00172 #ifdef HAVE_LIBMAGIC_GETPATH 00173 maindatabase = magic_getpath(0, FILE_LOAD); 00174 #else 00175 maindatabase = TQString(LIBMAGIC_PATH); 00176 #endif 00177 if (maindatabase == "") { 00178 kdWarning() << k_funcinfo << "Unable to locate system mime magic database; mime type detection will not function correctly!" << endl; 00179 } 00180 } 00181 conf->databases.clear(); 00182 conf->databases.append(maindatabase); 00183 return magic_load(conf->magic, conf->databases[0].latin1()); 00184 } 00185 00186 /* 00187 * magic_process - process input file fn. Opens the file and reads a 00188 * fixed-size buffer to begin processing the contents. 00189 */ 00190 00191 void process(struct config_rec* conf, const TQString & fn) { 00192 KDE_struct_stat sb; 00193 TQCString fileName = TQFile::encodeName( fn ); 00194 00195 int magic_flags = MAGIC_ERROR|MAGIC_MIME_TYPE/*|MAGIC_DEBUG*/; 00196 if (conf->followLinks) { 00197 magic_flags |= MAGIC_SYMLINK; 00198 } 00199 magic_setflags(conf->magic, magic_flags); 00200 conf->resultBuf = TQString(magic_file(conf->magic, fileName)); 00201 conf->resultBuf = fixupMagicOutput(conf->resultBuf); 00202 00203 if ( conf->utimeConf && conf->utimeConf->restoreAccessTime( fn ) ) { 00204 /* 00205 * Try to restore access, modification times if read it. 00206 * This changes the "change" time (ctime), but we can't do anything 00207 * about that. 00208 */ 00209 struct utimbuf utbuf; 00210 utbuf.actime = sb.st_atime; 00211 utbuf.modtime = sb.st_mtime; 00212 (void) utime(fileName, &utbuf); 00213 } 00214 } 00215 00216 KMimeMagic::KMimeMagic() { 00217 // Magic file detection init 00218 TQString mimefile = locate( "mime", "magic" ); 00219 init( mimefile ); 00220 // Add snippets from share/config/magic/* 00221 TQStringList snippets = TDEGlobal::dirs()->findAllResources( "config", "magic/*.magic", true ); 00222 for ( TQStringList::Iterator it = snippets.begin() ; it != snippets.end() ; ++it ) { 00223 if ( !mergeConfig( *it ) ) { 00224 kdWarning() << k_funcinfo << "Failed to parse " << *it << endl; 00225 } 00226 } 00227 } 00228 00229 KMimeMagic::KMimeMagic(const TQString & _configfile) { 00230 init( _configfile ); 00231 } 00232 00233 void KMimeMagic::init( const TQString& _configfile ) { 00234 int result; 00235 conf = new config_rec; 00236 00237 /* initialize libmagic */ 00238 conf->magic = magic_open(MAGIC_MIME_TYPE); 00239 magicResult = NULL; 00240 conf->followLinks = false; 00241 00242 conf->utimeConf = 0L; // created on demand 00243 /* on the first time through we read the magic file */ 00244 result = apprentice(_configfile); 00245 if (result == -1) { 00246 return; 00247 } 00248 } 00249 00250 /* 00251 * The destructor. 00252 * Free the magic-table and other resources. 00253 */ 00254 KMimeMagic::~KMimeMagic() { 00255 if (conf) { 00256 magic_close(conf->magic); 00257 delete conf->utimeConf; 00258 delete conf; 00259 } 00260 delete magicResult; 00261 } 00262 00263 bool KMimeMagic::mergeConfig(const TQString & _configfile) { 00264 conf->databases.append(_configfile); 00265 TQString merged_databases = conf->databases.join(":"); 00266 #ifdef MAGIC_VERSION 00267 int magicvers = magic_version(); 00268 #else // MAGIC_VERSION 00269 int magicvers = 0; 00270 #endif // MAGIC_VERSION 00271 if ((magicvers < 512) || (magicvers >= 518)) { 00272 if (magic_load(conf->magic, merged_databases.latin1()) == 0) { 00273 return true; 00274 } 00275 else { 00276 return false; 00277 } 00278 } 00279 else { 00280 kdWarning(7018) << k_funcinfo << "KMimeMagic::mergeConfig disabled due to buggy libmagic version " << magicvers << endl; 00281 return false; 00282 } 00283 } 00284 00285 void KMimeMagic::setFollowLinks( bool _enable ) { 00286 conf->followLinks = _enable; 00287 } 00288 00289 KMimeMagicResult *KMimeMagic::findBufferType(const TQByteArray &array) { 00290 conf->resultBuf = TQString::null; 00291 if ( !magicResult ) { 00292 magicResult = new KMimeMagicResult(); 00293 } 00294 magicResult->setInvalid(); 00295 conf->accuracy = 100; 00296 00297 int nbytes = array.size(); 00298 if (nbytes == 0) { 00299 conf->resultBuf = MIME_BINARY_ZEROSIZE; 00300 } 00301 else { 00302 int magic_flags = MAGIC_ERROR|MAGIC_MIME_TYPE/*|MAGIC_DEBUG*/; 00303 if (conf->followLinks) { 00304 magic_flags |= MAGIC_SYMLINK; 00305 } 00306 magic_setflags(conf->magic, magic_flags); 00307 conf->resultBuf = TQString(magic_buffer(conf->magic, array.data(), nbytes)); 00308 conf->resultBuf = fixupMagicOutput(conf->resultBuf); 00309 } 00310 /* if we have any results, put them in the request structure */ 00311 magicResult->setMimeType(conf->resultBuf.stripWhiteSpace()); 00312 magicResult->setAccuracy(conf->accuracy); 00313 return magicResult; 00314 } 00315 00316 static void refineResult(KMimeMagicResult *r, const TQString & _filename) { 00317 TQString tmp = r->mimeType(); 00318 if (tmp.isEmpty()) 00319 return; 00320 if ( tmp == "text/x-c" || tmp == "text/x-objc" ) 00321 { 00322 if ( _filename.right(2) == ".h" ) 00323 tmp += "hdr"; 00324 else 00325 tmp += "src"; 00326 r->setMimeType(tmp); 00327 } 00328 else 00329 if ( tmp == "text/x-c++" ) 00330 { 00331 if ( _filename.endsWith(".h") 00332 || _filename.endsWith(".hh") 00333 || _filename.endsWith(".H") 00334 || !_filename.right(4).contains('.')) 00335 tmp += "hdr"; 00336 else 00337 tmp += "src"; 00338 r->setMimeType(tmp); 00339 } 00340 else 00341 if ( tmp == "application/x-sharedlib" ) 00342 { 00343 if ( _filename.find( ".so" ) == -1 ) 00344 { 00345 tmp = "application/x-executable"; 00346 r->setMimeType( tmp ); 00347 } 00348 } 00349 } 00350 00351 KMimeMagicResult *KMimeMagic::findBufferFileType( const TQByteArray &data, const TQString &fn) { 00352 KMimeMagicResult * r = findBufferType( data ); 00353 refineResult(r, fn); 00354 return r; 00355 } 00356 00357 /* 00358 * Find the content-type of the given file. 00359 */ 00360 KMimeMagicResult* KMimeMagic::findFileType(const TQString & fn) { 00361 #ifdef DEBUG_MIMEMAGIC 00362 kdDebug(7018) << "KMimeMagic::findFileType " << fn << endl; 00363 #endif 00364 conf->resultBuf = TQString::null; 00365 00366 if ( !magicResult ) { 00367 magicResult = new KMimeMagicResult(); 00368 } 00369 magicResult->setInvalid(); 00370 conf->accuracy = 100; 00371 00372 if ( !conf->utimeConf ) { 00373 conf->utimeConf = new KMimeMagicUtimeConf(); 00374 } 00375 00376 /* process it based on the file contents */ 00377 process(conf, fn ); 00378 00379 /* if we have any results, put them in the request structure */ 00380 magicResult->setMimeType(conf->resultBuf.stripWhiteSpace()); 00381 magicResult->setAccuracy(conf->accuracy); 00382 refineResult(magicResult, fn); 00383 return magicResult; 00384 }