fish-shell/mimedb.c
axel 149594f974 Initial revision
darcs-hash:20050920132639-ac50b-fa3b476891e1f5f67207cf4cc7bf623834cc5edc.gz
2005-09-20 23:26:39 +10:00

1267 lines
21 KiB
C

/** \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;
}