From 443d42608dee4b30c1494802f4041f0e2970cf3e Mon Sep 17 00:00:00 2001 From: Witold Filipczyk Date: Sat, 21 May 2022 18:17:04 +0200 Subject: [PATCH] [dgi] Experimental DGI support. Dos Gateway Interface was introduced by Arachne browser. I tested two cases: file/cdplayer.dgi |[7]$ecdplayer.exe $s application/pdf pdf>txt|$epdftotext $1 $2 --- src/mime/backend/Makefile | 1 + src/mime/backend/common.c | 4 + src/mime/backend/dgi.c | 460 ++++++++++++++++++++++++++++++++++ src/mime/backend/dgi.h | 21 ++ src/mime/backend/meson.build | 3 + src/mime/mime.c | 4 + src/mime/mime.h | 3 + src/protocol/file/dgi.c | 471 +++++++++++++++++++++++++---------- src/protocol/file/dgi.h | 5 +- src/protocol/protocol.c | 1 + src/protocol/protocol.h | 1 + src/session/download.c | 102 +++++++- src/session/download.h | 23 ++ 13 files changed, 970 insertions(+), 129 deletions(-) create mode 100644 src/mime/backend/dgi.c create mode 100644 src/mime/backend/dgi.h diff --git a/src/mime/backend/Makefile b/src/mime/backend/Makefile index 9c0f1e00..57c01eae 100644 --- a/src/mime/backend/Makefile +++ b/src/mime/backend/Makefile @@ -1,6 +1,7 @@ top_builddir=../../.. include $(top_builddir)/Makefile.config +OBJS-$(CONFIG_DGI) += dgi.o OBJS-$(CONFIG_MAILCAP) += mailcap.o OBJS-$(CONFIG_MIMETYPES) += mimetypes.o diff --git a/src/mime/backend/common.c b/src/mime/backend/common.c index a5a6bfc3..4ef1b88e 100644 --- a/src/mime/backend/common.c +++ b/src/mime/backend/common.c @@ -21,11 +21,15 @@ /* Backends dynamic area: */ #include "mime/backend/default.h" +#include "mime/backend/dgi.h" #include "mime/backend/mailcap.h" #include "mime/backend/mimetypes.h" static const struct mime_backend *const mime_backends[] = { &default_mime_backend, +#ifdef CONFIG_DGI + &dgi_mime_backend, +#endif #ifdef CONFIG_MAILCAP &mailcap_mime_backend, #endif diff --git a/src/mime/backend/dgi.c b/src/mime/backend/dgi.c new file mode 100644 index 00000000..3b143533 --- /dev/null +++ b/src/mime/backend/dgi.c @@ -0,0 +1,460 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "elinks.h" + +#include "config/options.h" +#include "intl/libintl.h" +#include "main/module.h" +#include "mime/backend/common.h" +#include "mime/backend/dgi.h" +#include "mime/mime.h" +#include "osdep/osdep.h" /* For exe() */ +#include "session/session.h" +#include "util/file.h" +#include "util/hash.h" +#include "util/lists.h" +#include "util/memory.h" +#include "util/string.h" + +struct dgi_hash_item { + /* The entries associated with the type */ + LIST_OF(struct dgi_entry) entries; + + /* The content type of all @entries. Must be last! */ + char type[1]; +}; + +struct dgi_entry { + LIST_HEAD(struct dgi_entry); + + char *type; + char *inpext; + char *outext; + + /* The 'raw' unformatted (view)command from the dgi files. */ + char command[1]; +}; + +/* State variables */ +static struct hash *dgi_map = NULL; + +enum dgi_option { + DGI_TREE, + + DGI_ENABLE, + DGI_MIME_CFG, + DGI_ASK +}; + +static union option_info dgi_options[] = { + INIT_OPT_TREE("mime", N_("DGI"), + "dgi", OPT_ZERO, + N_("Dos gateway interface specific options.")), + + INIT_OPT_BOOL("mime.dgi", N_("Enable"), + "enable", OPT_ZERO, 1, + N_("Enable DGI support.")), + + INIT_OPT_STRING("mime.dgi", N_("Config filename"), + "mime_cfg", OPT_ZERO, "mime.cfg", + N_("Filename and location of config file for DGI.")), + + INIT_OPT_BOOL("mime.dgi", N_("Ask before opening"), + "ask", OPT_ZERO, 0, + N_("Ask before using the handlers defined by DGI.")), + + INIT_OPT_STRING("mime.dgi", N_("Path $a"), + "a", OPT_ZERO, "", + N_("Path to cache.")), + INIT_OPT_STRING("mime.dgi", N_("Path $b"), + "b", OPT_ZERO, "", + N_("Full name of bookmarks.")), + INIT_OPT_STRING("mime.dgi", N_("Path $c"), + "c", OPT_ZERO, "", + N_("Full name of cache index.")), + INIT_OPT_STRING("mime.dgi", N_("Path $d"), + "d", OPT_ZERO, "", + N_("Document name.")), + INIT_OPT_STRING("mime.dgi", N_("Path $e"), + "e", OPT_ZERO, "", + N_("Path to executable files.")), + INIT_OPT_STRING("mime.dgi", N_("Path $f"), + "f", OPT_ZERO, "", + N_("File browser arguments.")), + INIT_OPT_STRING("mime.dgi", N_("Path $g"), + "g", OPT_ZERO, "", + N_("IP address of 1st gateway.")), + INIT_OPT_STRING("mime.dgi", N_("Path $h"), + "h", OPT_ZERO, "", + N_("Full name of History file.")), + INIT_OPT_STRING("mime.dgi", N_("Path $i"), + "i", OPT_ZERO, "", + N_("Your IP address.")), + INIT_OPT_STRING("mime.dgi", N_("Path $j"), + "j", OPT_ZERO, "", + N_("DJPEG arguments.")), + INIT_OPT_STRING("mime.dgi", N_("Path $l"), + "l", OPT_ZERO, "", + N_("Last visited document.")), + INIT_OPT_STRING("mime.dgi", N_("Path $m"), + "m", OPT_ZERO, "", + N_("Path to mail.")), + INIT_OPT_STRING("mime.dgi", N_("Path $n"), + "n", OPT_ZERO, "", + N_("IP address of 1st nameserver.")), + INIT_OPT_STRING("mime.dgi", N_("Path $p"), + "p", OPT_ZERO, "", + N_("Host.")), + INIT_OPT_STRING("mime.dgi", N_("Path $q"), + "q", OPT_ZERO, "", + N_("Filename of query string (file created only " + "when using this macro).")), + INIT_OPT_STRING("mime.dgi", N_("Path $r"), + "r", OPT_ZERO, "", + N_("Horizontal resolution of screen.")), + INIT_OPT_STRING("mime.dgi", N_("Path $s"), + "s", OPT_ZERO, "", + N_("CGI compatible query string.")), + INIT_OPT_STRING("mime.dgi", N_("Path $t"), + "t", OPT_ZERO, "", + N_("Path for temporary files.")), + INIT_OPT_STRING("mime.dgi", N_("Path $u"), + "u", OPT_ZERO, "", + N_("URL of document.")), + INIT_OPT_STRING("mime.dgi", N_("Path $w"), + "w", OPT_ZERO, "", + N_("Download path.")), + INIT_OPT_STRING("mime.dgi", N_("Path $x"), + "x", OPT_ZERO, "", + N_("Netmask.")), + NULL_OPTION_INFO, +}; + +#define get_opt_dgi(which) dgi_options[(which)].option +#define get_dgi(which) get_opt_dgi(which).value +#define get_dgi_ask() get_dgi(DGI_ASK).number +#define get_dgi_enable() get_dgi(DGI_ENABLE).number + +static inline void +done_dgi_entry(struct dgi_entry *entry) +{ + if (!entry) return; + mem_free_if(entry->type); + mem_free_if(entry->inpext); + mem_free_if(entry->outext); + mem_free(entry); +} + +/* Takes care of all initialization of dgi entries. + * Clear memory to make freeing it safer later and we get. */ +static inline struct dgi_entry * +init_dgi_entry(char *type, char *inpext, char *outext, char *command) +{ + struct dgi_entry *entry; + int commandlen = strlen(command); + + entry = (struct dgi_entry *)mem_calloc(1, sizeof(*entry) + commandlen); + if (!entry) return NULL; + + memcpy(entry->command, command, commandlen); + entry->type = stracpy(type); + + if (inpext) { + entry->inpext = straconcat(".", inpext, NULL); + } + + if (outext) { + entry->outext = straconcat(".", outext, NULL); + } + + return entry; +} + +static inline void +add_dgi_entry(struct dgi_entry *entry, char *type, int typelen) +{ + struct dgi_hash_item *mitem; + struct hash_item *item; + + /* Time to get the entry into the dgi_map */ + /* First check if the type is already checked in */ + item = get_hash_item(dgi_map, type, typelen); + if (!item) { + mitem = (struct dgi_hash_item *)mem_alloc(sizeof(*mitem) + typelen); + if (!mitem) { + done_dgi_entry(entry); + return; + } + + safe_strncpy(mitem->type, type, typelen + 1); + + init_list(mitem->entries); + + item = add_hash_item(dgi_map, mitem->type, typelen, mitem); + if (!item) { + mem_free(mitem); + done_dgi_entry(entry); + return; + } + } else if (item->value) { + mitem = (struct dgi_hash_item *)item->value; + } else { + done_dgi_entry(entry); + return; + } + + add_to_list_end(mitem->entries, entry); +} + +/* Parses whole mime_cfg file line-by-line adding entries to the map */ +static void +parse_dgi_file(char *filename) +{ + FILE *file = fopen(filename, "rb"); + char *line = NULL; + size_t linelen = MAX_STR_LEN; + int lineno = 1; + + if (!file) return; + + while ((line = file_read_line(line, &linelen, file, &lineno))) { + struct dgi_entry *entry; + char *linepos; + char *command; + char *basetypeend; + char *type; + char *inpext, *outext; + char *pipe, *greater, *space; + int typelen; + + /* Ignore comments */ + if (*line == ';' || *line == '[') continue; + + linepos = line; + + pipe = strchr(linepos, '|'); + if (!pipe) continue; + + *pipe = '\0'; + command = pipe + 1; + + greater = strchr(linepos, '>'); + + if (!greater) { + outext = NULL; + } else { + *greater = '\0'; + outext = greater + 1; + } + + space = strrchr(linepos, ' '); + if (!space) { + inpext = NULL; + } else { + inpext = space + 1; + } + + space = strchr(linepos, ' '); + if (!space) continue; + *space = '\0'; + + type = linepos; + if (!*type) continue; + + entry = init_dgi_entry(type, inpext, outext, command); + if (!entry) continue; + + basetypeend = strchr(type, '/'); + typelen = strlen(type); + + if (!basetypeend) { + char implicitwild[64]; + + if (typelen + 3 > sizeof(implicitwild)) { + done_dgi_entry(entry); + continue; + } + + memcpy(implicitwild, type, typelen); + implicitwild[typelen++] = '/'; + implicitwild[typelen++] = '*'; + implicitwild[typelen++] = '\0'; + add_dgi_entry(entry, implicitwild, typelen); + continue; + } + + add_dgi_entry(entry, type, typelen); + } + + fclose(file); + mem_free_if(line); /* Alloced by file_read_line() */ +} + +static struct hash * +init_dgi_map(void) +{ + dgi_map = init_hash8(); + + if (!dgi_map) return NULL; + + parse_dgi_file(get_opt_str("mime.dgi.mime_cfg", NULL)); + + return dgi_map; +} + +static void +done_dgi(struct module *module) +{ + struct hash_item *item; + int i; + + if (!dgi_map) return; + + foreach_hash_item (item, *dgi_map, i) { + struct dgi_hash_item *mitem = (struct dgi_hash_item *)item->value; + + if (!mitem) continue; + + while (!list_empty(mitem->entries)) { + struct dgi_entry *entry = (struct dgi_entry *)mitem->entries.next; + + del_from_list(entry); + done_dgi_entry(entry); + } + + mem_free(mitem); + } + + free_hash(&dgi_map); +} + +static int +change_hook_dgi(struct session *ses, struct option *current, struct option *changed) +{ + if (changed == &get_opt_dgi(DGI_MIME_CFG) + || (changed == &get_opt_dgi(DGI_ENABLE) + && !get_dgi_enable())) { + done_dgi(&dgi_mime_module); + } + + return 0; +} + +static void +init_dgi(struct module *module) +{ + static const struct change_hook_info mimetypes_change_hooks[] = { + { "mime.dgi.enable", change_hook_dgi }, + { "mime.dgi.mime_cfg", change_hook_dgi }, + { NULL, NULL }, + }; + + register_change_hooks(mimetypes_change_hooks); + + if (get_cmd_opt_bool("anonymous")) + get_opt_bool("mime.dgi.enable", NULL) = 0; +} + +/* Returns first usable dgi_entry from a list where @entry is the head. + * Use of @filename is not supported (yet). */ +static struct dgi_entry * +check_entries(struct dgi_hash_item *item) +{ + struct dgi_entry *entry; + + foreach (entry, item->entries) { + return entry; + } + + return NULL; +} + +static struct dgi_entry * +get_dgi_entry(char *type) +{ + struct dgi_entry *entry; + struct hash_item *item; + + item = get_hash_item(dgi_map, type, strlen(type)); + + /* Check list of entries */ + entry = ((item && item->value) ? check_entries((struct dgi_hash_item *)item->value) : NULL); + + if (!entry) { + struct dgi_entry *wildcard = NULL; + char *wildpos = strchr(type, '/'); + + if (wildpos) { + int wildlen = wildpos - type + 1; /* include '/' */ + char *wildtype = memacpy(type, wildlen + 2); + + if (!wildtype) return NULL; + + wildtype[wildlen++] = '*'; + wildtype[wildlen] = '\0'; + + item = get_hash_item(dgi_map, wildtype, wildlen); + mem_free(wildtype); + + if (item && item->value) + wildcard = check_entries((struct dgi_hash_item *)item->value); + } + + /* Use @wildcard if its priority is better or @entry is NULL */ + if (wildcard && (!entry)) + entry = wildcard; + } + + return entry; +} + +struct mime_handler * +get_mime_handler_dgi(char *type, int xwin) +{ + struct dgi_entry *entry; + struct mime_handler *handler; + char *program; + + if (!get_dgi_enable() + || (!dgi_map && !init_dgi_map())) + return NULL; + + entry = get_dgi_entry(type); + if (!entry) return NULL; + + program = stracpy(entry->command); + if (!program) return NULL; + + handler = init_mime_handler(program, NULL, + dgi_mime_module.name, + get_dgi_ask(), 0); + mem_free(program); + + handler->inpext = entry->inpext; + handler->outext = entry->outext; + handler->dgi = 1; + return handler; +} + +const struct mime_backend dgi_mime_backend = { + /* get_content_type: */ NULL, + /* get_mime_handler: */ get_mime_handler_dgi, +}; + +/* Setup the exported module. */ +struct module dgi_mime_module = struct_module( + /* name: */ N_("DGI mime"), + /* options: */ dgi_options, + /* hooks: */ NULL, + /* submodules: */ NULL, + /* data: */ NULL, + /* init: */ init_dgi, + /* done: */ done_dgi +); diff --git a/src/mime/backend/dgi.h b/src/mime/backend/dgi.h new file mode 100644 index 00000000..90516c3d --- /dev/null +++ b/src/mime/backend/dgi.h @@ -0,0 +1,21 @@ +#ifndef EL__MIME_BACKEND_DGI_H +#define EL__MIME_BACKEND_DGI_H + +#include "main/module.h" +#include "mime/backend/common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct mime_backend dgi_mime_backend; +extern struct module dgi_mime_module; + +struct mime_handler *get_mime_handler_dgi(char *type, int xwin); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/mime/backend/meson.build b/src/mime/backend/meson.build index a00f3cc3..9a115dc5 100644 --- a/src/mime/backend/meson.build +++ b/src/mime/backend/meson.build @@ -1,3 +1,6 @@ +if conf_data.get('CONFIG_DGI') + srcs += files('dgi.c') +endif if conf_data.get('CONFIG_MAILCAP') srcs += files('mailcap.c') endif diff --git a/src/mime/mime.c b/src/mime/mime.c index 2e1501ac..66a1c0c4 100644 --- a/src/mime/mime.c +++ b/src/mime/mime.c @@ -348,11 +348,15 @@ add_mime_filename_to_string(struct string *string, struct uri *uri) /* Backends dynamic area: */ #include "mime/backend/default.h" +#include "mime/backend/dgi.h" #include "mime/backend/mailcap.h" #include "mime/backend/mimetypes.h" static struct module *mime_submodules[] = { &default_mime_module, +#ifdef CONFIG_DGI + &dgi_mime_module, +#endif #ifdef CONFIG_MAILCAP &mailcap_mime_module, #endif diff --git a/src/mime/mime.h b/src/mime/mime.h index 2f042a58..6c471743 100644 --- a/src/mime/mime.h +++ b/src/mime/mime.h @@ -13,9 +13,12 @@ struct uri; struct mime_handler { char *description; const char *backend_name; + char *inpext; + char *outext; unsigned int ask:1; unsigned int block:1; unsigned int copiousoutput:1; + unsigned int dgi:1; char program[1]; /* XXX: Keep last! */ }; diff --git a/src/protocol/file/dgi.c b/src/protocol/file/dgi.c index 195b1574..008d6a4e 100644 --- a/src/protocol/file/dgi.c +++ b/src/protocol/file/dgi.c @@ -25,6 +25,7 @@ #include "cookies/cookies.h" #include "intl/libintl.h" #include "mime/backend/common.h" +#include "mime/backend/dgi.h" #include "network/connection.h" #include "network/progress.h" #include "network/socket.h" @@ -38,92 +39,12 @@ #include "terminal/terminal.h" #include "util/conv.h" #include "util/env.h" +#include "util/qs_parse/qs_parse.h" #include "util/string.h" -static union option_info dgi_options[] = { - INIT_OPT_TREE("protocol.file", N_("DGI"), - "dgi", OPT_ZERO, - N_("Dos gateway interface specific options.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $a"), - "a", OPT_ZERO, "", - N_("Path to cache.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $b"), - "b", OPT_ZERO, "", - N_("Full name of bookmarks.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $c"), - "c", OPT_ZERO, "", - N_("Full name of cache index.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $d"), - "d", OPT_ZERO, "", - N_("Document name.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $e"), - "e", OPT_ZERO, "", - N_("Path to executable files.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $f"), - "f", OPT_ZERO, "", - N_("File browser arguments.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $g"), - "g", OPT_ZERO, "", - N_("IP address of 1st gateway.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $h"), - "h", OPT_ZERO, "", - N_("Full name of History file.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $i"), - "i", OPT_ZERO, "", - N_("Your IP address.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $j"), - "j", OPT_ZERO, "", - N_("DJPEG arguments.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $l"), - "l", OPT_ZERO, "", - N_("Last visited document.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $m"), - "m", OPT_ZERO, "", - N_("Path to mail.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $n"), - "n", OPT_ZERO, "", - N_("IP address of 1st nameserver.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $p"), - "p", OPT_ZERO, "", - N_("Host.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $q"), - "q", OPT_ZERO, "", - N_("Filename of query string (file created only " - "when using this macro).")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $r"), - "r", OPT_ZERO, "", - N_("Horizontal resolution of screen.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $s"), - "s", OPT_ZERO, "", - N_("CGI compatible query string.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $t"), - "t", OPT_ZERO, "", - N_("Path for temporary files.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $u"), - "u", OPT_ZERO, "", - N_("URL of document.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $w"), - "w", OPT_ZERO, "", - N_("Download path.")), - INIT_OPT_STRING("protocol.file.dgi", N_("Path $x"), - "x", OPT_ZERO, "", - N_("Netmask.")), - NULL_OPTION_INFO, -}; - -struct dgi_entry { - const char *name; - const char *cmdline; -}; - -struct dgi_entry entries[] = { - { "cdplayer.dgi", "$ecdplayer.exe $s > $2" }, - NULL -}; - struct module dgi_protocol_module = struct_module( - /* name: */ N_("Dos Gateway Interface (DGI)"), - /* options: */ dgi_options, + /* name: */ N_("DGI"), + /* options: */ NULL, /* hooks: */ NULL, /* submodules: */ NULL, /* data: */ NULL, @@ -131,25 +52,29 @@ struct module dgi_protocol_module = struct_module( /* done: */ NULL ); -static struct dgi_entry * + +static struct mime_handler * find_dgi(const char *name) { const char *last = strrchr(name, '/'); - struct dgi_entry *entry; + struct mime_handler *handler; if (last) { name = last + 1; } - for (entry = entries; entry; entry++) { - if (!entry->name) break; + struct string dtype; - if (!strcmp(name, entry->name)) { - return entry; - } + if (!init_string(&dtype)) { + return NULL; } - return NULL; + add_to_string(&dtype, "file/"); + add_to_string(&dtype, name); + handler = get_mime_handler_dgi(dtype.source, 0); + done_string(&dtype); + + return handler; } static void @@ -175,16 +100,17 @@ write_request_to_file(struct connection *conn, const char *filename) enum dgi_state { NORMAL, DOLAR, - PERCENT + PERCENT, + LEFT_BRACKET }; static void -prepare_command(struct dgi_entry *entry, const char *query, struct string *cmd, char **inp, char **out, char **queryfile) +prepare_command(struct mime_handler *handler, const char *query, struct string *cmd, char **inp, char **out, char **queryfile) { const char *ch; enum dgi_state state = NORMAL; - for (ch = entry->cmdline; *ch; ch++) { + for (ch = handler->program; *ch; ch++) { switch (state) { case NORMAL: default: @@ -192,78 +118,89 @@ prepare_command(struct dgi_entry *entry, const char *query, struct string *cmd, state = DOLAR; } else if (*ch == '%') { state = PERCENT; + } else if (*ch == '[') { + state = LEFT_BRACKET; } else { add_char_to_string(cmd, *ch); } break; + case LEFT_BRACKET: + switch (*ch) { + case ']': + state = NORMAL; + break; + default: + break; + } + break; case DOLAR: case PERCENT: - switch(*ch) { + switch (*ch) { case 'a': - add_to_string(cmd, get_opt_str("protocol.file.dgi.a", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.a", NULL)); state = NORMAL; break; case 'b': - add_to_string(cmd, get_opt_str("protocol.file.dgi.b", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.b", NULL)); state = NORMAL; break; case 'c': - add_to_string(cmd, get_opt_str("protocol.file.dgi.c", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.c", NULL)); state = NORMAL; break; case 'd': - add_to_string(cmd, get_opt_str("protocol.file.dgi.d", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.d", NULL)); state = NORMAL; break; case 'e': - add_to_string(cmd, get_opt_str("protocol.file.dgi.e", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.e", NULL)); state = NORMAL; break; case 'f': - add_to_string(cmd, get_opt_str("protocol.file.dgi.f", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.f", NULL)); state = NORMAL; break; case 'g': - add_to_string(cmd, get_opt_str("protocol.file.dgi.g", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.g", NULL)); state = NORMAL; break; case 'h': - add_to_string(cmd, get_opt_str("protocol.file.dgi.h", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.h", NULL)); state = NORMAL; break; case 'i': - add_to_string(cmd, get_opt_str("protocol.file.dgi.i", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.i", NULL)); state = NORMAL; break; case 'j': - add_to_string(cmd, get_opt_str("protocol.file.dgi.j", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.j", NULL)); state = NORMAL; break; case 'l': - add_to_string(cmd, get_opt_str("protocol.file.dgi.l", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.l", NULL)); state = NORMAL; break; case 'm': - add_to_string(cmd, get_opt_str("protocol.file.dgi.m", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.m", NULL)); state = NORMAL; break; case 'n': - add_to_string(cmd, get_opt_str("protocol.file.dgi.n", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.n", NULL)); state = NORMAL; break; case 'p': - add_to_string(cmd, get_opt_str("protocol.file.dgi.p", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.p", NULL)); state = NORMAL; break; case 'q': - *queryfile = tempname(NULL, "elinks", ".txt"); + *queryfile = tempname(NULL, "elinks", handler->inpext); if (*queryfile) { add_to_string(cmd, *queryfile); } state = NORMAL; break; case 'r': - add_to_string(cmd, get_opt_str("protocol.file.dgi.r", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.r", NULL)); state = NORMAL; break; case 's': @@ -273,30 +210,30 @@ prepare_command(struct dgi_entry *entry, const char *query, struct string *cmd, state = NORMAL; break; case 't': - add_to_string(cmd, get_opt_str("protocol.file.dgi.t", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.t", NULL)); state = NORMAL; break; case 'u': - add_to_string(cmd, get_opt_str("protocol.file.dgi.u", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.u", NULL)); state = NORMAL; break; case 'w': - add_to_string(cmd, get_opt_str("protocol.file.dgi.w", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.w", NULL)); state = NORMAL; break; case 'x': - add_to_string(cmd, get_opt_str("protocol.file.dgi.x", NULL)); + add_to_string(cmd, get_opt_str("mime.dgi.x", NULL)); state = NORMAL; break; case '1': - *inp = tempname(NULL, "elinks", ".txt"); + *inp = tempname(NULL, "elinks", handler->inpext); if (*inp) { add_to_string(cmd, *inp); } state = NORMAL; break; case '2': - *out = tempname(NULL, "elinks", ".htm"); + *out = tempname(NULL, "elinks", handler->outext); if (*out) { add_to_string(cmd, *out); } @@ -312,11 +249,284 @@ prepare_command(struct dgi_entry *entry, const char *query, struct string *cmd, } } +static void +prepare_command2(char *program, const char *filename, char *inpext, char *outext, struct string *cmd, char **inp, char **out) +{ + const char *ch; + char *query = NULL; + enum dgi_state state = NORMAL; + + for (ch = program; *ch; ch++) { + switch (state) { + case NORMAL: + default: + if (*ch == '$') { + state = DOLAR; + } else if (*ch == '%') { + state = PERCENT; + } else if (*ch == '[') { + state = LEFT_BRACKET; + } else { + add_char_to_string(cmd, *ch); + } + break; + case LEFT_BRACKET: + switch (*ch) { + case ']': + state = NORMAL; + break; + default: + break; + } + break; + case DOLAR: + case PERCENT: + switch (*ch) { + case 'a': + add_to_string(cmd, get_opt_str("mime.dgi.a", NULL)); + state = NORMAL; + break; + case 'b': + add_to_string(cmd, get_opt_str("mime.dgi.b", NULL)); + state = NORMAL; + break; + case 'c': + add_to_string(cmd, get_opt_str("mime.dgi.c", NULL)); + state = NORMAL; + break; + case 'd': + add_to_string(cmd, get_opt_str("mime.dgi.d", NULL)); + state = NORMAL; + break; + case 'e': + add_to_string(cmd, get_opt_str("mime.dgi.e", NULL)); + state = NORMAL; + break; + case 'f': + add_to_string(cmd, get_opt_str("mime.dgi.f", NULL)); + state = NORMAL; + break; + case 'g': + add_to_string(cmd, get_opt_str("mime.dgi.g", NULL)); + state = NORMAL; + break; + case 'h': + add_to_string(cmd, get_opt_str("mime.dgi.h", NULL)); + state = NORMAL; + break; + case 'i': + add_to_string(cmd, get_opt_str("mime.dgi.i", NULL)); + state = NORMAL; + break; + case 'j': + add_to_string(cmd, get_opt_str("mime.dgi.j", NULL)); + state = NORMAL; + break; + case 'l': + add_to_string(cmd, get_opt_str("mime.dgi.l", NULL)); + state = NORMAL; + break; + case 'm': + add_to_string(cmd, get_opt_str("mime.dgi.m", NULL)); + state = NORMAL; + break; + case 'n': + add_to_string(cmd, get_opt_str("mime.dgi.n", NULL)); + state = NORMAL; + break; + case 'p': + add_to_string(cmd, get_opt_str("mime.dgi.p", NULL)); + state = NORMAL; + break; + case 'q': + state = NORMAL; + break; + case 'r': + add_to_string(cmd, get_opt_str("mime.dgi.r", NULL)); + state = NORMAL; + break; + case 's': + if (query) { + add_to_string(cmd, query); + } + state = NORMAL; + break; + case 't': + add_to_string(cmd, get_opt_str("mime.dgi.t", NULL)); + state = NORMAL; + break; + case 'u': + add_to_string(cmd, get_opt_str("mime.dgi.u", NULL)); + state = NORMAL; + break; + case 'w': + add_to_string(cmd, get_opt_str("mime.dgi.w", NULL)); + state = NORMAL; + break; + case 'x': + add_to_string(cmd, get_opt_str("mime.dgi.x", NULL)); + state = NORMAL; + break; + case '1': + if (filename) { + add_to_string(cmd, filename); + } + state = NORMAL; + break; + case '2': + *out = tempname(NULL, "elinks", outext); + if (*out) { + add_to_string(cmd, *out); + } + state = NORMAL; + break; + default: + add_char_to_string(cmd, *ch); + state = NORMAL; + break; + } + break; + } + } +} + +void +dgi_protocol_handler(struct connection *conn) +{ +#define NUMKVPAIRS 16 + char *ref, *query; + struct connection_state state = connection_state(S_OK); + int check; + + int i; + char *kvpairs[NUMKVPAIRS]; + char *command=NULL; + char *filename=NULL; + char *inpext=NULL; + char *outext=NULL; + char *del = NULL; + + struct string command_str; + char *tempfilename = NULL; + char *outputfilename = NULL; + + /* security checks */ + if (!conn->referrer || conn->referrer->protocol != PROTOCOL_DGI) { + goto bad; + } + ref = get_uri_string(conn->referrer, URI_PATH); + if (!ref) { + goto bad; + } + check = strcmp(ref, "/"); + mem_free(ref); + if (check) goto bad; + + if (!init_string(&command_str)) { + state = connection_state(S_OUT_OF_MEM); + abort_connection(conn, state); + return; + } + + query = get_uri_string(conn->uri, URI_QUERY); + + if (query) { + i = qs_parse(query, kvpairs, 16); + command = qs_k2v("command", kvpairs, i); + filename = qs_k2v("filename", kvpairs, i); + inpext = qs_k2v("inpext", kvpairs, i); + outext = qs_k2v("outext", kvpairs, i); + del = qs_k2v("delete", kvpairs, i); + } + prepare_command2(command, filename, inpext, outext, &command_str, &tempfilename, &outputfilename); + + system(command_str.source); + done_string(&command_str); + + if (del) { + unlink(filename); + } + + if (tempfilename) { + unlink(tempfilename); + } + + if (!outputfilename) { + state = connection_state(S_OK); + abort_connection(conn, state); + mem_free_if(query); + return; + } + + struct string page; + struct string name; + + if (!init_string(&name)) { + unlink(outputfilename); + mem_free_if(query); + return; + } + add_to_string(&name, outputfilename); + state = read_encoded_file(&name, &page); + unlink(outputfilename); + done_string(&name); + + if (is_in_state(state, S_OK)) { + struct cache_entry *cached; + + /* Try to add fragment data to the connection cache if either + * file reading or directory listing worked out ok. */ + cached = conn->cached = get_cache_entry(conn->uri); + if (!conn->cached) { + state = connection_state(S_OUT_OF_MEM); + } else { + add_fragment(cached, 0, page.source, page.length); + conn->from += page.length; + + if (1) { + char *head; + char *otype = NULL; + + if (outext) { + otype = get_extension_content_type(outext); + } + if (!otype) { + otype = stracpy("text/html"); + } + + /* If the system charset somehow + * changes after the directory listing + * has been generated, it should be + * parsed with the original charset. */ + head = straconcat("\r\nContent-Type: ", otype, "; charset=", + get_cp_mime_name(get_cp_index("System")), + "\r\n", (char *) NULL); + + mem_free_if(otype); + + /* Not so gracefully handle failed memory + * allocation. */ + if (!head) + state = connection_state(S_OUT_OF_MEM); + + /* Setup directory listing for viewing. */ + mem_free_set(&cached->head, head); + } + done_string(&page); + } + } + mem_free_if(query); + abort_connection(conn, state); + return; +bad: + abort_connection(conn, connection_state(S_BAD_URL)); +} + int execute_dgi(struct connection *conn) { char *script; - struct dgi_entry *entry; + struct mime_handler *handler; struct string command; char *tempfilename = NULL; char *outputfilename = NULL; @@ -335,8 +545,8 @@ execute_dgi(struct connection *conn) return 0; } - entry = find_dgi(script); - if (!entry) { + handler = find_dgi(script); + if (!handler) { mem_free(script); return 1; } @@ -348,7 +558,7 @@ execute_dgi(struct connection *conn) char *query = get_uri_string(conn->uri, URI_QUERY); - prepare_command(entry, query, &command, &tempfilename, &outputfilename, &queryfile); + prepare_command(handler, query, &command, &tempfilename, &outputfilename, &queryfile); mem_free_if(query); @@ -363,10 +573,6 @@ execute_dgi(struct connection *conn) fclose(f); } } - - fprintf(stderr, "%s\n", command.source); - - system(command.source); done_string(&command); mem_free(script); @@ -381,6 +587,7 @@ execute_dgi(struct connection *conn) if (!outputfilename) { + mem_free(handler); state = connection_state(S_OK); abort_connection(conn, state); return 0; @@ -391,6 +598,7 @@ execute_dgi(struct connection *conn) if (!init_string(&name)) { unlink(outputfilename); + mem_free(handler); return 0; } add_to_string(&name, outputfilename); @@ -412,15 +620,25 @@ execute_dgi(struct connection *conn) if (1) { char *head; + char *otype = NULL; + + if (handler->outext) { + otype = get_extension_content_type(handler->outext); + } + if (!otype) { + otype = stracpy("text/html"); + } /* If the system charset somehow * changes after the directory listing * has been generated, it should be * parsed with the original charset. */ - head = straconcat("\r\nContent-Type: text/html; charset=", + head = straconcat("\r\nContent-Type: ", otype, "; charset=", get_cp_mime_name(get_cp_index("System")), "\r\n", (char *) NULL); + mem_free_if(otype); + /* Not so gracefully handle failed memory * allocation. */ if (!head) @@ -432,6 +650,7 @@ execute_dgi(struct connection *conn) done_string(&page); } } + mem_free(handler); abort_connection(conn, state); return 0; } diff --git a/src/protocol/file/dgi.h b/src/protocol/file/dgi.h index fedbba91..33f7d9d6 100644 --- a/src/protocol/file/dgi.h +++ b/src/protocol/file/dgi.h @@ -1,7 +1,9 @@ - #ifndef EL__PROTOCOL_FILE_DGI_H #define EL__PROTOCOL_FILE_DGI_H +#include "main/module.h" +#include "protocol/protocol.h" + #ifdef __cplusplus extern "C" { #endif @@ -10,6 +12,7 @@ struct connection; struct module; extern struct module dgi_protocol_module; +extern protocol_handler_T dgi_protocol_handler; int execute_dgi(struct connection *); #ifdef __cplusplus diff --git a/src/protocol/protocol.c b/src/protocol/protocol.c index 42e072c2..a319aa1b 100644 --- a/src/protocol/protocol.c +++ b/src/protocol/protocol.c @@ -63,6 +63,7 @@ static const struct protocol_backend protocol_backends[] = { { "bittorrent", 0, bittorrent_protocol_handler, 0, 0, 1, 0, 1 }, { "bittorrent-peer",0,bittorrent_peer_protocol_handler, 1, 1, 0, 0, 1 }, { "data", 0, data_protocol_handler, 0, 0, 1, 0, 1 }, + { "dgi", 0, dgi_protocol_handler, 0, 0, 0, 0, 0 }, { "file", 0, file_protocol_handler, 1, 0, 0, 0, 0 }, { "finger", 79, finger_protocol_handler, 1, 1, 0, 0, 1 }, { "fsp", 21, fsp_protocol_handler, 1, 1, 0, 0, 1 }, diff --git a/src/protocol/protocol.h b/src/protocol/protocol.h index aad5592a..d13a2fcc 100644 --- a/src/protocol/protocol.h +++ b/src/protocol/protocol.h @@ -17,6 +17,7 @@ enum protocol { PROTOCOL_BITTORRENT, PROTOCOL_BITTORRENT_PEER, PROTOCOL_DATA, + PROTOCOL_DGI, PROTOCOL_FILE, PROTOCOL_FINGER, PROTOCOL_FSP, diff --git a/src/session/download.c b/src/session/download.c index 315c1073..c65fdbff 100644 --- a/src/session/download.c +++ b/src/session/download.c @@ -156,6 +156,8 @@ abort_download(struct file_download *file_download) if (file_download->delete_) unlink(file_download->file); mem_free(file_download->file); } + mem_free_if(file_download->inpext); + mem_free_if(file_download->outext); del_from_list(file_download); mem_free(file_download); } @@ -355,7 +357,6 @@ do_follow_url_mailcap(struct session *ses, struct uri *uri) ses_goto(ses, uri, NULL, NULL, CACHE_MODE_NORMAL, TASK_FORWARD, 0); } - static void exec_mailcap_command(void *data) { @@ -406,6 +407,71 @@ exec_later(struct session *ses, char *handler, char *file) } } +static void +exec_dgi_command(void *data) +{ + struct exec_dgi *exec_dgi = (struct exec_dgi *)data; + + if (exec_dgi) { + if (exec_dgi->command) { + struct string string; + + if (init_string(&string)) { + static char dgi_dgi[] = "dgi://"; + struct uri *ref = get_uri(dgi_dgi, URI_NONE); + struct uri *uri; + struct session *ses = exec_dgi->ses; + + add_to_string(&string, "dgi:///dgi?command="); + add_to_string(&string, exec_dgi->command); + add_to_string(&string, "&filename="); + if (exec_dgi->file) { + add_to_string(&string, exec_dgi->file); + } + add_to_string(&string, "&inpext="); + if (exec_dgi->inpext) { + add_to_string(&string, exec_dgi->inpext); + } + add_to_string(&string, "&outext="); + if (exec_dgi->outext) { + add_to_string(&string, exec_dgi->outext); + } + if (exec_dgi->del) { + add_to_string(&string, "&delete=1"); + } + uri = get_uri(string.source, URI_BASE_FRAGMENT); + done_string(&string); + set_session_referrer(ses, ref); + if (ref) done_uri(ref); + + do_follow_url_mailcap(ses, uri); + if (uri) done_uri(uri); + } + mem_free(exec_dgi->command); + } + mem_free_if(exec_dgi->file); + mem_free_if(exec_dgi->inpext); + mem_free_if(exec_dgi->outext); + mem_free(exec_dgi); + } +} + +static void +exec_later_dgi(struct session *ses, char *handler, char *file, char *inpext, char *outext, int del) +{ + struct exec_dgi *exec_dgi = (struct exec_dgi *)mem_calloc(1, sizeof(*exec_dgi)); + + if (exec_dgi) { + exec_dgi->ses = ses; + exec_dgi->command = null_or_stracpy(handler); + exec_dgi->file = null_or_stracpy(file); + exec_dgi->inpext = null_or_stracpy(inpext); + exec_dgi->outext = null_or_stracpy(outext); + exec_dgi->del = del; + register_bottom_half(exec_dgi_command, exec_dgi); + } +} + static void download_data_store(struct download *download, struct file_download *file_download) { @@ -462,6 +528,12 @@ download_data_store(struct download *download, struct file_download *file_downlo file_download->external_handler, file_download->file); /* Temporary file is deleted by the mailcap_protocol_handler */ file_download->delete_ = 0; + } else if (file_download->dgi) { + exec_later_dgi(file_download->ses, + file_download->external_handler, file_download->file, + file_download->inpext, file_download->outext, 1); + /* Temporary file is deleted by the dgi_protocol_handler */ + file_download->delete_ = 0; } else { exec_on_terminal(term, file_download->external_handler, file_download->file, @@ -1260,7 +1332,17 @@ continue_download_do(struct terminal *term, int fd, void *data, codw_hop->real_file = NULL; fd = -1; - if (type_query->external_handler) { + if (type_query->dgi && type_query->external_handler) { + file_download->external_handler = type_query->external_handler; + file_download->file = codw_hop->file; + file_download->inpext = null_or_stracpy(type_query->inpext); + file_download->outext = null_or_stracpy(type_query->outext); + file_download->dgi = type_query->dgi; + file_download->delete_ = 1; + /* change owners a few lines above */ + codw_hop->file = NULL; + type_query->external_handler = NULL; + } else if (type_query->external_handler) { file_download->external_handler = subst_file(type_query->external_handler, codw_hop->file, type_query->uri->string); @@ -1390,6 +1472,8 @@ done_type_query(struct type_query *type_query) object_unlock(type_query->cached); done_uri(type_query->uri); + mem_free_if(type_query->inpext); + mem_free_if(type_query->outext); mem_free_if(type_query->external_handler); mem_free_if(type_query->target_frame); del_from_list(type_query); @@ -1510,6 +1594,17 @@ tp_open(struct type_query *type_query) char *file = get_uri_string(type_query->uri, URI_PATH); char *handler = NULL; + if (type_query->dgi) { + if (file) { + decode_uri(file); + } + exec_later_dgi(type_query->ses, type_query->external_handler, file, + type_query->inpext, type_query->outext, 0); + mem_free_if(file); + done_type_query(type_query); + return; + } + if (file) { decode_uri(file); handler = subst_file(type_query->external_handler, @@ -1564,6 +1659,9 @@ do_type_query(struct type_query *type_query, char *ct, struct mime_handler *hand if (handler) { type_query->block = handler->block; type_query->copiousoutput = handler->copiousoutput; + type_query->dgi = handler->dgi; + type_query->inpext = null_or_stracpy(handler->inpext); + type_query->outext = null_or_stracpy(handler->outext); if (!handler->ask) { type_query->external_handler = stracpy(handler->program); tp_open(type_query); diff --git a/src/session/download.h b/src/session/download.h index d3460019..451313c4 100644 --- a/src/session/download.h +++ b/src/session/download.h @@ -100,6 +100,12 @@ struct type_query { * this frame. This string must be freed with mem_free(). */ char *target_frame; + /** input filename extension */ + char *inpext; + + /** output filename extension */ + char *outext; + /** Command line for an external handler, to be run when the * download finishes. When ELinks displays the type query, * it copies this from mime_handler.program of the default @@ -121,6 +127,7 @@ struct type_query { * from a "file" URI that does not refer to a local CGI, then * Elinks need not copy the file. */ unsigned int cgi:1; + unsigned int dgi:1; /** mailcap entry with copiousoutput */ unsigned int copiousoutput:1; @@ -152,6 +159,12 @@ struct file_download { int notify; struct download download; + /** input filename extension */ + char *inpext; + + /** output filename extension */ + char *outext; + /** Should the file be deleted when destroying the structure */ unsigned int delete_:1; @@ -161,6 +174,7 @@ struct file_download { /** Whether to block the terminal when running the external handler. */ unsigned int block:1; + unsigned int dgi:1; /** Mailcap entry with copiousoutput */ unsigned int copiousoutput:1; @@ -169,6 +183,15 @@ struct file_download { struct listbox_item *box_item; }; +struct exec_dgi { + struct session *ses; + char *command; + char *file; + char *inpext; + char *outext; + unsigned int del:1; +}; + /** Stack of all running downloads */ extern LIST_OF(struct file_download) downloads;