diff options
Diffstat (limited to 'mimedb.c')
-rw-r--r-- | mimedb.c | 1266 |
1 files changed, 1266 insertions, 0 deletions
diff --git a/mimedb.c b/mimedb.c new file mode 100644 index 00000000..62eb0d79 --- /dev/null +++ b/mimedb.c @@ -0,0 +1,1266 @@ +/** \file mimedb.c + +mimedb is a program for checking the mimetype, description and +default action associated with a file or mimetype. It uses the +xdgmime library written by the fine folks at freedesktop.org. There does +not seem to be any standard way for the user to change the preferred +application yet. + +The first implementation of mimedb used xml_grep to parse the xml +file for the mime entry to determine the description. This was abandoned +because of the performance implications of parsing xml. The current +version only does a simple string search, which is much, much +faster but it might fall on it's head. + +This code is Copyright 2005 Axel Liljencrantz. +It is released under the GPL. + +The xdgmime library is dual licensed under LGPL/artistic +license. Read the source code of the library for more information. +*/ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <libgen.h> +#include <errno.h> + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#include "xdgmime.h" +#include "util.h" + +/** + Location of the applications .desktop file, relative to a base mime directory +*/ +#define APPLICATIONS_DIR "applications/" + +/** + Location of the mime xml database, relative to a base mime directory +*/ +#define MIME_DIR "mime/" +/** + Filename suffix for XML files +*/ +#define MIME_SUFFIX ".xml" + +/** + Start tag for comment +*/ +#define START_TAG "<comment>" + +/** + End tab for comment +*/ +#define STOP_TAG "</comment>" + +/** + File contains cached list of mime actions +*/ +#define DESKTOP_DEFAULT "applications/defaults.list" + + +/** + All types of input and output possible +*/ +enum +{ + FILEDATA, + FILENAME, + MIMETYPE, + DESCRIPTION, + ACTION, + LAUNCH +} +; + +/** + Error flag. Non-zero if something bad happened. +*/ +static int error = 0; + +/** + String of characters to send to system() to launch a file +*/ +static char *launch_buff=0; + +/** + Length of the launch_buff buffer +*/ +static int launch_len=0; +/** + Current position in the launch_buff buffer +*/ +static int launch_pos=0; + +/** + Dynamically generated function, made from the documentation in doc_src. +*/ +void print_help(); + +/** + Call malloc, set error flag and print message on failure +*/ +void *my_malloc( size_t s ) +{ + void *res = malloc( s ); + if( !s ) + { + error=1; + fprintf( stderr, "mimedb: Out of memory\n" ); + } + return res; +} + +/** + Duplicate string, set error flag and print message on failure +*/ +char *my_strdup( char *s ) +{ + char *res = strdup( s ); + if( !s ) + { + error=1; + fprintf( stderr, "mimedb: Out of memory\n" ); + } + return res; +} + + +/** + Search the file \c filename for the first line starting with \c + match, which is returned in a newly allocated string. +*/ +static char * search_ini( const char *filename, const char *match ) +{ + FILE *f = fopen( filename, "r" ); + char buf[4096]; + int len=strlen(match); + int done = 0; + + if(!f ) + { + perror( "fopen" ); + error=1; + return 0; + } + while( !done ) + { + if( !fgets( buf, 4096, f ) ) + { + if( !feof( f ) ) + { + perror( "fgets" ); + error=1; + } + buf[0]=0; + done = 1; + } + else if( strncmp( buf, match,len )==0) + { + done=1; + } + } + fclose( f ); + if( buf[0] ) + { + char *res=strdup(buf); + if( res ) + { + if(res[strlen(res)-1]=='\n' ) + res[strlen(res)-1]='\0'; + } + return res; + } + else + return (char *)0; +} + +/** + Test if the specified file exists. If it does not, also try + replacing dashes with slashes in \c in. +*/ +static char *file_exists( const char *dir, const char *in ) +{ + char *filename = my_malloc( strlen( dir ) + strlen(in) + 1 ); + char *replaceme; + struct stat buf; + +// fprintf( stderr, "Check %s%s\n", dir, in ); + + if( !filename ) + { + return 0; + } + strcpy( filename, dir ); + strcat( filename, in ); + + if( !stat( filename, &buf ) ) + return filename; + + free( filename ); + + /* + DOH! File does not exist. But all is not lost. KDE sometimes uses + a slash in the name as a directory separator. We try to replace + a dash with a slash and try again. + */ + replaceme = strchr( in, '-' ); + if( replaceme ) + { + char *res; + + *replaceme = '/'; + res = file_exists( dir, in ); + *replaceme = '-'; + return res; + } + /* + OK, no more slashes left. We really are screwed. Nothing to to + but admit defeat and go home. + */ + return 0; +} + + +/** + Try to find the specified file in any of the possible directories + where mime files can be located. This code is shamelessly stolen + from xdg_run_command_on_dirs. +*/ +static char *get_filename( char *f ) +{ + char *result; + const char *xdg_data_home; + const char *xdg_data_dirs; + const char *ptr; + + xdg_data_home = getenv ("XDG_DATA_HOME"); + if (xdg_data_home) + { + result = file_exists( xdg_data_home, f ); + if (result) + return result; + } + else + { + const char *home; + + home = getenv ("HOME"); + if (home != NULL) + { + char *guessed_xdg_home; + + guessed_xdg_home = my_malloc (strlen (home) + strlen ("/.local/share/") + 1); + if( !guessed_xdg_home ) + return 0; + + strcpy (guessed_xdg_home, home); + strcat (guessed_xdg_home, "/.local/share/"); + result = file_exists( guessed_xdg_home, f ); + free (guessed_xdg_home); + + if (result) + return result; + } + } + + xdg_data_dirs = getenv ("XDG_DATA_DIRS"); + if (xdg_data_dirs == NULL) + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + + ptr = xdg_data_dirs; + + while (*ptr != '\000') + { + const char *end_ptr; + char *dir; + int len; + + end_ptr = ptr; + while (*end_ptr != ':' && *end_ptr != '\000') + end_ptr ++; + + if (end_ptr == ptr) + { + ptr++; + continue; + } + + if (*end_ptr == ':') + len = end_ptr - ptr; + else + len = end_ptr - ptr + 1; + dir = my_malloc (len + 1); + if( !dir ) + return 0; + + strncpy (dir, ptr, len); + dir[len] = '\0'; + result = file_exists( dir, f ); + + free (dir); + + if (result) + return result; + + ptr = end_ptr; + } + return 0; +} + +/** + Remove excessive whitespace from string. Replaces arbitrary sequence + of whitespace with a single space. Also removes any leading and + trailing whitespace +*/ +static char *munge( char *in ) +{ + char *out = my_malloc( strlen( in )+1 ); + char *p=out; + int had_whitespace = 0; + int printed = 0; + if( !out ) + { + return 0; + } + + while( 1 ) + { +// fprintf( stderr, "%c\n", *in ); + + switch( *in ) + { + case ' ': + case '\n': + case '\t': + case '\r': + { + had_whitespace = 1; + break; + } + case '\0': + *p = '\0'; + return out; + default: + { + if( printed && had_whitespace ) + { + *(p++)=' '; + } + printed=1; + had_whitespace=0; + *(p++)=*in; + break; + } + } + in++; + } + fprintf( stderr, "mimedb: Unknown error in munge()\n" ); + error=1; + return 0; +} + +/** + Get description for a specified mimetype. +*/ +static char *get_description( const char *mimetype ) +{ + char *fn_part; + + char *fn; + int fd; + struct stat st; + char *contents; + char *start, *stop; + + fn_part = my_malloc( strlen(MIME_DIR) + strlen( mimetype) + strlen(MIME_SUFFIX) + 1 ); + + if( !fn_part ) + { + return 0; + } + + strcpy( fn_part, MIME_DIR ); + strcat( fn_part, mimetype ); + strcat( fn_part, MIME_SUFFIX ); + + fn = get_filename(fn_part); //malloc( strlen(MIME_DIR) +strlen( MIME_SUFFIX)+ strlen( mimetype ) + 1 ); + free(fn_part ); + + if( !fn ) + { + return 0; + } + + fd = open( fn, O_RDONLY ); + +// fprintf( stderr, "%s\n", fn ); + + if( fd == -1 ) + { + perror( "open" ); + error=1; + return 0; + } + + if( stat( fn, &st) ) + { + perror( "stat" ); + error=1; + return 0; + } + + contents = my_malloc( st.st_size + 1 ); + if( !contents ) + { + return 0; + } + + if( read( fd, contents, st.st_size ) != st.st_size ) + { + perror( "read" ); + error=1; + return 0; + } + + close( fd ); + free( fn ); + + contents[st.st_size]=0; + + start = strstr( contents, START_TAG ); + if( start ) + { + start += strlen(START_TAG); + stop = strstr( start, STOP_TAG ); + if( stop ) + { + char *res; + *stop = '\0'; + res = munge( start ); + free( contents ); + return res; + } + } + free( contents ); + fprintf( stderr, "mimedb: No description for type %s\n", mimetype ); + error=1; + return 0; + +} + + +/** + Get default action for a specified mimetype. +*/ +static char *get_action( const char *mimetype ) +{ + char *res=0; + + char *launcher; + char *end; + char *mime_filename; + + char *launcher_str; + char *launcher_filename, *launcher_command_str, *launcher_command; + char *launcher_full; + + mime_filename = get_filename( DESKTOP_DEFAULT ); + if( !mime_filename ) + return 0; + + launcher_str = search_ini( mime_filename, mimetype ); + + free( mime_filename ); + + if( !launcher_str ) + { + /* + This type does not have a launcher. Try the supertype! + */ +// fprintf( stderr, "mimedb: %s does not have launcher, try supertype\n", mimetype ); + const char ** parents = xdg_mime_get_mime_parents(mimetype); + + const char **p; + if( parents ) + { + for( p=parents; *p; p++ ) + { + char *a = get_action(*p); + if( a != 0 ) + return a; + } + } + /* + Just in case subclassing doesn't work, (It doesn't on Fedora + Core 3) we also test some common subclassings. + */ + + if( strncmp( mimetype, "text/", 5 ) == 0 ) + return get_action( "text/plain" ); + + return 0; + } + +// fprintf( stderr, "WOOT %s\n", launcher_str ); + launcher = strchr( launcher_str, '=' ); + + if( !launcher ) + { + fprintf( stderr, "Could not parse launcher string %s\n", launcher_str ); + error=1; + return 0; + } + + /* Skip the = */ + launcher++; + + /* Only use first launcher */ + end = strchr( launcher, ';' ); + if( end ) + *end = '\0'; + + launcher_full = my_malloc( strlen( launcher) + strlen( APPLICATIONS_DIR)+1 ); + if( !launcher_full ) + { + free( launcher_str ); + return 0; + } + + strcpy( launcher_full, APPLICATIONS_DIR ); + strcat( launcher_full, launcher ); + free( launcher_str ); + + launcher_filename = get_filename( launcher_full ); + + free( launcher_full ); + + launcher_command_str = search_ini( launcher_filename, "Exec=" ); + + if( !launcher_command_str ) + { + fprintf( stderr, + "mimedb: Default launcher %s does not specify how to start\n", + launcher_filename ); + free( launcher_filename ); + return 0; + } + + free( launcher_filename ); + + launcher_command = strchr( launcher_command_str, '=' ); + launcher_command++; + + res = my_strdup( launcher_command ); + + free( launcher_command_str ); + + return res; +} + + +/** + Helper function for launch. Write the specified byte to the string we will execute +*/ +static void writer( char c ) +{ + if( launch_len == -1 ) + return; + + if( launch_len <= launch_pos ) + { + int new_len = launch_len?2*launch_len:256; + char *new_buff = realloc( launch_buff, new_len ); + if( !new_buff ) + { + free( launch_buff ); + launch_len = -1; + error=1; + return; + } + launch_buff = new_buff; + launch_len = new_len; + + } + launch_buff[launch_pos++]=c; +} + +/** + Write out the specified byte in hex +*/ +static void writer_hex( int num ) +{ + int a, b; + a = num /16; + b = num %16; + writer( a>9?('A'+a-10):('0'+a)); + writer( b>9?('A'+b-10):('0'+b)); +} + +/** + Return current directory in newly allocated string +*/ +static char *my_getcwd () +{ + size_t size = 100; + while (1) + { + char *buffer = (char *) malloc (size); + if (getcwd (buffer, size) == buffer) + return buffer; + free (buffer); + if (errno != ERANGE) + return 0; + size *= 2; + } +} + +/** + Return absolute filename of specified file + */ +static char *get_fullfile( char *file ) +{ + char *fullfile; + + if( file[0] == '/' ) + { + fullfile = file; + } + else + { + char *cwd = my_getcwd(); + if( !cwd ) + { + error = 1; + perror( "getcwd" ); + return 0; + } + + int l = strlen(cwd); + + fullfile = my_malloc( l + strlen(file)+2 ); + if( !fullfile ) + { + free(cwd); + return 0; + } + strcpy( fullfile, cwd ); + if( cwd[l-1] != '/' ) + strcat(fullfile, "/" ); + strcat( fullfile, file ); + + free(cwd); + } + return fullfile; +} + + +/** + Write specified file as an URL +*/ +static void write_url( char *file ) +{ + char *fullfile = get_fullfile( file ); + char *str = fullfile; + + if( str == 0 ) + { + launch_len = -1; + return; + } + + writer( 'f'); + writer( 'i'); + writer( 'l'); + writer( 'e'); + writer( ':'); + writer( '/'); + writer( '/'); + while( *str ) + { + if( ((*str >= 'a') && (*str <='z')) || + ((*str >= 'A') && (*str <='Z')) || + ((*str >= '0') && (*str <='9')) || + (strchr( "./_",*str) != 0) ) + { + writer(*str); + } + else if(strchr( "()?&=",*str) != 0) + { + writer('\\'); + writer(*str); + } + else + { + writer( '%' ); + writer_hex( *str ); + } + str++; + } + if( fullfile != file ) + free( fullfile ); + +} + +/** + Write specified file +*/ +static void write_file( char *file, int print_path ) +{ + char *fullfile; + char *str; + if( print_path ) + { + fullfile = get_fullfile( file ); + str = fullfile; + } + else + { + fullfile = my_strdup( file ); + if( !fullfile ) + { + return; + } + str = basename( fullfile ); + } + + if( !str ) + { + error = 1; + return; + } + + while( *str ) + { + switch(*str ) + { + case ')': + case '(': + case '-': + case '#': + case '$': + case '}': + case '{': + case ']': + case '[': + case '*': + case '?': + case ' ': + case '|': + case '<': + case '>': + case '^': + case '&': + case '\\': + case '`': + case '\'': + case '\"': + writer('\\'); + writer(*str); + break; + + case '\n': + writer('\\'); + writer('n'); + break; + + case '\r': + writer('\\'); + writer('r'); + break; + + case '\t': + writer('\\'); + writer('t'); + break; + + case '\b': + writer('\\'); + writer('b'); + break; + + case '\v': + writer('\\'); + writer('v'); + break; + + default: + writer(*str); + break; + } + str++; + } + + if( fullfile != file ) + free( fullfile ); +} + +/** + Use the specified launch filter to launch all the files in the specified list. + + \param filter the action to take + \param files the list of files for which to perform the action + \param fileno an internal value. Should always be set to zero. +*/ +static void launch( char *filter, array_list_t *files, int fileno ) +{ + char *filter_org=filter; + int count=0; + int launch_again=0; + + if( al_get_count( files ) <= fileno ) + return; + + + launch_pos=0; + + for( ;*filter && !error; filter++) + { + if(*filter == '%') + { + filter++; + switch( *filter ) + { + case 'u': + { + launch_again = 1; + write_url( (char *)al_get( files, fileno ) ); + break; + } + case 'U': + { + int i; + for( i=0; i<al_get_count( files ); i++ ) + { + if( i != 0 ) + writer( ' ' ); + write_url( (char *)al_get( files, i ) ); + if( error ) + break; + } + + break; + } + + case 'f': + case 'n': + { + launch_again = 1; + write_file( (char *)al_get( files, fileno ), *filter == 'f' ); + break; + } + + case 'F': + case 'N': + { + int i; + for( i=0; i<al_get_count( files ); i++ ) + { + if( i != 0 ) + writer( ' ' ); + write_file( (char *)al_get( files, i ), *filter == 'F' ); + if( error ) + break; + } + break; + } + + + case 'd': + { + char *cpy = get_fullfile( (char *)al_get( files, fileno ) ); + char *dir; + + launch_again=1; + /* + We wish to modify this string, make sure it is only a copy + */ + if( cpy == al_get( files, fileno ) ) + cpy = my_strdup( cpy ); + + if( cpy == 0 ) + { + break; + } + dir=dirname( cpy ); + write_file( dir, 1 ); + free( cpy ); + + break; + } + + case 'D': + { + int i; + for( i=0; i<al_get_count( files ); i++ ) + { + char *cpy = get_fullfile( (char *)al_get( files, i ) ); + char *dir; + + /* + We wish to modify this string, make sure it is only a copy + */ + if( cpy == al_get( files, i ) ) + cpy = my_strdup( cpy ); + + if( cpy == 0 ) + { + break; + } + dir=dirname( cpy ); + + if( i != 0 ) + writer( ' ' ); + + write_file( dir, 1 ); + free( cpy ); + + } + break; + } + + default: + fprintf( stderr, "Unsupported switch %c in launch string %s\n", *filter, filter_org ); + launch_len=0; + break; + + } + } + else + { + writer( *filter ); + count++; + } + } + + if( error ) + return; + + switch( launch_len ) + { + case -1: + { + launch_len = 0; + fprintf( stderr, "mimedb: Out of memory\n" ); + return; + } + case 0: + { + return; + } + default: + { + + writer( ' ' ); + writer( '&' ); + writer( '\0' ); + +// fprintf( stderr, "mimedb: %s\n", launch_buff ); + system( launch_buff ); + break; + } + } + if( launch_again ) + { + launch( filter_org, files, fileno+1 ); + } + +} + +/** + Clean up one entry from the hash table of launch files +*/ +static void clear_entry( const void *key, const void *val ) +{ + /* + The key is a mime value, either from the libraries internal hash + table of mime types or from the command line. Either way, it + should not be freed. + + The value is an array_list_t of filenames. The filenames com from + the argument list and should not be freed. The arraylist, + however, should be destroyed and freed. + */ + array_list_t *l = (array_list_t *)val; + al_destroy( l ); + free( l ); +} + + + +/** + Main function. Parses options and calls helper function for any heavy lifting. +*/ +int main (int argc, char *argv[]) +{ + int input_type=FILEDATA; + int output_type=MIMETYPE; + + const char *mimetype; + char *output=0; + + int i; + + hash_table_t launch_hash; + + + /* + Parse options + */ + while( 1 ) + { +#ifdef __GLIBC__ + static struct option + long_options[] = + { + { + "input-file-data", no_argument, 0, 't' + } + , + { + "input-filename", no_argument, 0, 'f' + } + , + { + "input-mime", no_argument, 0, 'i' + } + , + { + "output-mime", no_argument, 0, 'm' + } + , + { + "output-description", no_argument, 0, 'd' + } + , + { + "output-action", no_argument, 0, 'a' + } + , + { + "help", no_argument, 0, 'h' + } + , + { + "version", no_argument, 0, 'v' + } + , + { + "launch", no_argument, 0, 'l' + } + , + { + 0, 0, 0, 0 + } + } + ; + + int opt_index = 0; + + int opt = getopt_long( argc, + argv, + "tfimdalhv", + long_options, + &opt_index ); + +#else + int opt = getopt( argc, + argv, + "tfimdalhv" ); +#endif + if( opt == -1 ) + break; + + switch( opt ) + { + case 0: + break; + + case 't': + input_type=FILEDATA; + break; + + case 'f': + input_type=FILENAME; + break; + + case 'i': + input_type=MIMETYPE; + break; + + case 'm': + output_type=MIMETYPE; + break; + + case 'd': + output_type=DESCRIPTION; + break; + + case 'a': + output_type=ACTION; + break; + + case 'l': + output_type=LAUNCH; + break; + + case 'h': + print_help(); + exit(0); + + case 'v': + printf( "mimedb, version %s\n", PACKAGE_VERSION ); + exit( 0 ); + + case '?': + return 1; + + } + } + + if( ( output_type == LAUNCH )&&(input_type==MIMETYPE)) + { + fprintf( stderr, "Can not launch a mimetype\n" ); + print_help(); + exit(1); + } + + if( output_type == LAUNCH ) + hash_init( &launch_hash, &hash_str_func, &hash_str_cmp ); + + + /* + Loop over all non option arguments and do the specified lookup + */ + + //fprintf( stderr, "Input %d, output %d\n", input_type, output_type ); + + for (i = optind; (i < argc)&&(!error); i++) + { + /* Convert from filename to mimetype, if needed */ + if( input_type == FILENAME ) + { + mimetype = xdg_mime_get_mime_type_from_file_name(argv[i]); + } + else if( input_type == FILEDATA ) + { + mimetype = xdg_mime_get_mime_type_for_file(argv[i]); + } + else + mimetype = xdg_mime_is_valid_mime_type(argv[i])?argv[i]:0; + + mimetype = xdg_mime_unalias_mime_type (mimetype); + if( !mimetype ) + { + fprintf( stderr, "mimedb: Could not parse mimetype from argument %s\n", argv[i] ); + error=1; + return 1; + } + + /* + Convert from mimetype to whatever, if needed + */ + switch( output_type ) + { + case MIMETYPE: + { + output = (char *)mimetype; + break; + + } + case DESCRIPTION: + { + output = get_description( mimetype ); + break; + } + case ACTION: + { + output = get_action( mimetype ); + break; + } + case LAUNCH: + { + /* + There may be more files using the same launcher, we + add them all up in little array_list_ts and launched + them together after all the arguments have been + parsed. + */ + array_list_t *l= (array_list_t *)hash_get( &launch_hash, mimetype ); + output = 0; + + if( !l ) + { + l = my_malloc( sizeof( array_list_t ) ); + if( l == 0 ) + { + break; + } + al_init( l ); + hash_put( &launch_hash, mimetype, l ); + } + al_push( l, argv[i] ); + } + } + + /* + Print the glorious result + */ + if( output ) + { + printf( "%s\n", output ); + if( output != mimetype ) + free( output ); + } + output = 0; + } + + /* + Perform the actual launching + */ + if( output_type == LAUNCH ) + { + int i; + array_list_t mimes; + al_init( &mimes ); + hash_get_keys( &launch_hash, &mimes ); + for( i=0; i<al_get_count( &mimes ); i++ ) + { + char *mimetype = (char *)al_get( &mimes, i ); + array_list_t *files = (array_list_t *)hash_get( &launch_hash, mimetype ); + if( !files ) + { + fprintf( stderr, "mimedb: Unknown error\n" ); + error=1; + break; + } + + char *launcher = get_action( mimetype ); + + if( launcher ) + { + launch( launcher, files, 0 ); + free( launcher ); + } + } + hash_foreach( &launch_hash, &clear_entry ); + hash_destroy( &launch_hash ); + al_destroy( &mimes ); + } + + if( launch_buff ) + free( launch_buff ); + + xdg_mime_shutdown(); + + return error; +} |