1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-11-04 08:17:17 -05:00
elinks/src/mime/mime.c
2022-01-28 16:17:25 +01:00

374 lines
8.3 KiB
C

/* Functionality for handling mime types */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* XXX: we _WANT_ strcasestr() ! */
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "elinks.h"
#include "cache/cache.h"
#include "config/options.h"
#include "encoding/encoding.h"
#include "intl/libintl.h"
#include "main/module.h"
#include "mime/backend/common.h"
#include "mime/mime.h"
#include "protocol/header.h" /* For parse_header() */
#include "protocol/uri.h"
#include "util/conv.h"
#include "util/file.h"
#include "util/memory.h"
#include "util/string.h"
enum mime_options {
MIME_TREE,
MIME_DEFAULT_TYPE,
MIME_OPTIONS,
};
static union option_info mime_options[] = {
INIT_OPT_TREE(C_(""), N_("MIME"),
C_("mime"), OPT_SORT,
N_("MIME-related options (handlers of various MIME types).")),
INIT_OPT_STRING(C_("mime"), N_("Default MIME-type"),
C_("default_type"), OPT_ZERO, DEFAULT_MIME_TYPE,
N_("Document MIME-type to assume by default "
"(when we are unable to guess it properly "
"from known information about the document).")),
NULL_OPTION_INFO,
};
#define get_opt_mime(which) mime_options[(which)].option
#define get_default_mime_type() get_opt_mime(MIME_DEFAULT_TYPE).value.string
/* Checks protocols headers for a suitable filename */
static char *
get_content_filename(struct uri *uri, struct cache_entry *cached)
{
char *filename, *pos;
if (!cached) cached = find_in_cache(uri);
if (!cached || !cached->head)
return NULL;
pos = parse_header(cached->head, "Content-Disposition", NULL);
if (!pos) return NULL;
parse_header_param(pos, "filename", &filename, 1);
mem_free(pos);
if (!filename) return NULL;
/* Remove start and ending quotes. */
if (filename[0] == '"') {
int len = strlen(filename);
if (len > 1 && filename[len - 1] == '"') {
filename[len - 1] = 0;
memmove(filename, filename + 1, len);
}
/* It was an empty quotation: "" */
if (!filename[1]) {
mem_free(filename);
return NULL;
}
}
/* We don't want to add any directories from the path so make sure we
* only add the filename. */
pos = get_filename_position(filename);
if (!*pos) {
mem_free(filename);
return NULL;
}
if (pos > filename)
memmove(filename, pos, strlen(pos) + 1);
return filename;
}
/* Checks if application/x-<extension> has any handlers. */
static inline char *
check_extension_type(char *extension)
{
/* Trim the extension so only last .<extension> is used. */
char *trimmed = strrchr(extension, '.');
struct mime_handler *handler;
char *content_type;
if (!trimmed)
return NULL;
content_type = straconcat("application/x-", trimmed + 1,
(char *) NULL);
if (!content_type)
return NULL;
handler = get_mime_type_handler(content_type, 1);
if (handler) {
mem_free(handler);
return content_type;
}
mem_free(content_type);
return NULL;
}
/* Check if part of the extension coresponds to a supported encoding and if it
* has any handlers. */
static inline char *
check_encoding_type(char *extension)
{
stream_encoding_T encoding = guess_encoding(extension);
const char *const *extension_list;
char *last_extension = strrchr(extension, '.');
if (encoding == ENCODING_NONE || !last_extension)
return NULL;
for (extension_list = listext_encoded(encoding);
extension_list && *extension_list;
extension_list++) {
char *content_type;
if (strcmp(*extension_list, last_extension))
continue;
*last_extension = '\0';
content_type = get_content_type_backends(extension);
*last_extension = '.';
return content_type;
}
return NULL;
}
#if 0
#define DEBUG_CONTENT_TYPE
#endif
#ifdef DEBUG_CONTENT_TYPE
#define debug_get_content_type_params(cached) \
DBG("get_content_type(head, url)\n=== head ===\n%s\n=== url ===\n%s\n", (cached)->head, struri((cached)->uri))
#define debug_ctype(ctype__) DBG("ctype= %s", (ctype__))
#define debug_extension(extension__) DBG("extension= %s", (extension__))
#else
#define debug_get_content_type_params(cached)
#define debug_ctype(ctype__)
#define debug_extension(extension__)
#endif
char *
get_extension_content_type(char *extension)
{
char *ctype;
assert(extension && *extension);
ctype = get_content_type_backends(extension);
debug_ctype(ctype);
if (ctype) return ctype;
ctype = check_encoding_type(extension);
debug_ctype(ctype);
if (ctype) return ctype;
ctype = check_extension_type(extension);
debug_ctype(ctype);
return ctype;
}
char *
get_cache_header_content_type(struct cache_entry *cached)
{
char *extension, *ctype;
ctype = parse_header(cached->head, "Content-Type", NULL);
if (ctype) {
char *end = strchr(ctype, ';');
int ctypelen;
if (end) *end = '\0';
ctypelen = strlen(ctype);
while (ctypelen && ctype[--ctypelen] <= ' ')
ctype[ctypelen] = '\0';
debug_ctype(ctype);
if (*ctype) {
return ctype;
}
mem_free(ctype);
}
/* This searches cached->head for filename so put here */
extension = get_content_filename(cached->uri, cached);
debug_extension(extension);
if (extension) {
ctype = get_extension_content_type(extension);
mem_free(extension);
if (ctype) {
return ctype;
}
}
return NULL;
}
static char *
get_fragment_content_type(struct cache_entry *cached)
{
struct fragment *fragment;
size_t length;
char *sample;
char *ctype = NULL;
if (list_empty(cached->frag))
return NULL;
fragment = (struct fragment *)cached->frag.next;
if (fragment->offset)
return NULL;
length = fragment->length > 1024 ? 1024 : fragment->length;
sample = memacpy(fragment->data, length);
if (!sample)
return NULL;
if (c_strcasestr((const char *)sample, "<html>"))
ctype = stracpy("text/html");
mem_free(sample);
return ctype;
}
char *
get_content_type(struct cache_entry *cached)
{
char *extension, *ctype;
debug_get_content_type_params(cached);
if (cached->content_type)
return cached->content_type;
/* If there's one in header, it's simple.. */
if (cached->head) {
ctype = get_cache_header_content_type(cached);
if (ctype && *ctype) {
cached->content_type = ctype;
return ctype;
}
mem_free_if(ctype);
}
/* We can't use the extension string we are getting below, because we
* want to support also things like "ps.gz" - that'd never work, as we
* would always compare only to "gz". */
/* Guess type accordingly to the extension */
extension = get_extension_from_uri(cached->uri);
debug_extension(extension);
if (extension) {
/* XXX: A little hack for making extension handling case
* insensitive. We could probably do it better by making
* guess_encoding() case independent the real problem however
* is with default (via option system) and mimetypes resolving
* doing that option and hash lookup will not be easy to
* convert. --jonas */
convert_to_lowercase_locale_indep(extension, strlen(extension));
ctype = get_extension_content_type(extension);
mem_free(extension);
if (ctype && *ctype) {
cached->content_type = ctype;
return ctype;
}
mem_free_if(ctype);
}
ctype = get_fragment_content_type(cached);
if (ctype && *ctype) {
cached->content_type = ctype;
return ctype;
}
debug_ctype(get_default_mime_type());
/* text/plain for pager mode */
if (cached->uri && cached->uri->string
&& !strcmp(cached->uri->string, "file:///dev/stdin")) {
cached->content_type = stracpy("text/plain");
} else
/* Fallback.. use some hardwired default */
cached->content_type = stracpy(get_default_mime_type());
return cached->content_type;
}
struct mime_handler *
get_mime_type_handler(char *content_type, int xwin)
{
return get_mime_handler_backends(content_type, xwin);
}
struct string *
add_mime_filename_to_string(struct string *string, struct uri *uri)
{
char *filename = get_content_filename(uri, NULL);
assert(uri->data);
if (filename) {
add_shell_safe_to_string(string, filename, strlen(filename));
mem_free(filename);
return string;
}
return add_uri_to_string(string, uri, URI_FILENAME);
}
/* Backends dynamic area: */
#include "mime/backend/default.h"
#include "mime/backend/mailcap.h"
#include "mime/backend/mimetypes.h"
static struct module *mime_submodules[] = {
&default_mime_module,
#ifdef CONFIG_MAILCAP
&mailcap_mime_module,
#endif
#ifdef CONFIG_MIMETYPES
&mimetypes_mime_module,
#endif
NULL,
};
struct module mime_module = struct_module(
/* name: */ N_("MIME"),
/* options: */ mime_options,
/* hooks: */ NULL,
/* submodules: */ mime_submodules,
/* data: */ NULL,
/* init: */ NULL,
/* done: */ NULL
);