/* * MIME database file routines for CUPS. * * Copyright 2007-2014 by Apple Inc. * Copyright 1997-2006 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more information. */ /* * Include necessary headers... */ #include #include #include "mime-private.h" /* * Debug macros that used to be private API... */ #define DEBUG_puts(x) #define DEBUG_printf(...) /* * Local types... */ typedef struct _mime_fcache_s /**** Filter cache structure ****/ { char *name, /* Filter name */ *path; /* Full path to filter if available */ } _mime_fcache_t; /* * Local functions... */ static const char *mime_add_fcache(cups_array_t *filtercache, const char *name, const char *filterpath); static int mime_compare_fcache(_mime_fcache_t *a, _mime_fcache_t *b); static void mime_delete_fcache(cups_array_t *filtercache); static void mime_delete_rules(mime_magic_t *rules); static void mime_load_convs(mime_t *mime, const char *filename, const char *filterpath, cups_array_t *filtercache); static void mime_load_types(mime_t *mime, const char *filename); /* * 'mimeDelete()' - Delete (free) a MIME database. */ void mimeDelete(mime_t *mime) /* I - MIME database */ { mime_type_t *type; /* Current type */ mime_filter_t *filter; /* Current filter */ DEBUG_printf(("mimeDelete(mime=%p)", mime)); if (!mime) return; /* * Loop through filters and free them... */ for (filter = (mime_filter_t *)cupsArrayFirst(mime->filters); filter; filter = (mime_filter_t *)cupsArrayNext(mime->filters)) mimeDeleteFilter(mime, filter); /* * Loop through the file types and delete any rules... */ for (type = (mime_type_t *)cupsArrayFirst(mime->types); type; type = (mime_type_t *)cupsArrayNext(mime->types)) mimeDeleteType(mime, type); /* * Free the types and filters arrays, and then the MIME database structure. */ cupsArrayDelete(mime->types); cupsArrayDelete(mime->filters); cupsArrayDelete(mime->srcs); free(mime); } /* * 'mimeDeleteFilter()' - Delete a filter from the MIME database. */ void mimeDeleteFilter(mime_t *mime, /* I - MIME database */ mime_filter_t *filter) /* I - Filter */ { DEBUG_printf(("mimeDeleteFilter(mime=%p, filter=%p(%s/%s->%s/%s, cost=%d, " "maxsize=" CUPS_LLFMT "))", mime, filter, filter ? filter->src->super : "???", filter ? filter->src->type : "???", filter ? filter->dst->super : "???", filter ? filter->dst->super : "???", filter ? filter->cost : -1, filter ? CUPS_LLCAST filter->maxsize : CUPS_LLCAST -1)); if (!mime || !filter) return; #ifdef DEBUG if (!cupsArrayFind(mime->filters, filter)) DEBUG_puts("1mimeDeleteFilter: Filter not in MIME database."); #endif /* DEBUG */ cupsArrayRemove(mime->filters, filter); free(filter); /* * Deleting a filter invalidates the source lookup cache used by * mimeFilter()... */ if (mime->srcs) { DEBUG_puts("1mimeDeleteFilter: Deleting source lookup cache."); cupsArrayDelete(mime->srcs); mime->srcs = NULL; } } /* * 'mimeDeleteType()' - Delete a type from the MIME database. */ void mimeDeleteType(mime_t *mime, /* I - MIME database */ mime_type_t *mt) /* I - Type */ { DEBUG_printf(("mimeDeleteType(mime=%p, mt=%p(%s/%s))", mime, mt, mt ? mt->super : "???", mt ? mt->type : "???")); if (!mime || !mt) return; #ifdef DEBUG if (!cupsArrayFind(mime->types, mt)) DEBUG_puts("1mimeDeleteFilter: Type not in MIME database."); #endif /* DEBUG */ cupsArrayRemove(mime->types, mt); mime_delete_rules(mt->rules); free(mt); } /* * '_mimeError()' - Show an error message. */ void _mimeError(mime_t *mime, /* I - MIME database */ const char *message, /* I - Printf-style message string */ ...) /* I - Additional arguments as needed */ { va_list ap; /* Argument pointer */ char buffer[8192]; /* Message buffer */ if (mime->error_cb) { va_start(ap, message); vsnprintf(buffer, sizeof(buffer), message, ap); va_end(ap); (*mime->error_cb)(mime->error_ctx, buffer); } } /* * 'mimeFirstFilter()' - Get the first filter in the MIME database. */ mime_filter_t * /* O - Filter or NULL */ mimeFirstFilter(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("6mimeFirstFilter(mime=%p)", mime)); if (!mime) { DEBUG_puts("7mimeFirstFilter: Returning NULL."); return (NULL); } else { mime_filter_t *first = (mime_filter_t *)cupsArrayFirst(mime->filters); /* First filter */ DEBUG_printf(("7mimeFirstFilter: Returning %p.", first)); return (first); } } /* * 'mimeFirstType()' - Get the first type in the MIME database. */ mime_type_t * /* O - Type or NULL */ mimeFirstType(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("6mimeFirstType(mime=%p)", mime)); if (!mime) { DEBUG_puts("7mimeFirstType: Returning NULL."); return (NULL); } else { mime_type_t *first = (mime_type_t *)cupsArrayFirst(mime->types); /* First type */ DEBUG_printf(("7mimeFirstType: Returning %p.", first)); return (first); } } /* * 'mimeLoad()' - Create a new MIME database from disk. * * This function uses @link mimeLoadFilters@ and @link mimeLoadTypes@ to * create a MIME database from a single directory. */ mime_t * /* O - New MIME database */ mimeLoad(const char *pathname, /* I - Directory to load */ const char *filterpath) /* I - Directory to load */ { mime_t *mime; /* New MIME database */ DEBUG_printf(("mimeLoad(pathname=\"%s\", filterpath=\"%s\")", pathname, filterpath)); mime = mimeLoadFilters(mimeLoadTypes(NULL, pathname), pathname, filterpath); DEBUG_printf(("1mimeLoad: Returning %p.", mime)); return (mime); } /* * 'mimeLoadFilters()' - Load filter definitions from disk. * * This function loads all of the .convs files from the specified directory. * Use @link mimeLoadTypes@ to load all types before you load the filters. */ mime_t * /* O - MIME database */ mimeLoadFilters(mime_t *mime, /* I - MIME database */ const char *pathname, /* I - Directory to load from */ const char *filterpath) /* I - Default filter program directory */ { cups_dir_t *dir; /* Directory */ cups_dentry_t *dent; /* Directory entry */ char filename[1024]; /* Full filename of .convs file */ cups_array_t *filtercache; /* Filter cache */ DEBUG_printf(("mimeLoadFilters(mime=%p, pathname=\"%s\", filterpath=\"%s\")", mime, pathname, filterpath)); /* * Range check input... */ if (!mime || !pathname || !filterpath) { DEBUG_puts("1mimeLoadFilters: Bad arguments."); return (mime); } /* * Then open the directory specified by pathname... */ if ((dir = cupsDirOpen(pathname)) == NULL) { DEBUG_printf(("1mimeLoadFilters: Unable to open \"%s\": %s", pathname, strerror(errno))); _mimeError(mime, "Unable to open \"%s\": %s", pathname, strerror(errno)); return (mime); } /* * Read all the .convs files... */ filtercache = cupsArrayNew((cups_array_func_t)mime_compare_fcache, NULL); while ((dent = cupsDirRead(dir)) != NULL) { if (strlen(dent->filename) > 6 && !strcmp(dent->filename + strlen(dent->filename) - 6, ".convs")) { /* * Load a mime.convs file... */ snprintf(filename, sizeof(filename), "%s/%s", pathname, dent->filename); DEBUG_printf(("1mimeLoadFilters: Loading \"%s\".", filename)); mime_load_convs(mime, filename, filterpath, filtercache); } } mime_delete_fcache(filtercache); cupsDirClose(dir); return (mime); } /* * 'mimeLoadTypes()' - Load type definitions from disk. * * This function loads all of the .types files from the specified directory. * Use @link mimeLoadFilters@ to load all filters after you load the types. */ mime_t * /* O - MIME database */ mimeLoadTypes(mime_t *mime, /* I - MIME database or @code NULL@ to create a new one */ const char *pathname) /* I - Directory to load from */ { cups_dir_t *dir; /* Directory */ cups_dentry_t *dent; /* Directory entry */ char filename[1024]; /* Full filename of .types file */ DEBUG_printf(("mimeLoadTypes(mime=%p, pathname=\"%s\")", mime, pathname)); /* * First open the directory specified by pathname... */ if ((dir = cupsDirOpen(pathname)) == NULL) { DEBUG_printf(("1mimeLoadTypes: Unable to open \"%s\": %s", pathname, strerror(errno))); DEBUG_printf(("1mimeLoadTypes: Returning %p.", mime)); _mimeError(mime, "Unable to open \"%s\": %s", pathname, strerror(errno)); return (mime); } /* * If "mime" is NULL, make a new, empty database... */ if (!mime) mime = mimeNew(); if (!mime) { cupsDirClose(dir); DEBUG_puts("1mimeLoadTypes: Returning NULL."); return (NULL); } /* * Read all the .types files... */ while ((dent = cupsDirRead(dir)) != NULL) { if (strlen(dent->filename) > 6 && !strcmp(dent->filename + strlen(dent->filename) - 6, ".types")) { /* * Load a mime.types file... */ snprintf(filename, sizeof(filename), "%s/%s", pathname, dent->filename); DEBUG_printf(("1mimeLoadTypes: Loading \"%s\".", filename)); mime_load_types(mime, filename); } } cupsDirClose(dir); DEBUG_printf(("1mimeLoadTypes: Returning %p.", mime)); return (mime); } /* * 'mimeNew()' - Create a new, empty MIME database. */ mime_t * /* O - MIME database */ mimeNew(void) { return ((mime_t *)calloc(1, sizeof(mime_t))); } /* * 'mimeNextFilter()' - Get the next filter in the MIME database. */ mime_filter_t * /* O - Filter or NULL */ mimeNextFilter(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("6mimeNextFilter(mime=%p)", mime)); if (!mime) { DEBUG_puts("7mimeNextFilter: Returning NULL."); return (NULL); } else { mime_filter_t *next = (mime_filter_t *)cupsArrayNext(mime->filters); /* Next filter */ DEBUG_printf(("7mimeNextFilter: Returning %p.", next)); return (next); } } /* * 'mimeNextType()' - Get the next type in the MIME database. */ mime_type_t * /* O - Type or NULL */ mimeNextType(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("6mimeNextType(mime=%p)", mime)); if (!mime) { DEBUG_puts("7mimeNextType: Returning NULL."); return (NULL); } else { mime_type_t *next = (mime_type_t *)cupsArrayNext(mime->types); /* Next type */ DEBUG_printf(("7mimeNextType: Returning %p.", next)); return (next); } } /* * 'mimeNumFilters()' - Get the number of filters in a MIME database. */ int mimeNumFilters(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("mimeNumFilters(mime=%p)", mime)); if (!mime) { DEBUG_puts("1mimeNumFilters: Returning 0."); return (0); } else { DEBUG_printf(("1mimeNumFilters: Returning %d.", cupsArrayCount(mime->filters))); return (cupsArrayCount(mime->filters)); } } /* * 'mimeNumTypes()' - Get the number of types in a MIME database. */ int mimeNumTypes(mime_t *mime) /* I - MIME database */ { DEBUG_printf(("mimeNumTypes(mime=%p)", mime)); if (!mime) { DEBUG_puts("1mimeNumTypes: Returning 0."); return (0); } else { DEBUG_printf(("1mimeNumTypes: Returning %d.", cupsArrayCount(mime->types))); return (cupsArrayCount(mime->types)); } } /* * 'mimeSetErrorCallback()' - Set the callback for error messages. */ void mimeSetErrorCallback( mime_t *mime, /* I - MIME database */ mime_error_cb_t cb, /* I - Callback function */ void *ctx) /* I - Context pointer for callback */ { if (mime) { mime->error_cb = cb; mime->error_ctx = ctx; } } /* * 'mime_add_fcache()' - Add a filter to the filter cache. */ static const char * /* O - Full path to filter or NULL */ mime_add_fcache( cups_array_t *filtercache, /* I - Filter cache */ const char *name, /* I - Filter name */ const char *filterpath) /* I - Filter path */ { _mime_fcache_t key, /* Search key */ *temp; /* New filter cache */ char path[1024]; /* Full path to filter */ DEBUG_printf(("2mime_add_fcache(filtercache=%p, name=\"%s\", " "filterpath=\"%s\")", filtercache, name, filterpath)); key.name = (char *)name; if ((temp = (_mime_fcache_t *)cupsArrayFind(filtercache, &key)) != NULL) { DEBUG_printf(("3mime_add_fcache: Returning \"%s\".", temp->path)); return (temp->path); } if ((temp = calloc(1, sizeof(_mime_fcache_t))) == NULL) { DEBUG_puts("3mime_add_fcache: Returning NULL."); return (NULL); } temp->name = strdup(name); if (cupsFileFind(name, filterpath, 1, path, sizeof(path))) temp->path = strdup(path); cupsArrayAdd(filtercache, temp); DEBUG_printf(("3mime_add_fcache: Returning \"%s\".", temp->path)); return (temp->path); } /* * 'mime_compare_fcache()' - Compare two filter cache entries. */ static int /* O - Result of comparison */ mime_compare_fcache(_mime_fcache_t *a, /* I - First entry */ _mime_fcache_t *b) /* I - Second entry */ { return (strcmp(a->name, b->name)); } /* * 'mime_delete_fcache()' - Free all memory used by the filter cache. */ static void mime_delete_fcache( cups_array_t *filtercache) /* I - Filter cache */ { _mime_fcache_t *current; /* Current cache entry */ DEBUG_printf(("2mime_delete_fcache(filtercache=%p)", filtercache)); for (current = (_mime_fcache_t *)cupsArrayFirst(filtercache); current; current = (_mime_fcache_t *)cupsArrayNext(filtercache)) { free(current->name); if (current->path) free(current->path); free(current); } cupsArrayDelete(filtercache); } /* * 'mime_delete_rules()' - Free all memory for the given rule tree. */ static void mime_delete_rules(mime_magic_t *rules) /* I - Rules to free */ { mime_magic_t *next; /* Next rule to free */ DEBUG_printf(("2mime_delete_rules(rules=%p)", rules)); /* * Free the rules list, descending recursively to free any child rules. */ while (rules != NULL) { next = rules->next; if (rules->child != NULL) mime_delete_rules(rules->child); if (rules->op == MIME_MAGIC_REGEX) regfree(&(rules->value.rev)); free(rules); rules = next; } } /* * 'mime_load_convs()' - Load a xyz.convs file. */ static void mime_load_convs( mime_t *mime, /* I - MIME database */ const char *filename, /* I - Convs file to load */ const char *filterpath, /* I - Path for filters */ cups_array_t *filtercache) /* I - Filter program cache */ { cups_file_t *fp; /* Convs file */ char line[1024], /* Input line from file */ *lineptr, /* Current position in line */ super[MIME_MAX_SUPER], /* Super-type name */ type[MIME_MAX_TYPE], /* Type name */ *temp, /* Temporary pointer */ *filter; /* Filter program */ mime_type_t *temptype, /* MIME type looping var */ *dsttype; /* Destination MIME type */ int cost; /* Cost of filter */ DEBUG_printf(("2mime_load_convs(mime=%p, filename=\"%s\", filterpath=\"%s\", " "filtercache=%p)", mime, filename, filterpath, filtercache)); /* * First try to open the file... */ if ((fp = cupsFileOpen(filename, "r")) == NULL) { DEBUG_printf(("3mime_load_convs: Unable to open \"%s\": %s", filename, strerror(errno))); _mimeError(mime, "Unable to open \"%s\": %s", filename, strerror(errno)); return; } /* * Then read each line from the file, skipping any comments in the file... */ while (cupsFileGets(fp, line, sizeof(line)) != NULL) { /* * Skip blank lines and lines starting with a #... */ if (!line[0] || line[0] == '#') continue; /* * Strip trailing whitespace... */ for (lineptr = line + strlen(line) - 1; lineptr >= line && isspace(*lineptr & 255); lineptr --) *lineptr = '\0'; /* * Extract the destination super-type and type names from the middle of * the line. */ lineptr = line; while (*lineptr != ' ' && *lineptr != '\t' && *lineptr != '\0') lineptr ++; while (*lineptr == ' ' || *lineptr == '\t') lineptr ++; temp = super; while (*lineptr != '/' && *lineptr != '\n' && *lineptr != '\0' && (temp - super + 1) < MIME_MAX_SUPER) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; if (*lineptr != '/') continue; lineptr ++; temp = type; while (*lineptr != ' ' && *lineptr != '\t' && *lineptr != '\n' && *lineptr != '\0' && (temp - type + 1) < MIME_MAX_TYPE) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; if (*lineptr == '\0' || *lineptr == '\n') continue; if ((dsttype = mimeType(mime, super, type)) == NULL) { DEBUG_printf(("3mime_load_convs: Destination type %s/%s not found.", super, type)); continue; } /* * Then get the cost and filter program... */ while (*lineptr == ' ' || *lineptr == '\t') lineptr ++; if (*lineptr < '0' || *lineptr > '9') continue; cost = atoi(lineptr); while (*lineptr != ' ' && *lineptr != '\t' && *lineptr != '\0') lineptr ++; while (*lineptr == ' ' || *lineptr == '\t') lineptr ++; if (*lineptr == '\0' || *lineptr == '\n') continue; filter = lineptr; if (strcmp(filter, "-")) { /* * Verify that the filter exists and is executable... */ if (!mime_add_fcache(filtercache, filter, filterpath)) { DEBUG_printf(("mime_load_convs: Filter %s not found in %s.", filter, filterpath)); _mimeError(mime, "Filter \"%s\" not found.", filter); continue; } } /* * Finally, get the source super-type and type names from the beginning of * the line. We do it here so we can support wildcards... */ lineptr = line; temp = super; while (*lineptr != '/' && *lineptr != '\n' && *lineptr != '\0' && (temp - super + 1) < MIME_MAX_SUPER) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; if (*lineptr != '/') continue; lineptr ++; temp = type; while (*lineptr != ' ' && *lineptr != '\t' && *lineptr != '\n' && *lineptr != '\0' && (temp - type + 1) < MIME_MAX_TYPE) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; if (!strcmp(super, "*") && !strcmp(type, "*")) { /* * Force * / * to be "application/octet-stream"... */ strlcpy(super, "application", sizeof(super)); strlcpy(type, "octet-stream", sizeof(type)); } /* * Add the filter to the MIME database, supporting wildcards as needed... */ for (temptype = (mime_type_t *)cupsArrayFirst(mime->types); temptype; temptype = (mime_type_t *)cupsArrayNext(mime->types)) if ((super[0] == '*' || !strcmp(temptype->super, super)) && (type[0] == '*' || !strcmp(temptype->type, type))) mimeAddFilter(mime, temptype, dsttype, cost, filter); } cupsFileClose(fp); } /* * 'mime_load_types()' - Load a xyz.types file. */ static void mime_load_types(mime_t *mime, /* I - MIME database */ const char *filename) /* I - Types file to load */ { cups_file_t *fp; /* Types file */ size_t linelen; /* Length of line */ char line[32768], /* Input line from file */ *lineptr, /* Current position in line */ super[MIME_MAX_SUPER], /* Super-type name */ type[MIME_MAX_TYPE], /* Type name */ *temp; /* Temporary pointer */ mime_type_t *typeptr; /* New MIME type */ DEBUG_printf(("2mime_load_types(mime=%p, filename=\"%s\")", mime, filename)); /* * First try to open the file... */ if ((fp = cupsFileOpen(filename, "r")) == NULL) { DEBUG_printf(("3mime_load_types: Unable to open \"%s\": %s", filename, strerror(errno))); _mimeError(mime, "Unable to open \"%s\": %s", filename, strerror(errno)); return; } /* * Then read each line from the file, skipping any comments in the file... */ while (cupsFileGets(fp, line, sizeof(line)) != NULL) { /* * Skip blank lines and lines starting with a #... */ if (!line[0] || line[0] == '#') continue; /* * While the last character in the line is a backslash, continue on to the * next line (and the next, etc.) */ linelen = strlen(line); while (line[linelen - 1] == '\\') { linelen --; if (cupsFileGets(fp, line + linelen, sizeof(line) - linelen) == NULL) line[linelen] = '\0'; else linelen += strlen(line + linelen); } /* * Extract the super-type and type names from the beginning of the line. */ lineptr = line; temp = super; while (*lineptr != '/' && *lineptr != '\n' && *lineptr != '\0' && (temp - super + 1) < MIME_MAX_SUPER) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; if (*lineptr != '/') continue; lineptr ++; temp = type; while (*lineptr != ' ' && *lineptr != '\t' && *lineptr != '\n' && *lineptr != '\0' && (temp - type + 1) < MIME_MAX_TYPE) *temp++ = (char)tolower(*lineptr++ & 255); *temp = '\0'; /* * Add the type and rules to the MIME database... */ typeptr = mimeAddType(mime, super, type); mimeAddTypeRule(typeptr, lineptr); } cupsFileClose(fp); }