| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * mod_mime_libmagic: determine the Content-Type of a file from its |
| * contents using libmagic(3) from the "file" package. A fallback for |
| * files which mod_mime cannot type by extension, in the style of |
| * mod_mime_magic, but using the system magic database and matching |
| * engine rather than a private copy. |
| * |
| * A magic_t handle is opened, loaded and closed for each request: all |
| * libmagic's mutable state lives in the handle, so this is safe from |
| * any number of threads, and loading a compiled (.mgc) database is an |
| * mmap() of a file already in the page cache. |
| */ |
| |
| #include "apr.h" |
| #include "apr_strings.h" |
| #include "apr_lib.h" |
| |
| #include <errno.h> |
| |
| #include "ap_config.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| #include "http_log.h" |
| #include "http_protocol.h" |
| #include "http_request.h" |
| |
| #include <magic.h> |
| |
| module AP_MODULE_DECLARE_DATA mime_libmagic_module; |
| |
| #define UNSET (-1) |
| #define DEFAULT_BYTES 16384 |
| #define MAX_BYTES 1048576 |
| |
| typedef struct { |
| int enabled; /* MimeLibmagic On|Off */ |
| const char *magicfile; /* MimeLibmagicFile, or the library default */ |
| apr_int64_t bytes; /* MimeLibmagicBytes */ |
| int charset; /* MimeLibmagicCharset On|Off */ |
| } libmagic_server_conf; |
| |
| static void *create_server_conf(apr_pool_t *p, server_rec *s) |
| { |
| libmagic_server_conf *conf = apr_pcalloc(p, sizeof *conf); |
| |
| conf->enabled = UNSET; |
| conf->magicfile = NULL; |
| conf->bytes = UNSET; |
| conf->charset = UNSET; |
| return conf; |
| } |
| |
| static void *merge_server_conf(apr_pool_t *p, void *basev, void *addv) |
| { |
| libmagic_server_conf *base = basev, *add = addv; |
| libmagic_server_conf *conf = apr_palloc(p, sizeof *conf); |
| |
| conf->enabled = add->enabled != UNSET ? add->enabled : base->enabled; |
| conf->magicfile = add->magicfile ? add->magicfile : base->magicfile; |
| conf->bytes = add->bytes != UNSET ? add->bytes : base->bytes; |
| conf->charset = add->charset != UNSET ? add->charset : base->charset; |
| return conf; |
| } |
| |
| static const char *set_enabled(cmd_parms *cmd, void *dummy, int flag) |
| { |
| libmagic_server_conf *conf = |
| ap_get_module_config(cmd->server->module_config, &mime_libmagic_module); |
| |
| conf->enabled = flag; |
| return NULL; |
| } |
| |
| static const char *set_magicfile(cmd_parms *cmd, void *dummy, const char *arg) |
| { |
| libmagic_server_conf *conf = |
| ap_get_module_config(cmd->server->module_config, &mime_libmagic_module); |
| |
| conf->magicfile = ap_server_root_relative(cmd->pool, arg); |
| if (!conf->magicfile) { |
| return apr_pstrcat(cmd->pool, "Invalid MimeLibmagicFile path ", arg, NULL); |
| } |
| return NULL; |
| } |
| |
| static const char *set_bytes(cmd_parms *cmd, void *dummy, const char *arg) |
| { |
| libmagic_server_conf *conf = |
| ap_get_module_config(cmd->server->module_config, &mime_libmagic_module); |
| char *end; |
| apr_int64_t n = apr_strtoi64(arg, &end, 10); |
| |
| if (*arg == '\0' || *end != '\0' || n < 1 || n > MAX_BYTES) { |
| return "MimeLibmagicBytes must be between 1 and " |
| APR_STRINGIFY(MAX_BYTES); |
| } |
| conf->bytes = n; |
| return NULL; |
| } |
| |
| static const char *set_charset(cmd_parms *cmd, void *dummy, int flag) |
| { |
| libmagic_server_conf *conf = |
| ap_get_module_config(cmd->server->module_config, &mime_libmagic_module); |
| |
| conf->charset = flag; |
| return NULL; |
| } |
| |
| /* Open a handle with flags and load the database at path. Returns |
| * NULL after logging the failure. */ |
| static magic_t open_magic(request_rec *r, int flags, const char *path) |
| { |
| magic_t ms = magic_open(flags); |
| |
| if (!ms) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), r, |
| APLOGNO(10624) "libmagic: could not open handle"); |
| return NULL; |
| } |
| |
| if (magic_load(ms, path) != 0) { |
| const char *err = magic_error(ms); |
| |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(magic_errno(ms)), |
| r, APLOGNO(10625) "libmagic: could not load magic " |
| "database %s: %s", path, err ? err : "unknown error"); |
| magic_close(ms); |
| return NULL; |
| } |
| |
| return ms; |
| } |
| |
| /* Set the content type from libmagic's result string, either "type" |
| * or "type; charset=encoding". Returns OK or DECLINED. */ |
| static int set_type(request_rec *r, const char *result, int charset_on) |
| { |
| char *type = apr_pstrdup(r->pool, result); |
| char *charset = NULL; |
| const char *slash, *p; |
| char *sep; |
| |
| sep = strchr(type, ';'); |
| if (sep) { |
| *sep++ = '\0'; |
| while (*sep == ' ') { |
| sep++; |
| } |
| if (strncmp(sep, "charset=", 8) == 0) { |
| charset = sep + 8; |
| } |
| } |
| |
| /* Some matchers append a note, e.g. "application/x-executable, no |
| * program header": keep the type. */ |
| type[strcspn(type, ", \t")] = '\0'; |
| |
| /* the type must be of the form token "/" token */ |
| slash = ap_scan_http_token(type); |
| if (slash == type || *slash != '/' |
| || (p = ap_scan_http_token(slash + 1)) == slash + 1 || *p != '\0') { |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10626) |
| "libmagic: ignoring invalid type '%s' for %s", |
| result, r->filename); |
| return DECLINED; |
| } |
| ap_content_type_tolower(type); |
| |
| /* Unknown binary content is left untyped, as mod_mime_magic does. */ |
| if (strcmp(type, "application/octet-stream") == 0 |
| || strcmp(type, "application/x-empty") == 0) { |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, |
| "libmagic: no type for %s (%s)", r->filename, result); |
| return DECLINED; |
| } |
| |
| if (charset_on && charset && strncmp(type, "text/", 5) == 0 |
| && strcmp(charset, "binary") != 0 |
| && strcmp(charset, "unknown-8bit") != 0 |
| && *charset && *ap_scan_http_token(charset) == '\0') { |
| type = apr_pstrcat(r->pool, type, "; charset=", charset, NULL); |
| } |
| |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, |
| "libmagic: %s is '%s' (%s)", r->filename, type, result); |
| ap_set_content_type_ex(r, type, 1); |
| return OK; |
| } |
| |
| static int libmagic_find_ct(request_rec *r) |
| { |
| const libmagic_server_conf *conf; |
| apr_file_t *fd; |
| apr_status_t rv; |
| apr_size_t nbytes, bytes; |
| magic_t ms; |
| const char *result, *path; |
| char *buf; |
| int ret, flags; |
| |
| /* the file has to exist */ |
| if (r->finfo.filetype == APR_NOFILE || !r->filename) { |
| return DECLINED; |
| } |
| |
| /* was someone else already here? */ |
| if (r->content_type) { |
| return DECLINED; |
| } |
| |
| conf = ap_get_module_config(r->server->module_config, &mime_libmagic_module); |
| if (conf->enabled != 1) { |
| return DECLINED; |
| } |
| |
| switch (r->finfo.filetype) { |
| case APR_DIR: |
| ap_set_content_type_ex(r, DIR_MAGIC_TYPE, 1); |
| return OK; |
| case APR_CHR: |
| case APR_BLK: |
| case APR_PIPE: |
| case APR_SOCK: |
| ap_set_content_type_ex(r, "application/octet-stream", 1); |
| return OK; |
| case APR_REG: |
| break; |
| default: |
| /* a broken symlink, or something exotic: leave it to the handler */ |
| return DECLINED; |
| } |
| |
| rv = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY, |
| APR_OS_DEFAULT, r->pool); |
| if (rv != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, |
| "libmagic: could not open %s", r->filename); |
| return DECLINED; |
| } |
| |
| bytes = conf->bytes != UNSET ? conf->bytes : DEFAULT_BYTES; |
| buf = apr_palloc(r->pool, bytes); |
| rv = apr_file_read_full(fd, buf, bytes, &nbytes); |
| apr_file_close(fd); |
| if (rv != APR_SUCCESS && !APR_STATUS_IS_EOF(rv)) { |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(10627) |
| "libmagic: could not read %s", r->filename); |
| return DECLINED; |
| } |
| |
| if (nbytes == 0) { |
| ap_set_content_type_ex(r, "text/plain", 1); |
| return OK; |
| } |
| |
| /* With no MimeLibmagicFile, the library's default database; action |
| * 1 means the user's ~/.magic is not consulted, unlike |
| * magic_load(ms, NULL). */ |
| path = conf->magicfile ? conf->magicfile : magic_getpath(NULL, 1); |
| /* Never look inside compressed files: that path spawns external |
| * decompressors from the calling thread. */ |
| flags = MAGIC_MIME_TYPE | MAGIC_NO_CHECK_COMPRESS |
| | (conf->charset == 1 ? MAGIC_MIME_ENCODING : 0); |
| ms = open_magic(r, flags, path); |
| if (!ms) { |
| return DECLINED; |
| } |
| |
| result = magic_buffer(ms, buf, nbytes); |
| if (!result) { |
| const char *err = magic_error(ms); |
| |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_FROM_OS_ERROR(magic_errno(ms)), |
| r, APLOGNO(10628) "libmagic: could not determine type " |
| "of %s: %s", r->filename, err ? err : "unknown error"); |
| ret = DECLINED; |
| } |
| else { |
| /* result points into the handle, so must be used before close */ |
| ret = set_type(r, result, conf->charset == 1); |
| } |
| |
| magic_close(ms); |
| |
| return ret; |
| } |
| |
| static const command_rec libmagic_cmds[] = { |
| AP_INIT_FLAG("MimeLibmagic", set_enabled, NULL, RSRC_CONF, |
| "Enable content type detection using libmagic"), |
| AP_INIT_TAKE1("MimeLibmagicFile", set_magicfile, NULL, RSRC_CONF, |
| "Path to the magic database, in place of the library default"), |
| AP_INIT_TAKE1("MimeLibmagicBytes", set_bytes, NULL, RSRC_CONF, |
| "Number of bytes of a file examined"), |
| AP_INIT_FLAG("MimeLibmagicCharset", set_charset, NULL, RSRC_CONF, |
| "Add a charset parameter to text types"), |
| {NULL} |
| }; |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| static const char *const pre[] = { "mod_mime.c", NULL }; |
| static const char *const post[] = { "mod_mime_magic.c", NULL }; |
| |
| /* Run after mod_mime and before mod_mime_magic, since this module |
| * is default-off and it should win over mod_mime_magic when |
| * enabled. */ |
| ap_hook_type_checker(libmagic_find_ct, pre, post, APR_HOOK_MIDDLE); |
| } |
| |
| AP_DECLARE_MODULE(mime_libmagic) = |
| { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* dir config creator */ |
| NULL, /* dir merger --- default is to override */ |
| create_server_conf, /* server config */ |
| merge_server_conf, /* merge server config */ |
| libmagic_cmds, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |