#include <cups/string-private.h>
#include <cups/debug-private.h>
#include <locale.h>
#include "mime.h"
typedef struct _mime_filebuf_s
{
cups_file_t *fp;
int offset,
length;
unsigned char buffer[MIME_MAX_BUFFER];
} _mime_filebuf_t;
static int mime_compare_types(mime_type_t *t0, mime_type_t *t1);
static int mime_check_rules(const char *filename, _mime_filebuf_t *fb,
mime_magic_t *rules);
static int mime_patmatch(const char *s, const char *pat);
#ifdef DEBUG
static const char * const debug_ops[] =
{
"NOP",
"AND",
"OR",
"MATCH",
"ASCII",
"PRINTABLE",
"STRING",
"CHAR",
"SHORT",
"INT",
"LOCALE",
"CONTAINS",
"ISTRING",
"REGEX"
};
#endif
mime_type_t *
mimeAddType(mime_t *mime,
const char *super,
const char *type)
{
mime_type_t *temp;
size_t typelen;
DEBUG_printf(("mimeAddType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
type));
if (!mime || !super || !type)
{
DEBUG_puts("1mimeAddType: Returning NULL (bad arguments).");
return (NULL);
}
if ((temp = mimeType(mime, super, type)) != NULL)
{
DEBUG_printf(("1mimeAddType: Returning %p (existing).", temp));
return (temp);
}
if (!mime->types)
mime->types = cupsArrayNew((cups_array_func_t)mime_compare_types, NULL);
if (!mime->types)
{
DEBUG_puts("1mimeAddType: Returning NULL (no types).");
return (NULL);
}
typelen = strlen(type) + 1;
if ((temp = calloc(1, sizeof(mime_type_t) - MIME_MAX_TYPE + typelen)) == NULL)
{
DEBUG_puts("1mimeAddType: Returning NULL (out of memory).");
return (NULL);
}
strlcpy(temp->super, super, sizeof(temp->super));
memcpy(temp->type, type, typelen);
temp->priority = 100;
cupsArrayAdd(mime->types, temp);
DEBUG_printf(("1mimeAddType: Returning %p (new).", temp));
return (temp);
}
int
mimeAddTypeRule(mime_type_t *mt,
const char *rule)
{
int num_values,
op,
logic,
invert;
char name[255],
value[3][255],
*ptr,
quote;
int length[3];
mime_magic_t *temp,
*current;
DEBUG_printf(("mimeAddTypeRule(mt=%p(%s/%s), rule=\"%s\")", mt,
mt ? mt->super : "???", mt ? mt->type : "???", rule));
if (!mt || !rule)
return (-1);
for (current = mt->rules; current; current = current->next)
if (!current->next)
break;
logic = MIME_MAGIC_NOP;
invert = 0;
while (*rule != '\0')
{
while (isspace(*rule & 255))
rule ++;
if (*rule == '(')
{
DEBUG_puts("1mimeAddTypeRule: New parenthesis group");
logic = MIME_MAGIC_NOP;
rule ++;
}
else if (*rule == ')')
{
DEBUG_puts("1mimeAddTypeRule: Close paren...");
if (current == NULL || current->parent == NULL)
return (-1);
current = current->parent;
if (current->parent == NULL)
logic = MIME_MAGIC_OR;
else
logic = current->parent->op;
rule ++;
}
else if (*rule == '+' && current != NULL)
{
if (logic != MIME_MAGIC_AND &&
current != NULL && current->prev != NULL)
{
if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
return (-1);
temp->op = MIME_MAGIC_AND;
temp->child = current;
temp->parent = current->parent;
current->prev->next = temp;
temp->prev = current->prev;
current->prev = NULL;
current->parent = temp;
DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p.", temp));
}
else if (current->parent)
{
DEBUG_printf(("1mimeAddTypeRule: Setting group %p op to AND.",
current->parent));
current->parent->op = MIME_MAGIC_AND;
}
logic = MIME_MAGIC_AND;
rule ++;
}
else if (*rule == ',')
{
if (logic != MIME_MAGIC_OR && current != NULL)
{
if (current->parent == NULL)
{
if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
return (-1);
DEBUG_printf(("1mimeAddTypeRule: Creating new AND group %p inside OR "
"group.", temp));
while (current->prev != NULL)
{
current->parent = temp;
current = current->prev;
}
current->parent = temp;
temp->op = MIME_MAGIC_AND;
temp->child = current;
mt->rules = current = temp;
}
else
{
DEBUG_puts("1mimeAddTypeRule: Going up one level.");
current = current->parent;
}
}
logic = MIME_MAGIC_OR;
rule ++;
}
else if (*rule == '!')
{
DEBUG_puts("1mimeAddTypeRule: NOT");
invert = 1;
rule ++;
}
else if (isalnum(*rule & 255))
{
ptr = name;
while (isalnum(*rule & 255) && (size_t)(ptr - name) < (sizeof(name) - 1))
*ptr++ = *rule++;
*ptr = '\0';
if (*rule == '(')
{
rule ++;
for (num_values = 0;
num_values < (int)(sizeof(value) / sizeof(value[0]));
num_values ++)
{
ptr = value[num_values];
while ((size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1) &&
*rule != '\0' && *rule != ',' && *rule != ')')
{
if (isspace(*rule & 255))
{
rule ++;
continue;
}
else if (*rule == '\"' || *rule == '\'')
{
quote = *rule++;
while (*rule != '\0' && *rule != quote &&
(size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1))
*ptr++ = *rule++;
if (*rule == quote)
rule ++;
else
return (-1);
}
else if (*rule == '<')
{
rule ++;
while (*rule != '>' && *rule != '\0' &&
(size_t)(ptr - value[num_values]) < (sizeof(value[0]) - 1))
{
if (isxdigit(rule[0] & 255) && isxdigit(rule[1] & 255))
{
if (isdigit(*rule))
*ptr = (char)((*rule++ - '0') << 4);
else
*ptr = (char)((tolower(*rule++) - 'a' + 10) << 4);
if (isdigit(*rule))
*ptr++ |= *rule++ - '0';
else
*ptr++ |= tolower(*rule++) - 'a' + 10;
}
else
return (-1);
}
if (*rule == '>')
rule ++;
else
return (-1);
}
else
*ptr++ = *rule++;
}
*ptr = '\0';
length[num_values] = ptr - value[num_values];
if (*rule != ',')
{
num_values ++;
break;
}
rule ++;
}
if (*rule != ')')
return (-1);
rule ++;
if (!strcmp(name, "match"))
op = MIME_MAGIC_MATCH;
else if (!strcmp(name, "ascii"))
op = MIME_MAGIC_ASCII;
else if (!strcmp(name, "printable"))
op = MIME_MAGIC_PRINTABLE;
else if (!strcmp(name, "regex"))
op = MIME_MAGIC_REGEX;
else if (!strcmp(name, "string"))
op = MIME_MAGIC_STRING;
else if (!strcmp(name, "istring"))
op = MIME_MAGIC_ISTRING;
else if (!strcmp(name, "char"))
op = MIME_MAGIC_CHAR;
else if (!strcmp(name, "short"))
op = MIME_MAGIC_SHORT;
else if (!strcmp(name, "int"))
op = MIME_MAGIC_INT;
else if (!strcmp(name, "locale"))
op = MIME_MAGIC_LOCALE;
else if (!strcmp(name, "contains"))
op = MIME_MAGIC_CONTAINS;
else if (!strcmp(name, "priority") && num_values == 1)
{
mt->priority = atoi(value[0]);
continue;
}
else
return (-1);
}
else
{
snprintf(value[0], sizeof(value[0]), "*.%s", name);
length[0] = (int)strlen(value[0]);
op = MIME_MAGIC_MATCH;
}
if ((temp = calloc(1, sizeof(mime_magic_t))) == NULL)
return (-1);
temp->invert = (short)invert;
if (current != NULL)
{
temp->parent = current->parent;
current->next = temp;
}
else
mt->rules = temp;
temp->prev = current;
if (logic == MIME_MAGIC_NOP)
{
DEBUG_printf(("1mimeAddTypeRule: Making new OR group %p for "
"parenthesis.", temp));
temp->op = MIME_MAGIC_OR;
if ((temp->child = calloc(1, sizeof(mime_magic_t))) == NULL)
return (-1);
temp->child->parent = temp;
temp->child->invert = temp->invert;
temp->invert = 0;
temp = temp->child;
logic = MIME_MAGIC_OR;
}
DEBUG_printf(("1mimeAddTypeRule: Adding %p: %s, op=MIME_MAGIC_%s(%d), "
"logic=MIME_MAGIC_%s, invert=%d.", temp, name,
debug_ops[op], op, debug_ops[logic], invert));
current = temp;
temp->op = (short)op;
invert = 0;
switch (op)
{
case MIME_MAGIC_MATCH :
if ((size_t)length[0] > (sizeof(temp->value.matchv) - 1))
return (-1);
strlcpy(temp->value.matchv, value[0], sizeof(temp->value.matchv));
break;
case MIME_MAGIC_ASCII :
case MIME_MAGIC_PRINTABLE :
temp->offset = strtol(value[0], NULL, 0);
temp->length = strtol(value[1], NULL, 0);
if (temp->length > MIME_MAX_BUFFER)
temp->length = MIME_MAX_BUFFER;
break;
case MIME_MAGIC_REGEX :
temp->offset = strtol(value[0], NULL, 0);
temp->length = MIME_MAX_BUFFER;
if (regcomp(&(temp->value.rev), value[1], REG_NOSUB | REG_EXTENDED))
return (-1);
break;
case MIME_MAGIC_STRING :
case MIME_MAGIC_ISTRING :
temp->offset = strtol(value[0], NULL, 0);
if ((size_t)length[1] > sizeof(temp->value.stringv))
return (-1);
temp->length = length[1];
memcpy(temp->value.stringv, value[1], (size_t)length[1]);
break;
case MIME_MAGIC_CHAR :
temp->offset = strtol(value[0], NULL, 0);
if (length[1] == 1)
temp->value.charv = (unsigned char)value[1][0];
else
temp->value.charv = (unsigned char)strtol(value[1], NULL, 0);
DEBUG_printf(("1mimeAddTypeRule: CHAR(%d,0x%02x)", temp->offset,
temp->value.charv));
break;
case MIME_MAGIC_SHORT :
temp->offset = strtol(value[0], NULL, 0);
temp->value.shortv = (unsigned short)strtol(value[1], NULL, 0);
break;
case MIME_MAGIC_INT :
temp->offset = strtol(value[0], NULL, 0);
temp->value.intv = (unsigned)strtol(value[1], NULL, 0);
break;
case MIME_MAGIC_LOCALE :
if ((size_t)length[0] > (sizeof(temp->value.localev) - 1))
return (-1);
strlcpy(temp->value.localev, value[0], sizeof(temp->value.localev));
break;
case MIME_MAGIC_CONTAINS :
temp->offset = strtol(value[0], NULL, 0);
temp->region = strtol(value[1], NULL, 0);
if ((size_t)length[2] > sizeof(temp->value.stringv))
return (-1);
temp->length = length[2];
memcpy(temp->value.stringv, value[2], (size_t)length[2]);
break;
}
}
else
break;
}
return (0);
}
mime_type_t *
mimeFileType(mime_t *mime,
const char *pathname,
const char *filename,
int *compression)
{
_mime_filebuf_t fb;
const char *base;
mime_type_t *type,
*best;
DEBUG_printf(("mimeFileType(mime=%p, pathname=\"%s\", filename=\"%s\", "
"compression=%p)", mime, pathname, filename, compression));
if (!mime || !pathname)
{
DEBUG_puts("1mimeFileType: Returning NULL.");
return (NULL);
}
if ((fb.fp = cupsFileOpen(pathname, "r")) == NULL)
{
DEBUG_printf(("1mimeFileType: Unable to open \"%s\": %s", pathname,
strerror(errno)));
DEBUG_puts("1mimeFileType: Returning NULL.");
return (NULL);
}
fb.offset = 0;
fb.length = (int)cupsFileRead(fb.fp, (char *)fb.buffer, MIME_MAX_BUFFER);
if (fb.length <= 0)
{
DEBUG_printf(("1mimeFileType: Unable to read from \"%s\": %s", pathname, strerror(errno)));
DEBUG_puts("1mimeFileType: Returning NULL.");
cupsFileClose(fb.fp);
return (NULL);
}
if (filename)
{
if ((base = strrchr(filename, '/')) != NULL)
base ++;
else
base = filename;
}
else if ((base = strrchr(pathname, '/')) != NULL)
base ++;
else
base = pathname;
for (type = (mime_type_t *)cupsArrayFirst(mime->types), best = NULL;
type;
type = (mime_type_t *)cupsArrayNext(mime->types))
if (mime_check_rules(base, &fb, type->rules))
{
if (!best || type->priority > best->priority)
best = type;
}
if (compression)
{
*compression = cupsFileCompression(fb.fp);
DEBUG_printf(("1mimeFileType: *compression=%d", *compression));
}
cupsFileClose(fb.fp);
DEBUG_printf(("1mimeFileType: Returning %p(%s/%s).", best,
best ? best->super : "???", best ? best->type : "???"));
return (best);
}
mime_type_t *
mimeType(mime_t *mime,
const char *super,
const char *type)
{
mime_type_t key,
*mt;
DEBUG_printf(("mimeType(mime=%p, super=\"%s\", type=\"%s\")", mime, super,
type));
if (!mime || !super || !type)
{
DEBUG_puts("1mimeType: Returning NULL.");
return (NULL);
}
strlcpy(key.super, super, sizeof(key.super));
strlcpy(key.type, type, sizeof(key.type));
mt = (mime_type_t *)cupsArrayFind(mime->types, &key);
DEBUG_printf(("1mimeType: Returning %p.", mt));
return (mt);
}
static int
mime_compare_types(mime_type_t *t0,
mime_type_t *t1)
{
int i;
if ((i = _cups_strcasecmp(t0->super, t1->super)) == 0)
i = _cups_strcasecmp(t0->type, t1->type);
return (i);
}
static int
mime_check_rules(
const char *filename,
_mime_filebuf_t *fb,
mime_magic_t *rules)
{
int n;
int region;
int logic,
result;
unsigned intv;
short shortv;
unsigned char *bufptr;
DEBUG_printf(("4mime_check_rules(filename=\"%s\", fb=%p, rules=%p)", filename,
fb, rules));
if (rules == NULL)
return (0);
if (rules->parent == NULL)
logic = MIME_MAGIC_OR;
else
logic = rules->parent->op;
result = 0;
while (rules != NULL)
{
switch (rules->op)
{
case MIME_MAGIC_MATCH :
result = mime_patmatch(filename, rules->value.matchv);
break;
case MIME_MAGIC_ASCII :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->length) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_ASCII fb->length=%d", fb->length));
}
if ((rules->offset + rules->length) > (fb->offset + fb->length))
n = fb->offset + fb->length - rules->offset;
else
n = rules->length;
bufptr = fb->buffer + rules->offset - fb->offset;
while (n > 0)
if ((*bufptr >= 32 && *bufptr <= 126) ||
(*bufptr >= 8 && *bufptr <= 13) ||
*bufptr == 26 || *bufptr == 27)
{
n --;
bufptr ++;
}
else
break;
result = (n == 0);
break;
case MIME_MAGIC_PRINTABLE :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->length) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_PRINTABLE fb->length=%d", fb->length));
}
if ((rules->offset + rules->length) > (fb->offset + fb->length))
n = fb->offset + fb->length - rules->offset;
else
n = rules->length;
bufptr = fb->buffer + rules->offset - fb->offset;
while (n > 0)
if (*bufptr >= 128 ||
(*bufptr >= 32 && *bufptr <= 126) ||
(*bufptr >= 8 && *bufptr <= 13) ||
*bufptr == 26 || *bufptr == 27)
{
n --;
bufptr ++;
}
else
break;
result = (n == 0);
break;
case MIME_MAGIC_REGEX :
DEBUG_printf(("5mime_check_rules: regex(%d, \"%s\")", rules->offset,
rules->value.stringv));
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->length) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_REGEX fb->length=%d", fb->length));
DEBUG_printf(("5mime_check_rules: loaded %d byte fb->buffer at %d, starts "
"with \"%c%c%c%c\".",
fb->length, fb->offset, fb->buffer[0], fb->buffer[1],
fb->buffer[2], fb->buffer[3]));
}
if (fb->length > 0)
{
char temp[MIME_MAX_BUFFER + 1];
memcpy(temp, fb->buffer, (size_t)fb->length);
temp[fb->length] = '\0';
result = !regexec(&(rules->value.rev), temp, 0, NULL, 0);
}
DEBUG_printf(("5mime_check_rules: result=%d", result));
break;
case MIME_MAGIC_STRING :
DEBUG_printf(("5mime_check_rules: string(%d, \"%s\")", rules->offset,
rules->value.stringv));
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->length) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_STRING fb->length=%d", fb->length));
DEBUG_printf(("5mime_check_rules: loaded %d byte fb->buffer at %d, starts "
"with \"%c%c%c%c\".",
fb->length, fb->offset, fb->buffer[0], fb->buffer[1],
fb->buffer[2], fb->buffer[3]));
}
if ((rules->offset + rules->length) > (fb->offset + fb->length))
result = 0;
else
result = !memcmp(fb->buffer + rules->offset - fb->offset, rules->value.stringv, (size_t)rules->length);
DEBUG_printf(("5mime_check_rules: result=%d", result));
break;
case MIME_MAGIC_ISTRING :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->length) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_ISTRING fb->length=%d", fb->length));
}
if ((rules->offset + rules->length) > (fb->offset + fb->length))
result = 0;
else
result = !_cups_strncasecmp((char *)fb->buffer + rules->offset - fb->offset, rules->value.stringv, (size_t)rules->length);
break;
case MIME_MAGIC_CHAR :
if (fb->offset < 0 || rules->offset < fb->offset)
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_CHAR fb->length=%d", fb->length));
}
if (fb->length < 1)
result = 0;
else
result = (fb->buffer[rules->offset - fb->offset] ==
rules->value.charv);
break;
case MIME_MAGIC_SHORT :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + 2) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_SHORT fb->length=%d", fb->length));
}
if (fb->length < 2)
result = 0;
else
{
bufptr = fb->buffer + rules->offset - fb->offset;
shortv = (short)((bufptr[0] << 8) | bufptr[1]);
result = (shortv == rules->value.shortv);
}
break;
case MIME_MAGIC_INT :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + 4) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_INT fb->length=%d", fb->length));
}
if (fb->length < 4)
result = 0;
else
{
bufptr = fb->buffer + rules->offset - fb->offset;
intv = (unsigned)((((((bufptr[0] << 8) | bufptr[1]) << 8) | bufptr[2]) << 8) | bufptr[3]);
result = (intv == rules->value.intv);
}
break;
case MIME_MAGIC_LOCALE :
#if defined(WIN32) || defined(__EMX__) || defined(__APPLE__)
result = !strcmp(rules->value.localev, setlocale(LC_ALL, ""));
#else
result = !strcmp(rules->value.localev, setlocale(LC_MESSAGES, ""));
#endif
break;
case MIME_MAGIC_CONTAINS :
if (fb->offset < 0 || rules->offset < fb->offset ||
(rules->offset + rules->region) > (fb->offset + fb->length))
{
cupsFileSeek(fb->fp, rules->offset);
fb->length = cupsFileRead(fb->fp, (char *)fb->buffer,
sizeof(fb->buffer));
fb->offset = rules->offset;
DEBUG_printf(("4mime_check_rules: MIME_MAGIC_CONTAINS fb->length=%d", fb->length));
}
if ((rules->offset + rules->length) > (fb->offset + fb->length))
result = 0;
else
{
if (fb->length > rules->region)
region = rules->region - rules->length;
else
region = fb->length - rules->length;
for (n = 0; n < region; n ++)
if ((result = (memcmp(fb->buffer + rules->offset - fb->offset + n, rules->value.stringv, (size_t)rules->length) == 0)) != 0)
break;
}
break;
default :
if (rules->child != NULL)
result = mime_check_rules(filename, fb, rules->child);
else
result = 0;
break;
}
if (rules->invert)
result = !result;
DEBUG_printf(("5mime_check_rules: result of test %p (MIME_MAGIC_%s) is %d",
rules, debug_ops[rules->op], result));
if ((result && logic == MIME_MAGIC_OR) ||
(!result && logic == MIME_MAGIC_AND))
return (result);
rules = rules->next;
}
return (result);
}
static int
mime_patmatch(const char *s,
const char *pat)
{
if (s == NULL || pat == NULL)
return (0);
while (*s != '\0' && *pat != '\0')
{
if (*pat == '*')
{
pat ++;
if (*pat == '\0')
return (1);
while (*s != '\0')
{
if (mime_patmatch(s, pat))
return (1);
s ++;
}
}
else if (*pat == '?')
{
pat ++;
s ++;
continue;
}
else if (*pat == '[')
{
pat ++;
while (*pat != ']' && *pat != '\0')
if (*s == *pat)
break;
else
pat ++;
if (*pat == ']' || *pat == '\0')
return (0);
while (*pat != ']' && *pat != '\0')
pat ++;
if (*pat == ']')
pat ++;
continue;
}
else if (*pat == '\\')
{
pat ++;
}
if (*pat++ != *s++)
return (0);
}
return (*s == *pat);
}