2006-08-20 22:43:09 +04:00
/*
* $ Id $
*
* edelib : : MimeType - Detection of file types and handling
* Part of Equinox Desktop Environment ( EDE ) .
* Copyright ( c ) 2000 - 2006 EDE Authors .
*
* This program is licenced under terms of the
* GNU General Public Licence version 2 or newer .
* See COPYING for details .
*/
# include "MimeType.h"
# include <fltk/filename.h>
# include "Run.h"
# include "Config.h"
# include "Util.h"
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <ctype.h>
# include <errno.h>
2006-08-21 01:14:45 +04:00
# include <magic.h>
2006-08-20 22:43:09 +04:00
// I made this icon because on KDE there are stupidly two icons with same name
2006-08-26 19:47:41 +04:00
# define DEFAULT_ICON "mimetypes / misc"
2006-08-20 22:43:09 +04:00
# define FOLDER_ICON "folder"
# define RECYCLED_ICON "recycled"
# define LOCKED_ICON "lockoverlay"
# define FOLDERLOCKED_ICON "folder_locked"
// to be defined in separate file:
# define FILE_MANAGER "efiler"
using namespace fltk ;
using namespace edelib ;
// GLOBAL NOTE: asprintf() is a GNU extension - if it's unsupported on some
// platforms, use our tasprintf() instead (in Util.h)
// File format:
// id|description|handler program|icon|wildcard for filename (extension)|wildcard for file command output|classic mime type
//
// - id - short string; to setup subtypes, just use slash (/) as separator in ID
// - description - what is shown in gui
// - handler program - filename will be appended, specify any needed parameters for opening - THIS WILL BE MOVED INTO SEPARATE FILE (for handling multiple programs etc.)
// - icon - just name, don't give extension or path
// - extension - will be used only if multiple types have same file command match. You don't need to give asterisk (*) i.e. .png. If there are multiple extensions, separate them with slash (/). Actually, "extension" can be any part of filename, but I can't think of use for this
// - file output - relevant part of output from `file -bLnNp $filename`
// - classic mime type - what is used for interchange i.e. text/plain - may be used for matching if other methods fail
//
// This is how mimetype resolving is supposed to work: if there is exactly one match for `file`
// output, this is what we use. If there are multiple, the largest match is used. If there are
// no results or several results with same size we look at extension, then at classic mime type
// (using -i parameter to `file`).
// NOTE: not sure about this last thing, since -i parameter appears to just be alias
// queue/tree of mimetype data
static struct MimeData {
char * id , * typestr , * program , * iconname , * extension , * file_output , * classic_mime ;
MimeData * next ;
} * mime_first = 0 ;
// This is used instead of strstrmulti to check if filename ends with any of extensions
char * test_extension ( const char * file , const char * ext ) {
if ( ! file | | ! ext | | ( strlen ( file ) = = 0 ) | | ( strlen ( ext ) = = 0 ) )
return ( char * ) file ; // this means that empty search returns true
char * copy = strdup ( ext ) ;
char * token = strtok ( copy , " / " ) ;
char * result = 0 ;
do {
int k = strlen ( file ) - strlen ( token ) ;
if ( strcmp ( file + k , token ) = = 0 ) { return strdup ( file + k ) ; break ; }
} while ( ( token = strtok ( NULL , " / " ) ) ) ; // double braces to silence compiler warnings :(
free ( copy ) ;
if ( ! result & & ( ext [ strlen ( ext ) - 1 ] = = ' / ' ) )
return ( char * ) file ; // again
return result ;
}
// Read mime data from file
void get_mimedata ( ) {
// TODO: currently all mimes are on the same level...
mime_first = ( MimeData * ) malloc ( sizeof ( MimeData ) + 1 ) ;
MimeData * m = mime_first ;
char line [ 256 ] ;
char * mime_filename = Config : : find_file ( " mimetypes.conf " ) ;
FILE * f = fopen ( mime_filename , " r " ) ;
bool first = true ;
while ( ! feof ( f ) & & ( fgets ( line , 255 , f ) ) ) {
if ( feof ( f ) ) break ;
// delete comments
if ( char * p = strchr ( line , ' # ' ) ) { * p = ' \0 ' ; }
// delete spaces
wstrim ( line ) ;
// if there's nothing left, skip
if ( strlen ( line ) < 1 ) continue ;
char * p1 , * p2 ;
if ( ! ( p1 = strchr ( line , ' | ' ) ) ) continue ; // likewise
// Allocate next element if this is not first pass
if ( ! first ) {
m - > next = ( MimeData * ) malloc ( sizeof ( MimeData ) + 1 ) ;
m = m - > next ;
}
first = false ;
// parse line
m - > id = wstrim ( strndup ( line , p1 - line ) ) ;
if ( p1 & & ( p2 = strchr ( + + p1 , ' | ' ) ) ) m - > typestr = wstrim ( strndup ( p1 , p2 - p1 ) ) ; else m - > typestr = 0 ;
if ( p2 & & ( p1 = strchr ( + + p2 , ' | ' ) ) ) m - > program = wstrim ( strndup ( p2 , p1 - p2 ) ) ; else m - > program = 0 ;
if ( p1 & & ( p2 = strchr ( + + p1 , ' | ' ) ) ) m - > iconname = wstrim ( strndup ( p1 , p2 - p1 ) ) ; else m - > iconname = 0 ;
if ( p2 & & ( p1 = strchr ( + + p2 , ' | ' ) ) ) m - > extension = wstrim ( strndup ( p2 , p1 - p2 ) ) ; else m - > extension = 0 ;
if ( p1 & & ( p2 = strchr ( + + p1 , ' | ' ) ) ) m - > file_output = wstrim ( strndup ( p1 , p2 - p1 ) ) ; else m - > file_output = 0 ;
if ( p2 & & ( p1 = strchr ( + + p2 , ' | ' ) ) ) m - > classic_mime = wstrim ( strndup ( p2 , p1 - p2 ) ) ; else m - > classic_mime = 0 ;
}
2006-08-26 12:57:23 +04:00
m - > next = 0 ;
2006-08-20 22:43:09 +04:00
fclose ( f ) ;
}
void free_mimedata ( ) {
MimeData * m , * n ;
m = mime_first ;
while ( ( n = m - > next ) ) { free ( m ) ; m = n ; }
mime_first = 0 ;
}
void print_mimedata ( ) { // for debugging
MimeData * m = mime_first ;
while ( m ! = 0 ) {
fprintf ( stderr , " ID: '%s' Name: '%s' Prog: '%s' Icon: '%s' Ext: '%s' File: '%s' MIME: '%s' \n " , m - > id , m - > typestr , m - > program , m - > iconname , m - > extension , m - > file_output , m - > classic_mime ) ;
m = m - > next ;
}
}
// declare given MimeData as current
void MimeType : : set_found ( char * id ) {
if ( ! id ) return ;
// find id
MimeData * m = mime_first ;
while ( m & & strcmp ( m - > id , id ) ! = 0 ) m = m - > next ;
// copy data to cur_*
cur_id = strdup ( id ) ;
if ( m - > typestr ) cur_typestr = strdup ( m - > typestr ) ;
if ( m - > program & & ( strlen ( twstrim ( m - > program ) ) > 0 ) ) {
asprintf ( & cur_command , " %s \" %s \" " , m - > program , cur_filename ) ;
}
if ( m - > iconname ) cur_iconname = strdup ( m - > iconname ) ;
else cur_iconname = strdup ( DEFAULT_ICON ) ;
}
2006-09-02 16:54:42 +04:00
void MimeType : : set ( const char * filename , bool usefind ) {
// Drop any previous data from memory
if ( cur_id ) free ( cur_id ) ;
if ( cur_typestr ) free ( cur_typestr ) ;
if ( cur_command ) free ( cur_command ) ;
if ( cur_iconname ) free ( cur_iconname ) ;
if ( cur_filename ) free ( cur_filename ) ;
2006-08-20 22:43:09 +04:00
cur_id = cur_typestr = cur_command = cur_iconname = cur_filename = 0 ;
2006-09-02 16:54:42 +04:00
2006-08-20 22:43:09 +04:00
if ( filename & & filename_exist ( filename ) ) {
cur_filename = strdup ( filename ) ;
// Directory
if ( filename_isdir ( filename ) ) {
if ( access ( filename , R_OK | | X_OK ) ) {
// Not readable
cur_id = strdup ( " directory/locked " ) ;
cur_typestr = strdup ( " Directory (not accessible) " ) ;
cur_iconname = strdup ( FOLDERLOCKED_ICON ) ;
} else {
cur_id = strdup ( " directory " ) ;
cur_typestr = strdup ( " Directory " ) ;
asprintf ( & cur_command , " %s \" %s \" " , FILE_MANAGER , filename ) ;
cur_iconname = strdup ( FOLDER_ICON ) ;
}
return ;
}
// File not readable
if ( access ( filename , R_OK ) ) {
if ( errno = = EACCES ) {
cur_id = strdup ( " locked " ) ;
cur_typestr = strdup ( " Can't read file " ) ;
cur_iconname = strdup ( LOCKED_ICON ) ;
}
// we don't handle other errors specially
return ;
}
// Backup file
if ( filename [ strlen ( filename ) - 1 ] = = ' ~ ' ) {
cur_id = strdup ( " backup " ) ;
cur_typestr = strdup ( " Backup file " ) ;
cur_iconname = strdup ( RECYCLED_ICON ) ;
return ;
}
if ( ! mime_first ) get_mimedata ( ) ;
// Stuff we need to declare before goto for visibility reasons
MimeData * m = mime_first ;
2006-08-21 01:14:45 +04:00
//char buffer[256];
2006-08-21 23:25:32 +04:00
char * buffer ;
2006-08-20 22:43:09 +04:00
int found = 0 , foundext = 0 ;
2006-08-26 12:57:23 +04:00
MimeData * file_matches [ 50 ] , * ext_matches [ 50 ] = { 0 } ; // this is for if(!ext_matches[0])
2006-08-20 22:43:09 +04:00
if ( ! usefind ) goto nofind ; // using goto here makes less indentation ;)
2006-09-02 16:54:42 +04:00
// execute file command through libmagic
buffer = strdup ( magic_file ( magic_cookie , filename ) ) ;
2006-08-20 22:43:09 +04:00
2006-09-02 16:54:42 +04:00
fprintf ( stderr , " (%s) File said: %s (Error: %s) \n " , filename , buffer , magic_error ( magic_cookie ) ) ;
2006-08-20 22:43:09 +04:00
// find matches for 'file' command output
// TODO: add wildcard matching
2006-08-26 12:57:23 +04:00
while ( m ! = 0 ) {
2006-08-20 22:43:09 +04:00
if ( m - > file_output & & ( strstr ( buffer , m - > file_output ) ) )
file_matches [ found + + ] = m ;
2006-08-26 12:57:23 +04:00
m = m - > next ;
}
2006-08-20 22:43:09 +04:00
if ( found = = 1 ) { // one result found
this - > set_found ( file_matches [ 0 ] - > id ) ;
2006-08-21 23:25:32 +04:00
free ( buffer ) ;
2006-08-20 22:43:09 +04:00
return ;
}
if ( found > 1 ) { // multiple results - find best match
// We look for longest file match
uint max = 0 ;
for ( int i = 0 ; i < found ; i + + )
if ( strlen ( file_matches [ i ] - > file_output ) > max )
max = strlen ( file_matches [ i ] - > file_output ) ;
fprintf ( stderr , " Max: %d \n " , max ) ;
// If all matches are empty, this is probably bogus
if ( max = = 0 ) goto nofind ;
// Test to see if there are multiple best choices
int j = 0 ;
for ( int i = 0 ; i < found ; i + + )
if ( strlen ( file_matches [ i ] - > file_output ) = = max ) {
fprintf ( stderr , " Lokalni maximum '%s' \n " , file_matches [ i ] - > id ) ;
file_matches [ j + + ] = file_matches [ i ] ;
}
// Now **file_matches should contain only maximums
2006-08-21 23:25:32 +04:00
if ( j = = 1 ) {
this - > set_found ( file_matches [ 0 ] - > id ) ;
free ( buffer ) ;
return ;
}
2006-08-20 22:43:09 +04:00
// Compare maximums on extension
for ( int i = 0 ; i < j ; i + + )
if ( test_extension ( filename , file_matches [ i ] - > extension ) )
ext_matches [ foundext + + ] = file_matches [ i ] ;
// No extension matches - accept first result (FIXME)
if ( foundext = = 0 ) {
this - > set_found ( file_matches [ 0 ] - > id ) ;
2006-08-21 23:25:32 +04:00
free ( buffer ) ;
2006-08-20 22:43:09 +04:00
return ;
}
// From here we jump to comment " // continue extension matching"
}
nofind :
2006-08-21 23:25:32 +04:00
free ( buffer ) ;
2006-08-20 22:43:09 +04:00
if ( ! ext_matches [ 0 ] ) {
// Try extension matching on all mimetypes
// This code will be executed if:
// a) find command is disabled
// b) find command returned zero matches (not likely,
// because some mimetypes have empty 'find' field)
// c) all of find results have equal length and no extensions
// match (this is probably a misconfiguration, but its possible)
m = mime_first ;
do {
// take care not to match empty extension
if ( m - > extension
& & ( strlen ( m - > extension ) > 0 )
& &
( m - > extension [ strlen ( m - > extension ) - 1 ] ! = ' / ' )
& & ( test_extension ( filename , m - > extension ) ) ) {
fprintf ( stderr , " Extenzija '%s' \n " , m - > id ) ;
ext_matches [ foundext + + ] = m ;
}
} while ( ( m = m - > next ) ) ;
}
fprintf ( stderr , " Foundext: %d \n " , foundext ) ;
// continue extension matching
if ( foundext = = 1 ) { // one result found
this - > set_found ( ext_matches [ 0 ] - > id ) ;
return ;
}
if ( foundext > 1 ) { // multiple results - find best match
// Code is almost the same as above
// We look for longest extension match
uint max = 0 ;
for ( int i = 0 ; i < foundext ; i + + )
if ( strlen ( ext_matches [ i ] - > extension ) > max & &
( ext_matches [ i ] - > extension [ strlen ( ext_matches [ i ] - > extension ) - 1 ] ! = ' / ' ) )
max = strlen ( ext_matches [ i ] - > extension ) ;
// Test to see if there are multiple best choices
int j = 0 ;
for ( int i = 0 ; i < foundext ; i + + )
if ( strlen ( ext_matches [ i ] - > extension ) = = max )
ext_matches [ j + + ] = ext_matches [ i ] ;
// Now **ext_matches should contain only maximums
if ( j = = 1 ) { this - > set_found ( ext_matches [ 0 ] - > id ) ; return ; }
// Now what??? we return first one whether it be the only or not!
// FIXME
this - > set_found ( file_matches [ 0 ] - > id ) ;
return ;
}
// No extension results found - this is unknown file type
cur_id = strdup ( " unknown " ) ;
cur_typestr = strdup ( " Unknown " ) ;
cur_iconname = strdup ( DEFAULT_ICON ) ;
}
2006-09-02 16:54:42 +04:00
}
MimeType : : MimeType ( const char * filename , bool usefind ) {
cur_id = cur_typestr = cur_command = cur_iconname = cur_filename = 0 ;
// Load mime settings file
if ( ! mime_first ) get_mimedata ( ) ;
// Have libmagic read its configuration data
magic_cookie = magic_open ( MAGIC_NONE ) ;
magic_load ( magic_cookie , NULL ) ;
if ( filename ) set ( filename , usefind ) ;
2006-08-20 22:43:09 +04:00
}
MimeType : : ~ MimeType ( ) {
if ( cur_id ) free ( cur_id ) ;
if ( cur_typestr ) free ( cur_typestr ) ;
if ( cur_command ) free ( cur_command ) ;
if ( cur_iconname ) free ( cur_iconname ) ;
if ( cur_filename ) free ( cur_filename ) ;
2006-09-02 16:54:42 +04:00
// Free memory used by mimetype configuration
free_mimedata ( ) ; //- should we? mimedata is static for a reason...
// Free memory used by libmagic
magic_close ( magic_cookie ) ;
2006-08-20 22:43:09 +04:00
}