2005-09-15 15:58:31 +02:00
|
|
|
/* Internal "mailto", "telnet", "tn3270" and misc. protocol implementation */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "bfu/dialog.h"
|
|
|
|
#include "config/options.h"
|
|
|
|
#include "intl/gettext/libintl.h"
|
|
|
|
#include "main/module.h"
|
|
|
|
#include "osdep/osdep.h"
|
|
|
|
#include "protocol/uri.h"
|
|
|
|
#include "protocol/user.h"
|
|
|
|
#include "session/download.h"
|
|
|
|
#include "session/session.h"
|
|
|
|
#include "terminal/terminal.h"
|
|
|
|
#include "terminal/window.h"
|
|
|
|
#include "util/conv.h"
|
|
|
|
#include "util/file.h"
|
|
|
|
#include "util/memory.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
|
|
|
|
static struct option_info user_protocol_options[] = {
|
|
|
|
INIT_OPT_TREE("protocol", N_("User protocols"),
|
|
|
|
"user", OPT_AUTOCREATE,
|
Rewrap lines in option documentation.
Documentation strings of most options used to contain a "\n" at the
end of each source line. When the option manager displayed these
strings, it treated each "\n" as a hard newline. On 80x24 terminals
however, the option description window has only 60 columes available
for the text (with the default setup.h), and the hard newlines were
further apart, so the option manager wrapped the text a second time,
resulting in rather ugly output where long lones are interleaved with
short ones. This could also cause the text to take up too much
vertical space and not fit in the window.
Replace most of those hard newlines with spaces so that the option
manager (or perhaps BFU) will take care of the wrapping. At the same
time, rewrap the strings in source code so that the source lines are
at most 79 columns wide.
In some options though, there is a list of possible values and their
meanings. In those lists, if the description of one value does not
fit in one line, then continuation lines should be indented. The
option manager and BFU are not currently able to do that. So, keep
the hard newlines in those lists, but rewrap them to 60 columns so
that they are less likely to require further wrapping at runtime.
2009-03-07 20:48:38 +02:00
|
|
|
N_("User protocols. Options in this tree specify external "
|
|
|
|
"handlers for the appropriate protocols. Ie. "
|
2005-09-15 15:58:31 +02:00
|
|
|
"protocol.user.mailto.unix.")),
|
|
|
|
|
|
|
|
/* FIXME: Poorly designed options structure. Ought to be able to specify
|
|
|
|
* need_slashes, free_form and similar options as well :-(. --pasky */
|
|
|
|
|
|
|
|
/* Basically, it looks like protocol.user.mailto.win32 = "blah" */
|
|
|
|
|
|
|
|
INIT_OPT_TREE("protocol.user", NULL,
|
|
|
|
"_template_", OPT_AUTOCREATE,
|
Rewrap lines in option documentation.
Documentation strings of most options used to contain a "\n" at the
end of each source line. When the option manager displayed these
strings, it treated each "\n" as a hard newline. On 80x24 terminals
however, the option description window has only 60 columes available
for the text (with the default setup.h), and the hard newlines were
further apart, so the option manager wrapped the text a second time,
resulting in rather ugly output where long lones are interleaved with
short ones. This could also cause the text to take up too much
vertical space and not fit in the window.
Replace most of those hard newlines with spaces so that the option
manager (or perhaps BFU) will take care of the wrapping. At the same
time, rewrap the strings in source code so that the source lines are
at most 79 columns wide.
In some options though, there is a list of possible values and their
meanings. In those lists, if the description of one value does not
fit in one line, then continuation lines should be indented. The
option manager and BFU are not currently able to do that. So, keep
the hard newlines in those lists, but rewrap them to 60 columns so
that they are less likely to require further wrapping at runtime.
2009-03-07 20:48:38 +02:00
|
|
|
N_("Handler (external program) for this protocol. Name the "
|
|
|
|
"options in this tree after your system (ie. unix, "
|
|
|
|
"unix-xwin).")),
|
2005-09-15 15:58:31 +02:00
|
|
|
|
|
|
|
INIT_OPT_STRING("protocol.user._template_", NULL,
|
|
|
|
"_template_", 0, "",
|
|
|
|
N_("Handler (external program) for this protocol and system.\n"
|
|
|
|
"%f in the string means file name to include form data from\n"
|
|
|
|
"%h in the string means hostname (or email address)\n"
|
|
|
|
"%p in the string means port\n"
|
|
|
|
"%d in the string means path (everything after the port)\n"
|
|
|
|
"%s in the string means subject (?subject=<this>)\n"
|
|
|
|
"%u in the string means the whole URL")),
|
|
|
|
|
|
|
|
#define INIT_OPT_USER_PROTOCOL(scheme, system, cmd) \
|
|
|
|
INIT_OPT_STRING("protocol.user." scheme, NULL, system, 0, cmd, NULL)
|
|
|
|
|
|
|
|
#ifndef CONFIG_GOPHER
|
|
|
|
INIT_OPT_USER_PROTOCOL("gopher", "unix", DEFAULT_AC_OPT_GOPHER),
|
|
|
|
INIT_OPT_USER_PROTOCOL("gopher", "unix-xwin", DEFAULT_AC_OPT_GOPHER),
|
|
|
|
#endif
|
|
|
|
INIT_OPT_USER_PROTOCOL("irc", "unix", DEFAULT_AC_OPT_IRC),
|
|
|
|
INIT_OPT_USER_PROTOCOL("irc", "unix-xwin", DEFAULT_AC_OPT_IRC),
|
|
|
|
INIT_OPT_USER_PROTOCOL("mailto", "unix", DEFAULT_AC_OPT_MAILTO),
|
|
|
|
INIT_OPT_USER_PROTOCOL("mailto", "unix-xwin", DEFAULT_AC_OPT_MAILTO),
|
|
|
|
#ifndef CONFIG_NNTP
|
|
|
|
INIT_OPT_USER_PROTOCOL("news", "unix", DEFAULT_AC_OPT_NEWS),
|
|
|
|
INIT_OPT_USER_PROTOCOL("news", "unix-xwin", DEFAULT_AC_OPT_NEWS),
|
|
|
|
#endif
|
|
|
|
INIT_OPT_USER_PROTOCOL("telnet", "unix", DEFAULT_AC_OPT_TELNET),
|
|
|
|
INIT_OPT_USER_PROTOCOL("telnet", "unix-xwin", DEFAULT_AC_OPT_TELNET),
|
|
|
|
INIT_OPT_USER_PROTOCOL("tn3270", "unix", DEFAULT_AC_OPT_TN3270),
|
|
|
|
INIT_OPT_USER_PROTOCOL("tn3270", "unix-xwin", DEFAULT_AC_OPT_TN3270),
|
|
|
|
|
|
|
|
NULL_OPTION_INFO,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct module user_protocol_module = struct_module(
|
|
|
|
/* name: */ N_("User protocols"),
|
|
|
|
/* options: */ user_protocol_options,
|
|
|
|
/* hooks: */ NULL,
|
|
|
|
/* submodules: */ NULL,
|
|
|
|
/* data: */ NULL,
|
|
|
|
/* init: */ NULL,
|
|
|
|
/* done: */ NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
unsigned char *
|
|
|
|
get_user_program(struct terminal *term, unsigned char *progid, int progidlen)
|
|
|
|
{
|
|
|
|
struct option *opt;
|
|
|
|
int xwin = term ? term->environment & ENV_XWIN : 0;
|
|
|
|
struct string name;
|
|
|
|
|
|
|
|
if (!init_string(&name)) return NULL;
|
|
|
|
|
|
|
|
add_to_string(&name, "protocol.user.");
|
|
|
|
|
|
|
|
/* Now add lowercased progid part. Delicious. */
|
|
|
|
add_bytes_to_string(&name, progid, progidlen);
|
2008-11-01 18:18:06 +01:00
|
|
|
convert_to_lowercase_locale_indep(&name.source[sizeof("protocol.user.") - 1], progidlen);
|
2005-09-15 15:58:31 +02:00
|
|
|
|
|
|
|
add_char_to_string(&name, '.');
|
|
|
|
add_to_string(&name, get_system_str(xwin));
|
|
|
|
|
|
|
|
opt = get_opt_rec_real(config_options, name.source);
|
|
|
|
|
|
|
|
done_string(&name);
|
|
|
|
return (unsigned char *) (opt ? opt->value.string : NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned char *
|
|
|
|
subst_cmd(unsigned char *cmd, struct uri *uri, unsigned char *subj,
|
|
|
|
unsigned char *formfile)
|
|
|
|
{
|
|
|
|
struct string string;
|
|
|
|
|
|
|
|
if (!init_string(&string)) return NULL;
|
|
|
|
|
|
|
|
while (*cmd) {
|
|
|
|
int p;
|
|
|
|
|
|
|
|
for (p = 0; cmd[p] && cmd[p] != '%'; p++);
|
|
|
|
|
|
|
|
add_bytes_to_string(&string, cmd, p);
|
|
|
|
cmd += p;
|
|
|
|
|
|
|
|
if (*cmd != '%') break;
|
|
|
|
|
|
|
|
cmd++;
|
|
|
|
/* TODO: Decode URI fragments before adding them. --jonas */
|
|
|
|
switch (*cmd) {
|
|
|
|
case 'u':
|
|
|
|
{
|
|
|
|
unsigned char *url = struri(uri);
|
|
|
|
int length = get_real_uri_length(uri);
|
|
|
|
|
|
|
|
add_shell_safe_to_string(&string, url, length);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'h':
|
|
|
|
/* TODO For some user protocols it would be
|
|
|
|
* better if substitution of each uri
|
|
|
|
* field was completely configurable. Now
|
|
|
|
* @host contains both the uri username
|
|
|
|
* field, (password field) and hostname
|
|
|
|
* field because it is useful for mailto
|
|
|
|
* protocol handling. */
|
|
|
|
/* It would break a lot of configurations so I
|
|
|
|
* don't know. --jonas */
|
|
|
|
if (uri->userlen && uri->hostlen) {
|
|
|
|
int hostlen = uri->host + uri->hostlen - uri->user;
|
|
|
|
|
|
|
|
add_shell_safe_to_string(&string, uri->user,
|
|
|
|
hostlen);
|
|
|
|
} else if (uri->host) {
|
|
|
|
add_shell_safe_to_string(&string, uri->host,
|
|
|
|
uri->hostlen);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'p':
|
|
|
|
if (uri->portlen)
|
|
|
|
add_shell_safe_to_string(&string, uri->port,
|
|
|
|
uri->portlen);
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
if (uri->datalen)
|
|
|
|
add_shell_safe_to_string(&string, uri->data,
|
|
|
|
uri->datalen);
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
if (subj)
|
|
|
|
add_shell_safe_to_string(&string, subj,
|
|
|
|
strlen(subj));
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
if (formfile)
|
|
|
|
add_to_string(&string, formfile);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
add_bytes_to_string(&string, cmd - 1, 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (*cmd) cmd++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return string.source;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stay silent about complete RFC 2368 support or do it yourself! ;-).
|
|
|
|
* --pasky */
|
|
|
|
static unsigned char *
|
|
|
|
get_subject_from_query(unsigned char *query)
|
|
|
|
{
|
|
|
|
unsigned char *subject;
|
|
|
|
|
|
|
|
if (strncmp(query, "subject=", 8)) {
|
|
|
|
subject = strstr(query, "&subject=");
|
|
|
|
if (!subject) return NULL;
|
|
|
|
subject += 9;
|
|
|
|
} else {
|
|
|
|
subject = query + 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return subject until next '&'-value or end of string */
|
|
|
|
return memacpy(subject, strcspn(subject, "&"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned char *
|
|
|
|
save_form_data_to_file(struct uri *uri)
|
|
|
|
{
|
|
|
|
unsigned char *filename = get_tempdir_filename("elinks-XXXXXX");
|
2007-03-05 21:17:09 +01:00
|
|
|
int fd;
|
|
|
|
FILE *fp;
|
2007-03-05 21:37:24 +01:00
|
|
|
size_t nmemb, len;
|
2007-03-05 21:34:45 +01:00
|
|
|
unsigned char *formdata;
|
2005-09-15 15:58:31 +02:00
|
|
|
|
|
|
|
if (!filename) return NULL;
|
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
fd = safe_mkstemp(filename);
|
|
|
|
if (fd < 0) {
|
2005-09-15 15:58:31 +02:00
|
|
|
mem_free(filename);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-03-05 21:34:45 +01:00
|
|
|
if (!uri->post) return filename;
|
|
|
|
|
2007-03-05 21:37:24 +01:00
|
|
|
/* Jump the content type */
|
|
|
|
formdata = strchr(uri->post, '\n');
|
|
|
|
formdata = formdata ? formdata + 1 : uri->post;
|
|
|
|
len = strlen(formdata);
|
|
|
|
if (len == 0) return filename;
|
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
fp = fdopen(fd, "w");
|
|
|
|
if (!fp) {
|
2007-03-05 21:30:16 +01:00
|
|
|
|
|
|
|
error:
|
2007-03-05 21:24:02 +01:00
|
|
|
unlink(filename);
|
2005-09-15 15:58:31 +02:00
|
|
|
mem_free(filename);
|
2007-03-05 21:17:09 +01:00
|
|
|
close(fd);
|
2005-09-15 15:58:31 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-03-05 21:37:24 +01:00
|
|
|
nmemb = fwrite(formdata, len, 1, fp);
|
2007-03-05 21:34:45 +01:00
|
|
|
if (nmemb != 1) {
|
|
|
|
fclose(fp);
|
|
|
|
goto error;
|
2005-09-15 15:58:31 +02:00
|
|
|
}
|
2007-09-14 15:12:32 +02:00
|
|
|
|
2007-03-05 21:30:16 +01:00
|
|
|
if (fclose(fp) != 0)
|
|
|
|
goto error;
|
2005-09-15 15:58:31 +02:00
|
|
|
|
|
|
|
return filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
user_protocol_handler(struct session *ses, struct uri *uri)
|
|
|
|
{
|
|
|
|
unsigned char *subj = NULL, *prog;
|
2007-03-05 21:17:09 +01:00
|
|
|
unsigned char *filename;
|
2005-09-15 15:58:31 +02:00
|
|
|
|
|
|
|
prog = get_user_program(ses->tab->term, struri(uri), uri->protocollen);
|
|
|
|
if (!prog || !*prog) {
|
|
|
|
unsigned char *protocol = memacpy(struri(uri), uri->protocollen);
|
|
|
|
|
|
|
|
/* Shouldn't ever happen, but be paranoid. */
|
|
|
|
/* Happens when you're in X11 and you've no handler for it. */
|
|
|
|
info_box(ses->tab->term, MSGBOX_FREE_TEXT,
|
|
|
|
N_("No program"), ALIGN_CENTER,
|
|
|
|
msg_text(ses->tab->term,
|
|
|
|
N_("No program specified for protocol %s."),
|
|
|
|
empty_string_or_(protocol)));
|
|
|
|
|
|
|
|
mem_free_if(protocol);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uri->data && uri->datalen) {
|
|
|
|
/* Some mailto specific stuff follows... */
|
|
|
|
unsigned char *query = get_uri_string(uri, URI_QUERY);
|
|
|
|
|
|
|
|
if (query) {
|
|
|
|
subj = get_subject_from_query(query);
|
|
|
|
mem_free(query);
|
|
|
|
if (subj) decode_uri(subj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
filename = save_form_data_to_file(uri);
|
2005-09-15 15:58:31 +02:00
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
prog = subst_cmd(prog, uri, subj, filename);
|
2005-09-15 15:58:31 +02:00
|
|
|
mem_free_if(subj);
|
|
|
|
if (prog) {
|
2007-03-05 21:17:09 +01:00
|
|
|
unsigned char *delete = empty_string_or_(filename);
|
2005-09-15 15:58:31 +02:00
|
|
|
|
2007-07-14 12:26:45 +03:00
|
|
|
exec_on_terminal(ses->tab->term, prog, delete, TERM_EXEC_FG);
|
2005-09-15 15:58:31 +02:00
|
|
|
mem_free(prog);
|
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
} else if (filename) {
|
|
|
|
unlink(filename);
|
2005-09-15 15:58:31 +02:00
|
|
|
}
|
|
|
|
|
2007-03-05 21:17:09 +01:00
|
|
|
mem_free_if(filename);
|
2005-09-15 15:58:31 +02:00
|
|
|
}
|