/* retawq/init.c - initialization
This file is part of retawq (), a network
client created by Arne Thomassen; retawq is basically released under certain
versions of the GNU General Public License and WITHOUT ANY WARRANTY.
Read the file COPYING for license details, README for program information.
Copyright (C) 2001-2006 Arne Thomassen
*/
#include "stuff.h"
#include "init.h"
#include "parser.h"
#include "resource.h"
#if CONFIG_JAVASCRIPT
#include "javascript.h"
#endif
#if OPTION_BIRTCFG
#include "birtcfg.inc"
#endif
#if CAN_HANDLE_SIGNALS
#include
#endif
#if HAVE_SYS_IOCTL_H
#include
#endif
#if HAVE_NETINET_IN_H
#include
#endif
#if HAVE_UNAME
#include
#endif
declare_local_i18n_buffer
#define KILO (1024)
#define MEGA (1024 * 1024)
static char strbuf[STRBUF_SIZE];
static const char strEn[] = "en", strReverse[] = "reverse", strNone[] = "none";
#define MIGHT_USE_TERMWINTITLE ( (TGC_IS_CURSES) && (CONFIG_TG != TG_XCURSES) && (CONFIG_BLOAT & BLOAT_TERMWINTITLE) )
#define MIGHT_HIGHLIGHT ((TGC_IS_CURSES || (CONFIG_TG == TG_CONSOLE)) && (CONFIG_BLOAT & BLOAT_COLORHL) && (MIGHT_USE_COLORS))
#if MIGHT_HIGHLIGHT
static unsigned char color_highlight = 0; /* for tColorPairNumber 0..7 */
#endif
static void __init store_initmsg(const char* msg)
{ char* spfbuf;
my_spf(NULL, 0, &spfbuf, "%s\n
%s", null2empty(initial_messages), msg);
__dealloc(initial_messages); initial_messages = my_spf_use(spfbuf);
#if CONFIG_CONSOLE
{ const size_t len = strlen(msg); /* IMPROVEME! */
const tBoolean has_newline = cond2boolean((len > 0) && (msg[len-1]=='\n'));
my_spf(NULL, 0, &spfbuf, "%s%s%s", null2empty(initial_console_msgs), msg,
(has_newline ? strEmpty : strNewline));
__dealloc(initial_console_msgs); initial_console_msgs = my_spf_use(spfbuf);
/* CHECKME: this wastes memory unnecessarily if not pmConsole! */
}
#endif
}
static void __init show_warning(const char* str)
{ const char* strR = _("retawq: ");
size_t count = strlen(strR);
char buf[1024], *spfbuf, *temp;
my_spf(buf, 1024, &spfbuf, _("%swarning: %s\n"), strR, str);
/* ugly colon-ialism... */
if ( (lfdmbs(2))
#if CONFIG_CONSOLE
&& (program_mode != pmConsole) /* msg would be duplicate on console */
#endif
)
{ my_write_str(fd_stderr, spfbuf); }
/* call store_initmsg(), but skip the "retawq: " prefix; the code looks
somewhat complicated because we don't trust the last _() call much;
otherwise we could just say e.g. "temp = spfbuf + strlen(strR);"... */
temp = spfbuf;
while (count-- > 0)
{ if (*temp == '\0') goto out; /* "should not happen" */
temp++;
}
*temp = my_toupper(*temp);
store_initmsg(temp);
out:
my_spf_cleanup(buf, spfbuf);
}
/* Command-line options */
static one_caller void __init precheck_commandline(int argc, const char** argv)
/* This is done early during execution of the program in order to avoid
unnecessary initializations (especially the expensive gtk_init() call in
graphics mode) if there's a reason for premature exit, e.g. the command-line
option "--version". */
{ while (--argc > 0)
{ const char* str = *++argv;
if (*str != '-') { /* nothing; fastest check first, avoid lib calls */ }
else if ( (!strcmp(str, strOptVersion)) || (!strcmp(str, "-v")) )
{ /* if (lfdmbs(1)) */
{ my_write_str(fd_stdout, strCopyright);
my_write_str(fd_stdout, strProgramLegalese);
}
do_quit();
}
#if CONFIG_BLOAT & BLOAT_HELP
else if ( (!strcmp(str, strOptHelp)) || (!strcmp(str, "-h")) )
{ /*CHECKME: i18n? */
static const char h[] __initdata = "Documentation is available in the directory docu/ in the source code package,\nin the directory \"" PATH_INSTALL_DOC "\" (if installed)\nand at .\n";
/* if (lfdmbs(1)) */
{ my_write_str(fd_stdout, strCopyright); my_write_str(fd_stdout, h);
#if CONFIG_BLOAT & BLOAT_HELPARG
{ static const char h2[] __initdata = "\n"
"Command-line options:\n"
#if MIGHT_USE_COLORS
"--colors=off - disable colors\n"
"--colors=reverse - use black-on-white colors\n"
#endif
#if CONFIG_CONSOLE
"--console - enter console runmode\n"
#endif
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
"--download=URL - download content from URL, write it to stdout; quit\n"
#endif
#if CONFIG_EXTRA & EXTRA_DUMP
"--dump=URL - receive document from URL; write it to stdout (layouted); quit\n"
#endif
"--help, -h - print this help text; quit\n"
#if DO_PAGER
"--pager - run as a simple pager\n"
#endif
#if CONFIG_SESSIONS
"--resume-session - resume from default session file\n"
"--resume-session=FILE - resume from given session file\n"
#endif
#if CONFIG_RTCONFIG
"--rtcfg=LETTERS - use "
#if OPTION_BIRTCFG
"built-in/external "
#endif
"run-time configuration?\n"
#endif
"--userdir=PATH - set retawq's user directory\n"
"--version, -v - print version information; quit\n"
"Additionally, you can specify the URLs of one or two documents to be shown.\n"
;
my_write_str(fd_stdout, h2);
}
#endif
}
do_quit();
}
#endif
}
}
static one_caller void __init process_commandline(int argc, const char** argv)
{ while (--argc > 0)
{ const char* str = *++argv;
if (*str != '-') /* (fastest check first) */
{ if ( (is_environed) && (*str != '\0') && (launch_uri_count < 2) )
launch_uri[launch_uri_count++] = str;
}
else if (!strncmp(str, "--userdir=", 10))
{ const char* temp = str + 10;
if (*temp != '\0')
{ char* path;
__dealloc(config.path);
my_spf(NULL, 0, &path, strPercsPercs, temp,
( (temp[strlen(temp) - 1] != chDirsep) ? strDirsep : strEmpty ));
config.path = my_spf_use(path);
config.flags |= cfUserDefinedConfigPath;
}
}
else if (!strncmp(str, "--resume-session", 16))
{
#if CONFIG_SESSIONS
const char* temp = str + 16;
char ch = *temp;
if (ch == '\0') my_strdedup(config.session_resume, strEmpty);
else if (ch == '=')
{ temp++;
if (*temp != '\0') my_strdedup(config.session_resume, temp);
else goto uclo;
}
else goto uclo;
#else
char* spfbuf;
my_spf(strbuf, STRBUF_SIZE, &spfbuf, _(strFwdact), _(strFsessions) + 1,
strEmpty);
show_warning(spfbuf);
my_spf_cleanup(strbuf, spfbuf);
#endif
}
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
else if (!strncmp(str, "--download=", 11))
{ const char* temp = str + 11;
if (*temp != '\0')
{ my_strdedup(config.pm_uri, temp); program_mode = pmDownload; }
}
#endif
#if CONFIG_EXTRA & EXTRA_DUMP
else if (!strncmp(str, "--dump=", 7))
{ const char* temp = str + 7;
if (*temp != '\0')
{ my_strdedup(config.pm_uri, temp); program_mode = pmDump; }
}
#endif
#if CONFIG_CONSOLE
else if (!strcmp(str, "--console")) program_mode = pmConsole;
#endif
#if DO_PAGER
else if (!strncmp(str, "--pager", 7))
{ const char* temp = str + 7;
char ch;
program_mode = pmPager;
if (*temp == '\0') continue;
else if (*temp != '=') goto uclo;
temp++;
while ( (ch = *temp++) != '\0' ) { /* IMPLEMENTME: h, r, s! */ }
}
#endif
#if 0 /* IMPLEMENTME? */
else if (!strncmp(str, "--batch=", 8))
{ const char* temp = str + 8;
if (*temp != '\0')
{ my_strdedup(config.batchfile, temp); program_mode = pmBatch; }
}
#endif
#if MIGHT_USE_COLORS
else if (!strncmp(str, "--colors=", 9))
{ const char* temp = str + 9;
if (!strcmp(temp, strOff)) config.flags |= cfColorsOff;
#if TGC_IS_CURSES
else if (!strcmp(temp, strReverse)) config.flags |= cfColorsReverse;
#endif
}
#endif
#if MIGHT_HIGHLIGHT
else if (!strncmp(str, "--colorhl=", 10))
{ const char* temp = str + 10;
char *hl, *pos;
tColorPairNumber cpn;
tBoolean is_last, negate;
char ch;
if (strlen(temp) > STRBUF_SIZE / 2) goto hl_out; /* can't be serious */
strcpy(strbuf, temp); hl = strbuf; is_last = falsE;
hl_loop:
ch = *hl;
if (ch == '\0') goto hl_out;
if (ch == '!') { hl++; negate = truE; }
else negate = falsE;
pos = hl;
while (1)
{ ch = *pos;
if (ch == '\0') { is_last = truE; break; }
else if (ch == ',') { *pos = '\0'; break; }
pos++;
}
if (!strcmp(hl, "red"))
{ cpn = cpnRed;
hl_flip:
if (negate) color_highlight &= ~(1 << cpn);
else color_highlight |= (1 << cpn);
}
else if (!strcmp(hl, "green")) { cpn = cpnGreen; goto hl_flip; }
else if (!strcmp(hl, "yellow")) { cpn = cpnYellow; goto hl_flip; }
else if (!strcmp(hl, "blue")) { cpn = cpnBlue; goto hl_flip; }
else if (!strcmp(hl, "all")) color_highlight = ~((unsigned char) 0);
else if (!strcmp(hl, strNone)) color_highlight = 0;
if (!is_last) { hl = pos + 1; goto hl_loop; }
hl_out: {}
}
#endif
#if CONFIG_RTCONFIG
else if (!strncmp(str, "--rtcfg=", 8))
{ const char* temp = str + 8;
char ch;
config.flags &= ~cfRtAll;
while ( (ch = *temp++) != '\0' )
{ if (ch == 'e') config.flags |= cfRtExternal;
#if OPTION_BIRTCFG
else if (ch == 'b') config.flags |= cfRtBuiltin;
#endif
}
}
#endif
else
{ char* spfbuf;
uclo:
my_spf(strbuf, STRBUF_SIZE, &spfbuf,
_("unknown command-line option: \"%s\"%s"), str,
#if (CONFIG_BLOAT & BLOAT_HELP) && (CONFIG_BLOAT & BLOAT_HELPARG)
_("; try \"retawq --help\"")
#else
strEmpty
#endif
);
fatal_error(0, spfbuf);
/* my_spf_cleanup(strbuf, spfbuf); */
}
}
}
/* Internationalization */
#if OPTION_I18N
static /*@relnull@*/ /*@reldef@*/ const char* current_locale;
static one_caller void __init initialize_i18n(void)
{ (void) setlocale(LC_ALL, strEmpty);
(void) bindtextdomain(strRetawq, PATH_INSTALL_LOCALE);
(void) textdomain(strRetawq);
current_locale = setlocale(LC_MESSAGES, NULL);
}
/* begin-autogenerated */
#define NUM_LANGUAGES 6
static const char* const language[NUM_LANGUAGES] = { "de", strEn, "es", "fr", "ja", "pt_BR" };
/* end-autogenerated */
static one_caller tMbsIndex __init lookup_language_code(void)
{ my_binary_search(0, NUM_LANGUAGES - 1, strncmp(current_locale,
language[idx], 2), return(idx))
}
#endif /* #if OPTION_I18N */
static my_inline char __init str2char_(const char* const str)
/* translates the string and returns the first character of the result */
{
#if OPTION_I18N
const char* const istr = _(str);
return(*istr); /* ISTR that "ISTR" means "I seem to remember", but... :-) */
#else
return(*str);
#endif
}
/* Text terminal preparation */
#if MIGHT_DO_TERMIOS
static void __init prepare_terminal(void)
{ struct termios t;
my_memclr_var(t); /* for all those buggy libraries... */
if (tcgetattr(0, &t) == 0)
{ int err;
unsigned char loopcount = 0;
saved_termios = t; /* save the old settings */
t.c_iflag &= ~(INLCR | ISTRIP); t.c_iflag |= ICRNL;
t.c_oflag &= ~(OCRNL | ONOCR | ONLRET); t.c_oflag |= OPOST | ONLCR;
t.c_cflag &= ~(PARENB | CSIZE); t.c_cflag |= CS8; /* enable eight-bit */
t.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); /* disable echo */
t.c_lflag &= ~ICANON; /* disable canonical mode */
t.c_lflag |= ISIG; t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0;
loop:
err = tcsetattr(0, TCSAFLUSH, &t);
if (err == 0) do_restore_termios = truE;
else if ( (err == -1) && (errno == EINTR) && (++loopcount < 100) )
goto loop;
}
}
#endif
/* Curses library for text terminal control */
#if TGC_IS_CURSES
#if (CONFIG_TG == TG_BICURSES) || (CONFIG_TG == TG_XCURSES)
static tBoolean __init env2num(const char* str, /*@out@*/ int* _value,
int minv, int maxv)
{ tBoolean retval = falsE;
const char* vstr = getenv(str);
if ( (vstr != NULL) && (my_isdigit(*vstr)) )
{ my_atoi(vstr, _value, &vstr, maxv);
if ( (*vstr == '\0') && (*_value >= minv) ) retval = truE;
}
return(retval);
}
tBoolean __init env_termsize(int* _x, int* _y)
{ return(cond2boolean(env2num("COLUMNS", _x, CURSES_MINCOLS, CURSES_MAXCOLS) &&
env2num("LINES", _y, CURSES_MINLINES, CURSES_MAXLINES)));
}
#endif /* bicurses/xcurses */
static one_caller void __init initialize_curses(void)
{ if (initscr() == NULL) fatal_error(0, _("can't initialize terminal"));
/* (Some old curses libraries, e.g. on Minix 2.0, might return NULL instead
of exiting with an error message.) */
need_tglib_cleanup = truE;
#if MIGHT_USE_COLORS
if (!(config.flags & cfColorsOff))
{ if ( (start_color() != ERR) && (has_colors() == TRUE) &&
(COLOR_PAIRS > cpnMax) )
{ use_colors = truE; }
}
#endif
(void) cbreak(); (void) noecho(); (void) nonl();
(void) intrflush(stdscr, FALSE); (void) keypad(stdscr, TRUE);
(void) nodelay(stdscr, TRUE);
#if (CONFIG_DO_TEXTMODEMOUSE) && (!OFWAX)
(void) mousemask(TEXTMODEMOUSE_MASK, NULL);
#endif
#if MIGHT_USE_COLORS
if (use_colors)
{ const tBoolean do_rev = cond2boolean(config.flags & cfColorsReverse);
tColorCode bg = (do_rev ? ccWhite : ccBlack);
tColorPairNumber i;
(void) init_pair(cpnDefault, (do_rev ? ccBlack : ccWhite), bg);
(void) init_pair(cpnRed, ccRed, bg);
(void) init_pair(cpnGreen, ccGreen, bg);
(void) init_pair(cpnYellow, ccYellow, bg);
(void) init_pair(cpnBlue, ccBlue, bg);
for (i = cpnDefault; i <= cpnMax; i++)
{ tColorBitmask cb = COLOR_PAIR(i);
#if MIGHT_HIGHLIGHT
if (color_highlight & (1 << i)) cb |= A_BOLD;
#endif
color_bitmask[i] = cb;
}
bkgdset(COLOR_PAIR(cpnDefault));
my_set_color(cpnDefault);
}
#endif
#if MIGHT_USE_TERMWINTITLE
if ( (config.flags & cfTermwintitle) && (lfdmbs(1)) )
{ const char* str;
/* The testing order is important - we can have screen run inside xterm. */
if ( ( (str = getenv("STY")) != NULL ) && (*str != '\0') )
{ /* GNU screen terminal */
sprint_safe(strbuf, "\033k%s\033\\", strProgramVersion);
my_write_str(fd_stdout, strbuf); debugmsg("STY\n");
}
else if ( ( (str = getenv("DISPLAY")) != NULL ) && (*str != '\0') )
{ /* xterm-like terminal */
sprint_safe(strbuf, "\033]0;%s\a", strProgramVersion);
my_write_str(fd_stdout, strbuf); debugmsg("DISPLAY\n");
}
}
#endif
}
#endif /* #if TGC_IS_CURSES */
/* Signals */
#if CAN_HANDLE_SIGNALS
#if (!defined(SIGCHLD)) && defined(SIGCLD)
#define SIGCHLD (SIGCLD) /* synonyms */
#endif
#define HANDLE_SIGCHLD (MIGHT_FORK_EXEC && defined(SIGCHLD))
#define signal_concerns_terminal(number) \
( ((number) == SIGINT) || ((number) == SIGQUIT) || ((number) == SIGHUP) )
#if HAVE_SIGACTION
static one_caller tBoolean __initfe signal_is_ignored(int signal_number)
{ struct sigaction signal_handler;
return( (sigaction(signal_number, NULL, &signal_handler) == 0) ?
cond2boolean(signal_handler.sa_handler == SIG_IGN) : falsE );
}
static void __initfe my_sigaction(int signal_number, void (*handler)(int))
{ struct sigaction signal_handler;
if ( (!is_promptable) && (signal_concerns_terminal(signal_number)) &&
(signal_is_ignored(signal_number)) )
{ /* We're running as an automated job, so we don't wanna handle
terminal-related signals that we should rather ignore. */
return;
}
my_memclr_var(signal_handler);
signal_handler.sa_handler = handler;
signal_handler.sa_flags = SA_RESTART;
#if HANDLE_SIGCHLD
if (signal_number == SIGCHLD) signal_handler.sa_flags |= SA_NOCLDSTOP;
#endif
(void) sigemptyset(&signal_handler.sa_mask);
(void) sigaction(signal_number, &signal_handler, NULL);
}
#define signal_reestablish(sig, handler) do { /* nothing to do */ } while (0)
#else /* #if HAVE_SIGACTION */
/* We don't have sigaction(), so we use signal(). SUSv3 says, "Use of this
function is unspecified in a multi-threaded process.", but we use it mostly
at initialization time (or right after fork()), before extra threads are
created. The only problem is signal_reestablish(), but we don't have a
choice, so... */
static void __initfe my_sigaction(int signal_number, void (*handler)(int))
{ void (*old)(int);
old = signal(signal_number, handler);
if ( (!is_promptable) && (signal_concerns_terminal(signal_number)) &&
(old == SIG_IGN) && (handler != SIG_IGN) )
{ (void) signal(signal_number, old); /* set it back */
}
}
#define signal_reestablish(sig, handler) (void) signal(sig, handler)
#endif /* #if HAVE_SIGACTION */
#define notify_main(addr) (void) my_write_pipe(fd_any2main_write, addr, 4)
static void my_termination_signal_handler(__sunused int signal_number
__cunused)
/* long name, short action :-) */
{ notify_main(strQuit);
}
#if CONFIG_CONSOLE
static void my_console_discard_handler(__sunused int signal_number __cunused)
{ notify_main(strConsoleDiscard);
signal_reestablish(SIGINT, my_console_discard_handler);
}
#endif
#if defined(TIOCGWINSZ)
#define MY_TS_IOCTL (TIOCGWINSZ)
#define MY_TS_STRUCT struct winsize
#define MY_TS_X ws_col
#define MY_TS_Y ws_row
#elif defined(TIOCGSIZE)
#define MY_TS_IOCTL (TIOCGSIZE)
#define MY_TS_STRUCT struct ttysize
#define MY_TS_X ts_cols
#define MY_TS_Y ts_lines
#endif
#define MIGHT_CALC_TERMSIZE ( (MIGHT_WANT_TERMSIZE) && (defined(MY_TS_IOCTL)) )
#define MIGHT_SIG_TERMSIZE ( (MIGHT_CALC_TERMSIZE) && (defined(SIGWINCH)) && (HAVE_CURSES_RESIZETERM) )
#if MIGHT_WANT_TERMSIZE
tBoolean calc_termsize(int* _x, int* _y)
{ tBoolean retval = falsE;
#if MIGHT_CALC_TERMSIZE
MY_TS_STRUCT window_size;
unsigned char loopcount = 0;
int err;
loop:
my_memclr_var(window_size); /* (extra care) */
err = ioctl(1, MY_TS_IOCTL, &window_size);
if (err == 0)
{ if ( ( (*_x = window_size.MY_TS_X) > 0 ) &&
( (*_y = window_size.MY_TS_Y) > 0 ) )
retval = truE;
}
else if ( (err == -1) && (errno == EINTR) && (++loopcount < 100) ) goto loop;
#endif
return(retval);
}
#endif
#if MIGHT_SIG_TERMSIZE
static void my_sigwinch_handler(__sunused int signal_number __cunused)
{ int cols, rows;
if (calc_termsize(&cols, &rows))
{ if ( (cols >= CURSES_MINCOLS) && (cols <= CURSES_MAXCOLS) &&
(rows >= CURSES_MINLINES) && (rows <= CURSES_MAXLINES) )
{ char buf[4];
unsigned char* const temp = (unsigned char*) buf;
buf[0] = 'w'; /* buf[3] = ; -- irrelevant */
temp[1] = (unsigned char) cols; temp[2] = (unsigned char) rows;
notify_main(buf);
}
}
signal_reestablish(SIGWINCH, my_sigwinch_handler);
}
#endif /* #if MIGHT_SIG_TERMSIZE */
#if HANDLE_SIGCHLD
#if HAVE_SYS_WAIT_H
#include
#endif
static void my_sigchld_handler(__sunused int signal_number __cunused)
{ int dummy;
#if HAVE_WAITPID
while (waitpid(-1, &dummy, WNOHANG) > 0) { /* nothing */ }
#elif HAVE_WAIT3
while (wait3(&dummy, WNOHANG, NULL) > 0) { /* nothing */ }
#else
(void) wait(&dummy);
#endif
signal_reestablish(SIGCHLD, my_sigchld_handler);
}
#endif /* #if HANDLE_SIGCHLD */
static one_caller void __init initialize_signals(void)
{ my_sigaction(SIGHUP, my_termination_signal_handler);
my_sigaction(SIGTERM, my_termination_signal_handler);
my_sigaction(SIGQUIT, my_termination_signal_handler);
my_sigaction(SIGINT, (
#if CONFIG_CONSOLE
(program_mode == pmConsole) ? my_console_discard_handler :
#endif
(1 ? (SIG_IGN) : my_termination_signal_handler) ));
#if MIGHT_SIG_TERMSIZE
if (is_environed) my_sigaction(SIGWINCH, my_sigwinch_handler);
#endif
#if HANDLE_SIGCHLD
my_sigaction(SIGCHLD, my_sigchld_handler);
#endif
my_sigaction(SIGPIPE, SIG_IGN);
#ifdef SIGIO
my_sigaction(SIGIO, SIG_IGN);
#endif
/* Nowadays the standard defines "ignore this signal" as default handling for
SIGURG, but formerly (e.g. SUSv3 before issue 5) it was "abnormal
termination". *SIGh* :-) We wanna be sure: */
my_sigaction(SIGURG, SIG_IGN);
}
#if MIGHT_FORK_EXEC
void reset_signals(void)
/* Always call this right after fork() in the child process. The signals
handled here should be the same as in initialize_signals() - except SIGURG,
SIGPIPE and SIGIO, which should stay ignored. */
{ my_sigaction(SIGHUP, SIG_DFL);
my_sigaction(SIGINT, SIG_DFL);
my_sigaction(SIGTERM, SIG_DFL);
my_sigaction(SIGQUIT, SIG_DFL);
#if MIGHT_SIG_TERMSIZE
if (is_environed) my_sigaction(SIGWINCH, SIG_DFL);
#endif
#if HANDLE_SIGCHLD
my_sigaction(SIGCHLD, SIG_DFL);
#endif
}
#endif
#endif /* #if CAN_HANDLE_SIGNALS */
/* Run-time configuration */
#if CONFIG_RTCONFIG
static const char strCache[] __initdata = "cache";
#if OPTION_COOKIES || OPTION_LOCAL_CGI
static const char strAllow[] __initdata = "allow",
strDeny[] __initdata = "deny",
strAllowDenyExp[] __initdata = N_("\"allow\" or \"deny\" expected");
#endif
static unsigned char allow_uname_in_user_agent __initdata = 0;
my_enum1 enum
{ cscUnknown = 0, cscBookmarks = 1, cscCache = 2, cscColors = 3,
cscDontConfirm = 4, cscExecextShell = 5, cscFtpLogin = 6, cscFtpsMethod = 7,
cscHome = 8, cscHttpCookies = 9, cscHttpProxies = 10, cscHttpVersion = 11,
cscHttpsCookies = 12, cscHttpsProxies = 13, cscJumps = 14, cscKeymap = 15,
cscLanguages = 16, cscLocalCgi = 17, cscLocalDirFormat = 18,
cscLocalDirSort = 19, cscMayWrite = 20, cscNewsServerDefault = 21,
cscRedirections = 22, cscScrollBars = 23, cscSearchEngine = 24,
cscTermwintitle = 25, cscUserAgent = 26
} my_enum2(unsigned char) tConfigSectionCode;
#define MAX_CSC (26)
static tConfigSectionCode csc;
static const char* const strCsc[MAX_CSC + 1] __initdata =
{ "U"/*nknown*/, "bookmarks", strCache, "colors", "dont-confirm",
"execext-shell", "ftp-login", "ftps-method", "home", "http-cookies",
"http-proxies", "http-version", "https-cookies", "https-proxies", "jumps",
"keymap", "languages", "local-cgi", "local-dir-format", "local-dir-sort",
"may-write", "news-server-default", "redirections", "scroll-bars",
"search-engine", "termwintitle", "user-agent"
};
static tBoolean inside_comment, is_line_indented;
#if OPTION_COOKIES || OPTION_LOCAL_CGI
static tBoolean allow_this;
#endif
#if CONFIG_KEYMAPS
my_enum1 enum
{ kkUnknown = 0, kkCommand = 1, kkLineInput = 2
} my_enum2(unsigned char) tKeymapKind;
static tKeymapKind current_keymap_kind;
#endif
static size_t linecount, wordcount;
static const char /*@null@*/ *wordstart, *ptr;
#if CONFIG_FTP
static /*@null@*/ char *ftp_login_user = NULL, *ftp_login_password = NULL;
static /*@null@*/ tConfigLogin* last_ftp_login = NULL;
#endif
#if CONFIG_KEYMAPS
static /*@null@*/ char* current_keymap_keystr = NULL;
#endif
#define IS_NEWLINE(ch) ( ((ch) == '\n') || ((ch) == '\r') )
#define IS_WHITESPACE(ch) ( ((ch) == ' ') || ((ch) == '\t') )
#define wordlen (ptr - wordstart - 1)
static tBoolean __init is_word(const char* str)
{ const size_t len = wordlen;
return(cond2boolean( (len > 0) && (len == strlen(str)) &&
(!my_memdiff(wordstart, str, len)) ));
}
static void __init copy_word(char* dest)
{ size_t len = wordlen, max = STRBUF_SIZE / 2;
if (len > max) len = max; /* can't be serious */
my_memcpy(dest, wordstart, len);
dest[len] = '\0';
}
#define strdup_word(dest) dest = my_strndup(wordstart, wordlen)
#define strdup_word_tolower(dest) dest = my_strndup_tolower(wordstart, wordlen)
static void __init warn_section(tConfigSectionCode c, const char* str)
{ char buf[1024], *spfbuf;
my_spf(buf, 1024, &spfbuf, _("configuration section %s (line %d): %s"),
strCsc[c], linecount, str);
show_warning(spfbuf);
my_spf_cleanup(buf, spfbuf);
}
static one_caller void __init warn_section_fwdact(tConfigSectionCode c,
const char* str)
{ char* spfbuf;
if (*str == 'F') str++;
my_spf(strbuf, STRBUF_SIZE, &spfbuf, _(strFwdact), str,
_("; skipping section"));
warn_section(c, strbuf);
my_spf_cleanup(strbuf, spfbuf);
}
static my_inline void __init warn_section_wordcount(void)
{ warn_section(csc, _("too many arguments in line"));
inside_comment = truE; /* ignore the rest of the line */
}
static one_caller tMbsIndex __init do_lookup_csc(void)
{ my_binary_search(0, MAX_CSC, strcmp(strbuf, strCsc[idx]), return(idx))
}
static one_caller void __init lookup_csc(void)
{ copy_word(strbuf); csc = cscUnknown;
if (wordlen > 0)
{ tMbsIndex idx = do_lookup_csc();
if (idx > 0) csc = (tConfigSectionCode) idx;
}
if (csc == cscUnknown)
{ char buf[1024], *spfbuf;
my_spf(buf, sizeof(buf), &spfbuf,
_("unknown section name \"%s\"; skipping section"), strbuf);
show_warning(spfbuf);
my_spf_cleanup(buf, spfbuf);
}
}
static void __init interpret_portnumber(char* str, tPortnumber* dest,
tPortnumber _default, tBoolean none2zero)
{ tPortnumber portnumber;
char* temp;
if ( (str != NULL) && ( (temp = my_strrchr(str, ':')) != NULL ) &&
(my_strchr(temp + 1, ']') == NULL) ) /* port, not IPv6 address part */
{ int _portnumber;
*temp = '\0';
my_atoi(temp + 1, &_portnumber, NULL, 99999);
if ( (_portnumber >= 0) && (_portnumber <= 65535) )
portnumber = (tPortnumber) htons((tPortnumber) _portnumber);
else portnumber = _default;
}
else portnumber = (none2zero ? 0 /*==htons(0)*/ : _default);
*dest = portnumber;
}
#if OPTION_TLS
#define first_proxy \
( (csc == cscHttpsProxies) ? config.https_proxies : config.http_proxies )
#else
#define first_proxy (config.http_proxies)
#endif
static one_caller void __init handle_proxylinepart(void)
{ if (wordcount == 0)
{ tConfigProxy* p = memory_allocate(sizeof(tConfigProxy), mapPermanent);
p->next = first_proxy;
#if OPTION_TLS
if (csc == cscHttpsProxies) config.https_proxies = p;
else
#endif
{ config.http_proxies = p; }
if (!is_word(strNone)) strdup_word_tolower(p->proxy_hostname);
interpret_portnumber(p->proxy_hostname, &(p->proxy_portnumber),
(tPortnumber) htons(8080), truE);
}
else
{ tConfigProxy* p = first_proxy;
if (p == NULL) return; /* "should not happen" */
switch (wordcount)
{ case 1:
strdup_word_tolower(p->hosts_pattern);
interpret_portnumber(p->hosts_pattern, &(p->hosts_portnumber), 0,truE);
break;
#if CONFIG_HTTP & HTTP_PROXYAUTH
case 2: strdup_word(p->auth_user); break;
case 3: strdup_word(p->auth_pass); break;
#endif
default: warn_section_wordcount(); break;
}
}
}
static void __init handle_word(void)
{ if ( (wordstart == NULL) || (wordlen <= 0) ) return; /* nothing to do */
if (!is_line_indented) /* section-header line */
{ char* temp;
if (wordcount == 0) /* start of a new section */
{ const char* dis = NULL;
lookup_csc();
switch (csc)
{
#if CONFIG_LOCALDIR <= 1
case cscLocalDirSort: case cscLocalDirFormat:
dis = _("Flocal directory options"); break;
#endif
#if !OPTION_COOKIES
case cscHttpCookies: case cscHttpsCookies: dis = _("Fcookies"); break;
#endif
#if !OPTION_LOCAL_CGI
case cscLocalCgi: dis = _("Flocal CGI"); break;
#endif
#if (!(OPTION_EXECEXT & EXECEXT_SHELL))
case cscExecextShell: dis = _(strFexecext); break;
#endif
#if !OPTION_NEWS
case cscNewsServerDefault: dis = _("Fnews"); break;
#endif
#if !CONFIG_FTP
#if !OPTION_TLS
case cscFtpsMethod:
#endif
case cscFtpLogin: dis = "FFTP"; break;
#endif
#if !MIGHT_USE_COLORS
case cscColors: dis = _("Fcolors"); break;
#endif
#if !CONFIG_JUMPS
case cscJumps: dis = _(strFjumps); break;
#endif
#if !CONFIG_KEYMAPS
case cscKeymap: dis = _("Fkeymaps"); break;
#endif
#if !OPTION_TLS
#if OPTION_COOKIES /* (don't let the same case value occur more than once) */
case cscHttpsCookies:
#endif
#if CONFIG_FTP /* (don't let the same case value occur more than once) */
case cscFtpsMethod:
#endif
case cscHttpsProxies: dis = "FTLS"; break;
#endif
default: break; /* (some compilers reject empty switch blocks) */
}
if (dis != NULL) { warn_section_fwdact(csc, dis); csc = cscUnknown; }
if (csc == cscUnknown) inside_comment = truE;
#if CONFIG_KEYMAPS
else if (csc == cscKeymap) current_keymap_kind = kkUnknown;
#endif
goto out;
}
/* "else": parameter within the section-header line; check which one: */
switch (csc)
{case cscLanguages:
__dealloc(config.languages); strdup_word(config.languages);
break;
#if MIGHT_USE_TERMWINTITLE
case cscTermwintitle:
if (wordcount > 1) warn_section_wordcount();
else
{ int value;
copy_word(strbuf);
my_atoi(strbuf, &value, NULL, 9);
if (value > 0) config.flags |= cfTermwintitle;
}
break;
#endif
case cscUserAgent:
{ char ch = *wordstart;
if ( (ch < '0') || (ch > '3') || (wordlen > 1) )
warn_section(cscUserAgent, _("number expected (0..3)"));
else allow_uname_in_user_agent = (unsigned char) (ch - '0');
}
break;
case cscCache:
{ int value;
char ch;
size_t* target;
copy_word(strbuf); /* IMPROVEME! */
ch = my_tolower(*strbuf);
if ( (ch == 'r') && (strbuf[1] == ':') ) /* RAM cache size */
{ const char* temp2;
target = &(config.ramcachesize);
#if CONFIG_DISK_CACHE
set_cachesize:
#endif
my_atoi(strbuf + 2, &value, &temp2, MEGA);
ch = my_tolower(*temp2);
if (ch== 'k') value *= KILO;
else if (ch == 'm')
{ if (value > 100) value = 100;
value *= MEGA;
}
if (value > 100 * MEGA) value = 100 * MEGA;
*target = ((size_t) value);
}
#if CONFIG_DISK_CACHE
else if ( (ch == 'd') && (strbuf[1] == ':') ) /* disk cache size */
{ target = &(config.diskcachesize); goto set_cachesize; }
#endif
}
break;
case cscHome:
memory_deallocate(config.home_uri);
if (is_word(strOff)) config.home_uri = NULL;
else strdup_word(config.home_uri);
break;
#if MIGHT_USE_SCROLL_BARS
case cscScrollBars:
if (is_word(strOff)) config.flags &= ~cfUseScrollBars;
else if (is_word(strOn)) config.flags |= cfUseScrollBars;
else warn_section(cscScrollBars, _(strBadValue));
break;
#endif
case cscRedirections:
{ int value;
copy_word(strbuf); /* IMPROVEME! */
my_atoi(strbuf, &value, NULL, 99);
if (value < 3) value = 3;
else if (value > 20) value = 20;
config.redirections = (unsigned char) value;
}
break;
case cscSearchEngine:
__dealloc(config.search_engine); strdup_word(config.search_engine);
break;
case cscBookmarks:
__dealloc(config.bookmarks); strdup_word(config.bookmarks);
break;
#if MIGHT_USE_COLORS
case cscColors:
if (is_word(strOff)) config.flags |= cfColorsOff;
#if TGC_IS_CURSES
else if (is_word(strReverse)) config.flags |= cfColorsReverse;
#endif
break;
#endif
case cscDontConfirm: /* IMPROVEME: use a binary search? */
if (is_word(strQuit)) config.flags |= cfDontConfirmQuit;
else if (is_word(strClose)) config.flags |= cfDontConfirmClose;
else if (is_word("overwrite")) config.flags |= cfDontConfirmOverwrite;
else if (is_word("form-submit")) config.flags |= cfDontConfirmSubmit;
else if (is_word("form-reset")) config.flags |= cfDontConfirmReset;
else if (is_word("form-repost")) config.flags |= cfDontConfirmRepost;
else if (is_word("enforce-html")) config.flags |= cfDontConfirmHtml;
else if (is_word("enable")) config.flags |= cfDontConfirmEnable;
break;
case cscMayWrite:
#if CONFIG_DISK_CACHE
if (is_word(strCache)) config.flags |= cfMayWriteDiskCache;
#endif
break;
#if OPTION_NEWS
case cscNewsServerDefault:
if (wordcount > 1) warn_section_wordcount();
else
{ __dealloc(config.news_server);
strdup_word_tolower(config.news_server);
}
break;
#endif
#if TGC_IS_CURSES
#if CONFIG_KEYMAPS
case cscKeymap:
if (wordcount > 1) warn_section_wordcount();
else if (is_word("command")) current_keymap_kind = kkCommand;
else if (is_word("line-input")) current_keymap_kind = kkLineInput;
break;
#endif
#endif
#if OPTION_EXECEXT & EXECEXT_SHELL
case cscExecextShell:
if (wordcount == 1)
{ if (config.execext_shell != NULL)
{ warn_section(cscExecextShell, _("ignoring repeated option"));
ignore_shell: csc = cscUnknown; goto out;
}
if (*wordstart != chDirsep)
{ bad_shell: warn_section(cscExecextShell, _(strBadValue));
goto ignore_shell;
}
else
{ struct stat statbuf;
mode_t mode;
strdup_word(temp);
/* perform some tiny plausibility test to catch worst problems... */
if (my_stat(temp, &statbuf) != 0)
{ bad_shell_dealloc: memory_deallocate(temp); goto bad_shell; }
mode = statbuf.st_mode;
if ( (!S_ISREG(mode)) || (statbuf.st_size <= 0) ||
(!(mode & (S_IXUSR | S_IXGRP | S_IXOTH))) )
goto bad_shell_dealloc;
config.flags |= cfExecextShellCustom;
goto store_shellword;
}
}
else if (wordcount < 15)
{ tExecextParam* param;
strdup_word(temp);
store_shellword:
param = memory_allocate(sizeof(tExecextParam), mapPermanent);
param->param = temp; param->next = config.execext_shell;
config.execext_shell = param;
}
else warn_section_wordcount();
break;
#endif
}
}
else /* indented, thus in subsection ("rule") line */
{ switch (csc)
{case cscUnknown: inside_comment = truE; break;
case cscHttpVersion:
if (wordcount == 0)
{ tConfigProtocolVersion* v = (tConfigProtocolVersion*)
memory_allocate(sizeof(tConfigProtocolVersion), mapPermanent);
strdup_word_tolower(v->hosts_pattern);
interpret_portnumber(v->hosts_pattern, &(v->hosts_portnumber), 0,truE);
v->next = config.http_version; config.http_version = v;
}
else if (wordcount == 1)
{ if ( (is_word(str1dot0))
#if CONFIG_HTTP & HTTP_11
|| (is_word(str1dot1))
#endif
)
{ strdup_word(config.http_version->protstr); }
else warn_section(cscHttpVersion, _(strBadValue));
}
else warn_section_wordcount();
break;
case cscHttpProxies:
#if OPTION_TLS
case cscHttpsProxies:
#endif
handle_proxylinepart(); break;
#if OPTION_COOKIES
case cscHttpCookies:
#if OPTION_TLS
case cscHttpsCookies:
#endif
switch (wordcount)
{ case 0:
if (is_word(strAllow)) allow_this = truE;
else
{ if (!is_word(strDeny)) warn_section(csc, _(strAllowDenyExp));
allow_this = falsE;
}
break;
case 1:
{ tConfigCookie* cc = (tConfigCookie*)
memory_allocate(sizeof(tConfigCookie), mapPermanent);
strdup_word_tolower(cc->hosts_pattern);
if (allow_this) cc->flags |= ccfAllowed;
#if OPTION_TLS
if (csc == cscHttpsCookies)
{ cc->next = config.https_cookies; config.https_cookies = cc; }
else
#endif
{ cc->next = config.http_cookies; config.http_cookies = cc; }
}
break;
default: warn_section_wordcount(); break;
}
break;
#endif /* #if OPTION_COOKIES */
#if OPTION_LOCAL_CGI
case cscLocalCgi:
switch (wordcount)
{ case 0:
if (is_word(strAllow)) allow_this = truE;
else
{ if (!is_word(strDeny))
warn_section(cscLocalCgi, _(strAllowDenyExp));
allow_this = falsE;
}
break;
case 1:
if ( (allow_this) && (is_word(strAsterisk) || is_word("/*")) )
{ warn_section(cscLocalCgi, _("won't allow everything (\"*\") for security reasons; skipping remainder of section"));
csc = cscUnknown;
}
else if ( (*wordstart != chDirsep) && (!is_word(strAsterisk)) )
{ warn_section(cscLocalCgi,
_("path must be absolute or \"*\"; skipping remainder of section"));
csc = cscUnknown;
}
else
{ tConfigLocalCgi* clc = (tConfigLocalCgi*)
memory_allocate(sizeof(tConfigLocalCgi), mapPermanent);
strdup_word(clc->path_pattern);
if (allow_this) clc->flags |= clcfAllowed;
clc->next = config.local_cgi; config.local_cgi = clc;
}
break;
default: warn_section_wordcount(); break;
}
break;
#endif /* #if OPTION_LOCAL_CGI */
#if CONFIG_FTP
case cscFtpLogin:
if (wordcount == 0)
{ dealloc(ftp_login_user); strdup_word(ftp_login_user); }
else if (wordcount == 1)
{ dealloc(ftp_login_password); strdup_word(ftp_login_password); }
else if (wordcount == 2)
{ tConfigLogin* l = memory_allocate(sizeof(tConfigLogin), mapPermanent);
l->user = ftp_login_user; l->password = ftp_login_password;
ftp_login_user = ftp_login_password = NULL; /* detach */
strdup_word_tolower(l->hosts_pattern);
interpret_portnumber(l->hosts_pattern, &(l->hosts_portnumber),
rp2portnumber(rpFtp), truE);
if (last_ftp_login == NULL) config.ftp_login = l;
else last_ftp_login->next = l;
last_ftp_login = l;
}
break;
#endif
#if CONFIG_JUMPS
case cscJumps:
if (wordcount == 0)
{ tConfigJump* j = (tConfigJump*)
memory_allocate(sizeof(tConfigJump), mapPermanent);
j->next = config.jumps; config.jumps = j;
strdup_word(j->name);
}
else if (wordcount == 1) strdup_word(config.jumps->uri);
else if (wordcount < 50) /* (should be enough for all types of usage:-)*/
{ tConfigJump* j = config.jumps;
unsigned short count = j->argcount, maxcount = j->maxargcount;
if (count >= maxcount) /* need to allocate more space */
{ if (maxcount < 6) maxcount += 2;
else maxcount += 5;
j->arg = (char**) memory_reallocate(j->arg, maxcount *
sizeof(char*), mapPermanent);
j->maxargcount = maxcount;
}
strdup_word(j->arg[count]);
j->argcount++;
}
else warn_section_wordcount();
break;
#endif
#if CONFIG_LOCALDIR > 1
case cscLocalDirSort:
if (wordcount == 0)
{ tConfigLocaldirsort* s = (tConfigLocaldirsort*)
memory_allocate(sizeof(tConfigLocaldirsort), mapPermanent);
s->next = config.lds; config.lds = s;
strdup_word(s->path_pattern);
}
else if (wordcount == 1)
{ strdup_word(config.lds->sorting);
check_localdirsort(config.lds->sorting);
}
else warn_section_wordcount();
break;
case cscLocalDirFormat:
if (wordcount == 0)
{ tConfigLocaldirformat* s = (tConfigLocaldirformat*)
memory_allocate(sizeof(tConfigLocaldirformat), mapPermanent);
s->next = config.ldf; config.ldf = s;
strdup_word(s->path_pattern);
}
else if (wordcount == 1) strdup_word(config.ldf->format);
else warn_section_wordcount();
break;
#endif
#if CONFIG_KEYMAPS
case cscKeymap:
if (current_keymap_kind == kkUnknown)
{ warn_section(cscKeymap, _("bad keymap kind; skipping section"));
csc = cscUnknown; inside_comment = truE;
}
else if (wordcount == 0)
{ __dealloc(current_keymap_keystr); strdup_word(current_keymap_keystr); }
else if (wordcount == 1)
{ if (current_keymap_keystr != NULL)
{ const char* errstr;
unsigned char regres; /* registration result */
copy_word(strbuf);
regres = ( (current_keymap_kind == kkLineInput) ?
keymap_lineinput_key_register(current_keymap_keystr, strbuf) :
keymap_command_key_register(current_keymap_keystr, strbuf) );
switch (regres)
{ case 1: errstr = _("bad key identifier"); break;
case 2: errstr = _("repeatedly defined key"); break;
case 3: errstr = _("bad command identifier"); break;
default: errstr = NULL;
}
if (errstr != NULL) warn_section(cscKeymap, errstr);
}
}
else warn_section_wordcount();
break;
#endif
#if OPTION_TLS && CONFIG_FTP
case cscFtpsMethod:
if (wordcount == 0)
{ tConfigFtpTlsMethod* cftm = (tConfigFtpTlsMethod*)
memory_allocate(sizeof(tConfigFtpTlsMethod), mapPermanent);
cftm->next = config.ftp_tls_method; config.ftp_tls_method = cftm;
strdup_word_tolower(cftm->hosts_pattern);
interpret_portnumber(cftm->hosts_pattern, &(cftm->hosts_portnumber), 0,
truE);
}
else if (wordcount == 1)
{ tFtpTlsMethod ftm;
if (is_word("authssl")) ftm = ftmAuthSsl;
else if (is_word("authtls")) ftm = ftmAuthTls;
else if (is_word("authtls-dataclear")) ftm = ftmAuthTlsDataclear;
else if (is_word("tls")) ftm = ftmTls;
else
{ ftm = ftmAutodetect;
if (!is_word("autodetect"))
warn_section(cscFtpsMethod, _(strBadValue));
}
config.ftp_tls_method->ftm = ftm;
}
else warn_section_wordcount();
break;
#endif
default:
warn_section(csc,
_("no subsections possible; skipping remainder of section"));
csc = cscUnknown;
inside_comment = truE;
break;
}
}
out:
wordstart = NULL;
wordcount++;
}
static void __init cleanup_on_newline(void)
{
#if CONFIG_FTP
dealloc(ftp_login_user); dealloc(ftp_login_password);
#endif
#if CONFIG_KEYMAPS
dealloc(current_keymap_keystr);
#endif
}
static void __init rtconfig_parse(const char* buf, size_t count)
{ /* prepare... */
wordstart = NULL; wordcount = 0; csc = cscUnknown;
inside_comment = is_line_indented = falsE; linecount = 1; ptr = buf;
/* ...and parse */
while (count-- > 0)
{ const char ch = *ptr++;
if (IS_NEWLINE(ch))
{ if (!inside_comment) handle_word();
inside_comment = is_line_indented = falsE;
wordcount = 0; linecount++;
cleanup_on_newline();
}
else if (!inside_comment)
{ if (IS_WHITESPACE(ch))
{ if ( (wordcount == 0) && (wordstart == NULL) ) is_line_indented = truE;
handle_word();
}
else
{ if (wordstart == NULL)
{ if (ch == '#') inside_comment = truE;
else wordstart = ptr - 1;
}
}
}
}
handle_word();
cleanup_on_newline();
}
static one_caller void __init rtconfig_handle(void)
/* handles the (built-in/external) run-time configuration */
{ static const char strRtc[] = N_("Run-time configuration: ");
char path[1024], *spfbuf_path, *spfbuf;
int fd;
struct stat statbuf;
off_t filesize;
void* filebuf;
#if OPTION_BIRTCFG
if (config.flags & cfRtBuiltin)
{ sprint_safe(strbuf, _("%sparsing the built-in configuration%s"), _(strRtc),
" (about:birtcfg)");
store_initmsg(strbuf);
rtconfig_parse(strBirtcfg, strlen(strBirtcfg));
}
#endif
/* Map the config file into memory... */
if (!(config.flags & cfRtExternal)) goto no_external_rtcfg;
my_spf(path, 1024, &spfbuf_path, strPercsPercs, config.path, "config");
fd = my_open(spfbuf_path, O_RDONLY);
if (fd == -1) /* no (readable) config file */
{ if ( (errno == ENOENT) && (!(config.flags & cfUserDefinedConfigPath)) )
{ /* The error is "file does not exist", and the user didn't ask for a
non-default configuration directory on the command-line; this is the
case if new users didn't yet setup a config file, and we don't wanna
annoy users with such an (in this case pointless) error message. */
}
else
{ my_spf(strbuf, STRBUF_SIZE, &spfbuf,
_("%scan't open \"%s\" (error #%d, %s)"), _(strRtc), spfbuf_path,
errno, my_strerror(errno));
store_initmsg(spfbuf);
my_spf_cleanup(strbuf, spfbuf);
}
goto finish;
}
if (my_fstat(fd, &statbuf) != 0)
{ my_spf(strbuf, STRBUF_SIZE, &spfbuf,
_("%scan't get system information about \"%s\" (error #%d, %s)"),
_(strRtc), spfbuf_path, errno, my_strerror(errno));
fail_cleanup:
store_initmsg(spfbuf);
my_spf_cleanup(strbuf, spfbuf);
goto cleanup_file;
}
if (!S_ISREG(statbuf.st_mode)) /* could be /dev/zero :-) */
{ my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("%s\"%s\" isn't a regular file"),
_(strRtc), spfbuf_path);
goto fail_cleanup;
}
filesize = statbuf.st_size;
if (filesize <= 0)
{ my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("%sfile \"%s\" is empty"),
_(strRtc), spfbuf_path);
goto fail_cleanup;
}
filebuf = my_mmap(filesize, fd);
if (filebuf == MAP_FAILED)
{ my_spf(strbuf, STRBUF_SIZE, &spfbuf,
_("%scan't read file \"%s\" (error #%d, %s)"), _(strRtc), spfbuf_path,
errno, my_strerror(errno));
goto fail_cleanup;
}
my_madvise_sequential(filebuf, filesize);
/* (That's probably overkill since most config files will be small...) */
/* ...and parse it */
my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("%sreading file \"%s\""), _(strRtc),
spfbuf_path);
store_initmsg(spfbuf);
my_spf_cleanup(strbuf, spfbuf);
rtconfig_parse(filebuf, filesize);
my_munmap(filebuf, filesize);
cleanup_file:
my_close(fd);
finish:
my_spf_cleanup(path, spfbuf_path);
no_external_rtcfg:
list_reverse(config.http_version, tConfigProtocolVersion);
#if CONFIG_JUMPS
list_reverse(config.jumps, tConfigJump);
#endif
list_reverse(config.http_proxies, tConfigProxy);
#if OPTION_TLS
list_reverse(config.https_proxies, tConfigProxy);
#endif
#if CONFIG_LOCALDIR > 1
list_reverse(config.lds, tConfigLocaldirsort);
list_reverse(config.ldf, tConfigLocaldirformat);
#endif
#if OPTION_COOKIES
list_reverse(config.http_cookies, tConfigCookie);
#endif
#if OPTION_TLS && OPTION_COOKIES
list_reverse(config.https_cookies, tConfigCookie);
#endif
#if CONFIG_FTP && OPTION_TLS
list_reverse(config.ftp_tls_method, tConfigFtpTlsMethod);
#endif
#if OPTION_LOCAL_CGI
list_reverse(config.local_cgi, tConfigLocalCgi);
#endif
#if OPTION_EXECEXT & EXECEXT_SHELL
list_reverse(config.execext_shell, tExecextParam);
#endif
/* Setup cachepath */
#if CONFIG_DISK_CACHE
if (config.diskcachesize > 0)
{ tBoolean usable = falsE; /* assume failure */
char* cachepath;
my_spf(NULL, 0, &cachepath, strPercsPercs, config.path, "cache1/");
/* ("1" is the current cache format version number) */
if (my_stat(cachepath, &statbuf) == 0)
{ if (S_ISDIR(statbuf.st_mode)) usable = truE;
/* "else": someone messed up the contents of the directory ".retawq/" */
}
else
{ if ( (config.flags & cfMayWriteDiskCache) &&
(mkdir(cachepath, S_IRWXU) == 0) )
usable = truE;
}
if (usable)
{ config.flags |= cfUseDiskCache;
config.cachepath = my_spf_use(cachepath);
}
else my_spf_cleanup(NULL, cachepath);
}
#endif
}
#endif /* #if CONFIG_RTCONFIG */
static one_caller void __init initialize_config(void)
/* initializes the run-time configuration */
{ const char *langstr = strEn, *unamestr;
char *temp, *temp2;
char ch;
/* Set some default configuration values */
if (config.path == NULL)
{ my_spf(NULL, 0, &temp, strPercsPercs, get_homepath(), ".retawq/");
config.path = my_spf_use(temp);
}
config.home_uri = my_strdup("http://retawq.sourceforge.net/");
config.ramcachesize = 100 * KILO;
config.redirections = 10;
#if TGC_IS_GRAPHICS
config.width = 400; config.height = 400;
#elif TGC_IS_CURSES
config.char_yes = str2char_(strYes); config.char_no = str2char_(strNo);
#endif
config.char_file = str2char_(strFileUc);
config.char_dir = str2char_(strDirectoryUc);
ch = str2char_(strLink); config.char_link = my_toupper(ch);
#if CONFIG_CONSOLE
config.console_backspace = 8;
#endif
#if (MIGHT_USE_SCROLL_BARS) && ( (CONFIG_TG == TG_XCURSES) || (TGC_IS_GRAPHICS) )
config.flags |= cfUseScrollBars;
#endif
/* Handle the run-time configuration */
#if CONFIG_RTCONFIG
rtconfig_handle();
#endif
/* Setup the default session path */
#if CONFIG_SESSIONS
my_spf(NULL, 0, &temp, strPercsPercs, config.path, "session");
config.session_default = my_spf_use(temp);
#endif
/* Setup language stuff */
#if OPTION_I18N
if ( (current_locale != NULL) && (lookup_language_code() >= 0) )
{ /* valid, known, usable locale... */
langstr = current_locale;
}
#endif
if (config.languages == NULL) config.languages = my_strdup(langstr);
/* Setup user_agent */
unamestr = strEmpty; /* default */
#if HAVE_UNAME && CONFIG_RTCONFIG
if (allow_uname_in_user_agent > 0)
{ struct utsname uts;
if (uname(&uts) == 0)
{ const char *usys = uts.sysname, *urel = uts.release, *umac = uts.machine;
/* check pointers */
if ( (usys == NULL) || (*usys == '\0') ) goto no_uname;
if ( (allow_uname_in_user_agent >= 3) &&
( (umac == NULL) || (*umac == '\0') ) )
allow_uname_in_user_agent = 2;
if ( (allow_uname_in_user_agent >= 2) &&
( (urel == NULL) || (*urel == '\0') ) )
allow_uname_in_user_agent = 1;
/* build the string */
if (allow_uname_in_user_agent >= 3)
my_spf(strbuf, STRBUF_SIZE, &temp, "; %s %s; %s", usys, urel, umac);
else if (allow_uname_in_user_agent == 2)
my_spf(strbuf, STRBUF_SIZE, &temp, "; %s %s", usys, urel);
else /* allow_uname_in_user_agent == 1 */
my_spf(strbuf, STRBUF_SIZE, &temp, "; %s", usys);
unamestr = my_spf_use(temp);
no_uname: {}
}
}
#endif
#if CONFIG_JAVASCRIPT
#define strJs ", js"
#else
#define strJs ""
#endif
#if CONFIG_CSS
#define strCss ", css"
#else
#define strCss ""
#endif
my_spf(NULL, 0, &temp2, "retawq/" RETAWQ_VERSION " [%s] (%s" strJs strCss
strTls "%s)", langstr, strTG, unamestr);
config.user_agent = my_spf_use(temp2);
if (unamestr != strEmpty) my_spf_cleanup(strbuf, unamestr);
}
one_caller void __init initialize(int argc, const char** argv)
/* initializes most parts of the program */
{ struct stat statbuf;
#if NEED_FD_REGISTER
fd_register_init(); /* (must be done _very_ early) */
#endif
if (my_fstat_unregistried(0, &statbuf) == 0)
{ fd_stdin = 0; fd_register(&fd_stdin, fdkOther);
fd_keyboard_input = fd_stdin; __lfdmbs |= 1;
}
if (my_fstat_unregistried(1, &statbuf) == 0)
{ fd_stdout = 1; fd_register(&fd_stdout, fdkOther); __lfdmbs |= 2; }
if (my_fstat_unregistried(2, &statbuf) == 0)
{ fd_stderr = 2; fd_register(&fd_stderr, fdkOther); __lfdmbs |= 4; }
#if CONFIG_DEBUG
{ int fd = my_create("debug.txt", O_CREAT | O_TRUNC | O_WRONLY,
S_IRUSR | S_IWUSR);
if (fd < 0) fatal_error(errno, "can't create debugging file");
debugfd = fd;
}
make_fd_cloexec(debugfd);
debugmsg("retawq " RETAWQ_VERSION " general debugging file ()\n");
sprint_safe(strbuf, "__lfdmbs: %d\n", __lfdmbs);
debugmsg(strbuf);
#endif
precheck_commandline(argc, argv);
fd_observe_init();
#if CONFIG_TG == TG_GTK
gtk_init(&argc, &argv); /* (must be done _before_ process_commandline()) */
#endif
#if OPTION_I18N
initialize_i18n(); /* (do this early, but _after_ gtk_init()) */
#endif
my_memclr_var(config);
#if CONFIG_RTCONFIG
config.flags |= cfRtAll;
#endif
process_commandline(argc, argv);
#if TGC_IS_GRAPHICS
if (is_environed)
{
#if CONFIG_TG == TG_X
xws_display = XOpenDisplay(NULL);
if (xws_display == NULL) fatal_error(0, _(strCantOpenXws));
need_tglib_cleanup = truE;
#endif
}
else
#elif CONFIG_TG == TG_XCURSES
if (is_environed) { /* no isatty() requirements with xcurses window */ }
else
#endif
{ if ( (is_promptable) &&
( (!my_isatty(fd_stdin)) || (!my_isatty(fd_stdout)) ) )
fatal_error(0, _("need to be launched on a terminal"));
}
#if (!CONFIG_ASYNC_DNS)
/* show a warning once when compiled and every time when run */
#if CAN_ISSUE_WARNINGS
#warning "With the current configuration, all DNS hostname lookups will block the whole program, and it will seem to hang. Please consider setting OPTION_THREADING to 1."
#endif
show_warning(_("with the current configuration, DNS hostname lookups will \"block\" the whole program, and it will seem to hang"));
#endif
initialize_config();
if (is_environed)
{
#if (MIGHT_DO_TERMIOS) && (CONFIG_TG == TG_BICURSES)
prepare_terminal();
#endif
#if TGC_IS_CURSES
initialize_curses();
#elif (MIGHT_USE_COLORS) && (CONFIG_TG == TG_X)
if ( (!(config.flags & cfColorsOff)) &&
(XDisplayCells(xws_display, DefaultScreen(xws_display)) > 2) )
use_colors = truE;
#elif CONFIG_TG == TG_GTK
gdk_rgb_init();
#endif
}
#if MIGHT_DO_TERMIOS && CONFIG_CONSOLE
else if (program_mode == pmConsole) prepare_terminal();
#endif
#if CAN_HANDLE_SIGNALS
initialize_signals(); /* (must be called _after_ curses init) */
#endif
parser_initialize();
#if CONFIG_JAVASCRIPT
javascript_initialize();
#endif
i18n_cleanup
resource_initialize(); /* (do this quite late - it might create threads) */
#if TGC_IS_CURSES
if (is_environed)
{ /* (last step, to get rid of gdb's stupid thread announcements...) */
(void) clear();
}
#endif
}