Files
w3m/etc.c
Rene Kita 9192ed59db Add option to set directory for temporary files
With this patch applied the user can configure a directory to store
temporary/cache files. The history and cookies remain in RC_DIR.

I suppose the intent of writing the history to a temp file first is to
make the actual write an atomic operation. As rename() does not work
across mount points, we need to handle the temp file for the history
different to keep this behaviour.

Add a new type for the temp history file and handle this case different
when creating a temp file.
2022-05-01 11:50:09 +02:00

2099 lines
40 KiB
C

/* $Id: etc.c,v 1.81 2007/05/23 15:06:05 inu Exp $ */
#include "fm.h"
#ifndef __MINGW32_VERSION
#include <pwd.h>
#endif
#include "myctype.h"
#include "html.h"
#include "local.h"
#include "hash.h"
#include <fcntl.h>
#include <sys/types.h>
#include <time.h>
#if defined(HAVE_WAITPID) || defined(HAVE_WAIT3)
#include <sys/wait.h>
#endif
#include <signal.h>
#ifdef __WATT32__
#define read(a,b,c) read_s(a,b,c)
#define close(x) close_s(x)
#endif /* __WATT32__ */
struct auth_pass {
int bad;
int is_proxy;
Str host;
int port;
/* Str file; */
Str realm;
Str uname;
Str pwd;
struct auth_pass *next;
};
struct auth_pass *passwords = NULL;
int
columnSkip(Buffer *buf, int offset)
{
int i, maxColumn;
int column = buf->currentColumn + offset;
int nlines = buf->LINES + 1;
Line *l;
maxColumn = 0;
for (i = 0, l = buf->topLine; i < nlines && l != NULL; i++, l = l->next) {
if (l->width < 0)
l->width = COLPOS(l, l->len);
if (l->width - 1 > maxColumn)
maxColumn = l->width - 1;
}
maxColumn -= buf->COLS - 1;
if (column < maxColumn)
maxColumn = column;
if (maxColumn < 0)
maxColumn = 0;
if (buf->currentColumn == maxColumn)
return 0;
buf->currentColumn = maxColumn;
return 1;
}
int
columnPos(Line *line, int column)
{
int i;
for (i = 1; i < line->len; i++) {
if (COLPOS(line, i) > column)
break;
}
#ifdef USE_M17N
for (i--; i > 0 && line->propBuf[i] & PC_WCHAR2; i--) ;
return i;
#else
return i - 1;
#endif
}
Line *
lineSkip(Buffer *buf, Line *line, int offset, int last)
{
int i;
Line *l;
l = currentLineSkip(buf, line, offset, last);
if (!nextpage_topline)
for (i = buf->LINES - 1 - (buf->lastLine->linenumber - l->linenumber);
i > 0 && l->prev != NULL; i--, l = l->prev) ;
return l;
}
Line *
currentLineSkip(Buffer *buf, Line *line, int offset, int last)
{
int i, n;
Line *l = line;
if (buf->pagerSource && !(buf->bufferprop & BP_CLOSE)) {
n = line->linenumber + offset + buf->LINES;
if (buf->lastLine->linenumber < n)
getNextPage(buf, n - buf->lastLine->linenumber);
while ((last || (buf->lastLine->linenumber < n)) &&
(getNextPage(buf, 1) != NULL)) ;
if (last)
l = buf->lastLine;
}
if (offset == 0)
return l;
if (offset > 0)
for (i = 0; i < offset && l->next != NULL; i++, l = l->next) ;
else
for (i = 0; i < -offset && l->prev != NULL; i++, l = l->prev) ;
return l;
}
#define MAX_CMD_LEN 128
int
gethtmlcmd(char **s)
{
extern Hash_si tagtable;
char cmdstr[MAX_CMD_LEN];
char *p = cmdstr;
char *save = *s;
int cmd;
(*s)++;
/* first character */
if (IS_ALNUM(**s) || **s == '_' || **s == '/') {
*(p++) = TOLOWER(**s);
(*s)++;
}
else
return HTML_UNKNOWN;
if (p[-1] == '/')
SKIP_BLANKS(*s);
while ((IS_ALNUM(**s) || **s == '_') && p - cmdstr < MAX_CMD_LEN) {
*(p++) = TOLOWER(**s);
(*s)++;
}
if (p - cmdstr == MAX_CMD_LEN) {
/* buffer overflow: perhaps caused by bad HTML source */
*s = save + 1;
return HTML_UNKNOWN;
}
*p = '\0';
/* hash search */
cmd = getHash_si(&tagtable, cmdstr, HTML_UNKNOWN);
while (**s && **s != '>')
(*s)++;
if (**s == '>')
(*s)++;
return cmd;
}
#ifdef USE_ANSI_COLOR
static int
parse_ansi_color(char **str, Lineprop *effect, Linecolor *color)
{
char *p = *str, *q;
Lineprop e = *effect;
Linecolor c = *color;
int i;
if (*p != ESC_CODE || *(p + 1) != '[')
return 0;
p += 2;
for (q = p; IS_DIGIT(*q) || *q == ';'; q++) ;
if (*q != 'm')
return 0;
*str = q + 1;
while (1) {
if (*p == 'm') {
e = PE_NORMAL;
c = 0;
break;
}
if (IS_DIGIT(*p)) {
q = p;
for (p++; IS_DIGIT(*p); p++) ;
i = atoi(allocStr(q, p - q));
switch (i) {
case 0:
e = PE_NORMAL;
c = 0;
break;
case 1:
case 5:
e = PE_BOLD;
break;
case 4:
e = PE_UNDER;
break;
case 7:
e = PE_STAND;
break;
case 100: /* for EWS4800 kterm */
c = 0;
break;
case 39:
c &= 0xf0;
break;
case 49:
c &= 0x0f;
break;
default:
if (i >= 30 && i <= 37)
c = (c & 0xf0) | (i - 30) | 0x08;
else if (i >= 40 && i <= 47)
c = (c & 0x0f) | ((i - 40) << 4) | 0x80;
break;
}
if (*p == 'm')
break;
}
else {
e = PE_NORMAL;
c = 0;
break;
}
p++; /* *p == ';' */
}
*effect = e;
*color = c;
return 1;
}
#endif
/*
* Check character type
*/
Str
checkType(Str s, Lineprop **oprop, Linecolor **ocolor)
{
Lineprop mode;
Lineprop effect = PE_NORMAL;
Lineprop *prop;
static Lineprop *prop_buffer = NULL;
static int prop_size = 0;
char *str = s->ptr, *endp = &s->ptr[s->length], *bs = NULL;
#ifdef USE_ANSI_COLOR
Lineprop ceffect = PE_NORMAL;
Linecolor cmode = 0;
int check_color = FALSE;
Linecolor *color = NULL;
static Linecolor *color_buffer = NULL;
static int color_size = 0;
char *es = NULL;
#endif
int do_copy = FALSE;
#ifdef USE_M17N
int i;
int plen = 0, clen;
#endif
if (prop_size < s->length) {
prop_size = (s->length > LINELEN) ? s->length : LINELEN;
prop_buffer = New_Reuse(Lineprop, prop_buffer, prop_size);
}
prop = prop_buffer;
if (ShowEffect) {
bs = memchr(str, '\b', s->length);
#ifdef USE_ANSI_COLOR
if (ocolor) {
es = memchr(str, ESC_CODE, s->length);
if (es) {
if (color_size < s->length) {
color_size = (s->length > LINELEN) ? s->length : LINELEN;
color_buffer = New_Reuse(Linecolor, color_buffer,
color_size);
}
color = color_buffer;
}
}
#endif
if ((bs != NULL)
#ifdef USE_ANSI_COLOR
|| (es != NULL)
#endif
) {
char *sp = str, *ep;
s = Strnew_size(s->length);
do_copy = TRUE;
ep = bs ? (bs - 2) : endp;
#ifdef USE_ANSI_COLOR
if (es && ep > es - 2)
ep = es - 2;
#endif
for (; str < ep && IS_ASCII(*str); str++) {
*(prop++) = PE_NORMAL | (IS_CNTRL(*str) ? PC_CTRL : PC_ASCII);
#ifdef USE_ANSI_COLOR
if (color)
*(color++) = 0;
#endif
}
Strcat_charp_n(s, sp, (int)(str - sp));
}
}
if (!do_copy) {
for (; str < endp && IS_ASCII(*str); str++)
*(prop++) = PE_NORMAL | (IS_CNTRL(*str) ? PC_CTRL : PC_ASCII);
}
while (str < endp) {
if (prop - prop_buffer >= prop_size)
break;
if (bs != NULL) {
#ifdef USE_M17N
if (str == bs - 2 && !strncmp(str, "__\b\b", 4)) {
str += 4;
effect = PE_UNDER;
if (str < endp)
bs = memchr(str, '\b', endp - str);
continue;
}
else
#endif
if (str == bs - 1 && *str == '_') {
str += 2;
effect = PE_UNDER;
if (str < endp)
bs = memchr(str, '\b', endp - str);
continue;
}
else if (str == bs) {
if (*(str + 1) == '_') {
if (s->length) {
str += 2;
#ifdef USE_M17N
for (i = 1; i <= plen; i++)
*(prop - i) |= PE_UNDER;
#else
*(prop - 1) |= PE_UNDER;
#endif
}
else {
str++;
}
}
#ifdef USE_M17N
else if (!strncmp(str + 1, "\b__", 3)) {
if (s->length) {
str += (plen == 1) ? 3 : 4;
for (i = 1; i <= plen; i++)
*(prop - i) |= PE_UNDER;
}
else {
str += 2;
}
}
else if (*(str + 1) == '\b') {
if (s->length) {
clen = get_mclen(str + 2);
if (plen == clen &&
!strncmp(str - plen, str + 2, plen)) {
for (i = 1; i <= plen; i++)
*(prop - i) |= PE_BOLD;
str += 2 + clen;
}
else {
Strshrink(s, plen);
prop -= plen;
str += 2;
}
}
else {
str += 2;
}
}
#endif
else {
if (s->length) {
#ifdef USE_M17N
clen = get_mclen(str + 1);
if (plen == clen &&
!strncmp(str - plen, str + 1, plen)) {
for (i = 1; i <= plen; i++)
*(prop - i) |= PE_BOLD;
str += 1 + clen;
}
else {
Strshrink(s, plen);
prop -= plen;
str++;
}
#else
if (*(str - 1) == *(str + 1)) {
*(prop - 1) |= PE_BOLD;
str += 2;
}
else {
Strshrink(s, 1);
prop--;
str++;
}
#endif
}
else {
str++;
}
}
if (str < endp)
bs = memchr(str, '\b', endp - str);
continue;
}
#ifdef USE_ANSI_COLOR
else if (str > bs)
bs = memchr(str, '\b', endp - str);
#endif
}
#ifdef USE_ANSI_COLOR
if (es != NULL) {
if (str == es) {
int ok = parse_ansi_color(&str, &ceffect, &cmode);
if (str < endp)
es = memchr(str, ESC_CODE, endp - str);
if (ok) {
if (cmode)
check_color = TRUE;
continue;
}
}
else if (str > es)
es = memchr(str, ESC_CODE, endp - str);
}
#endif
mode = get_mctype(str) | effect;
#ifdef USE_ANSI_COLOR
if (color) {
*(color++) = cmode;
mode |= ceffect;
}
#endif
*(prop++) = mode;
#ifdef USE_M17N
plen = get_mclen(str);
if (plen > 1) {
mode = (mode & ~PC_WCHAR1) | PC_WCHAR2;
for (i = 1; i < plen; i++) {
*(prop++) = mode;
#ifdef USE_ANSI_COLOR
if (color)
*(color++) = cmode;
#endif
}
if (do_copy)
Strcat_charp_n(s, (char *)str, plen);
str += plen;
}
else
#endif
{
if (do_copy)
Strcat_char(s, (char)*str);
str++;
}
effect = PE_NORMAL;
}
*oprop = prop_buffer;
#ifdef USE_ANSI_COLOR
if (ocolor)
*ocolor = check_color ? color_buffer : NULL;
#endif
return s;
}
static int
nextColumn(int n, char *p, Lineprop *pr)
{
if (*pr & PC_CTRL) {
if (*p == '\t')
return (n + Tabstop) / Tabstop * Tabstop;
else if (*p == '\n')
return n + 1;
else if (*p != '\r')
return n + 2;
return n;
}
#ifdef USE_M17N
if (*pr & PC_UNKNOWN)
return n + 4;
return n + wtf_width((wc_uchar *) p);
#else
return n + 1;
#endif
}
int
calcPosition(char *l, Lineprop *pr, int len, int pos, int bpos, int mode)
{
static int *realColumn = NULL;
static int size = 0;
static char *prevl = NULL;
int i, j;
if (l == NULL || len == 0 || pos < 0)
return bpos;
if (l == prevl && mode == CP_AUTO) {
if (pos <= len)
return realColumn[pos];
}
if (size < len + 1) {
size = (len + 1 > LINELEN) ? (len + 1) : LINELEN;
realColumn = New_N(int, size);
}
prevl = l;
i = 0;
j = bpos;
#ifdef USE_M17N
if (pr[i] & PC_WCHAR2) {
for (; i < len && pr[i] & PC_WCHAR2; i++)
realColumn[i] = j;
if (i > 0 && pr[i - 1] & PC_KANJI && WcOption.use_wide)
j++;
}
#endif
while (1) {
realColumn[i] = j;
if (i == len)
break;
j = nextColumn(j, &l[i], &pr[i]);
i++;
#ifdef USE_M17N
for (; i < len && pr[i] & PC_WCHAR2; i++)
realColumn[i] = realColumn[i - 1];
#endif
}
if (pos >= i)
return j;
return realColumn[pos];
}
int
columnLen(Line *line, int column)
{
int i, j;
for (i = 0, j = 0; i < line->len;) {
j = nextColumn(j, &line->lineBuf[i], &line->propBuf[i]);
if (j > column)
return i;
i++;
#ifdef USE_M17N
while (i < line->len && line->propBuf[i] & PC_WCHAR2)
i++;
#endif
}
return line->len;
}
char *
lastFileName(char *path)
{
char *p, *q;
p = q = path;
while (*p != '\0') {
if (*p == '/')
q = p + 1;
p++;
}
return allocStr(q, -1);
}
#ifdef USE_INCLUDED_SRAND48
static unsigned long R1 = 0x1234abcd;
static unsigned long R2 = 0x330e;
#define A1 0x5deec
#define A2 0xe66d
#define C 0xb
void
srand48(long seed)
{
R1 = (unsigned long)seed;
R2 = 0x330e;
}
long
lrand48(void)
{
R1 = (A1 * R1 << 16) + A1 * R2 + A2 * R1 + ((A2 * R2 + C) >> 16);
R2 = (A2 * R2 + C) & 0xffff;
return (long)(R1 >> 1);
}
#endif
char *
mybasename(char *s)
{
char *p = s;
while (*p)
p++;
while (s <= p && *p != '/')
p--;
if (*p == '/')
p++;
else
p = s;
return allocStr(p, -1);
}
char *
mydirname(char *s)
{
char *p = s;
while (*p)
p++;
if (s != p)
p--;
while (s != p && *p == '/')
p--;
while (s != p && *p != '/')
p--;
if (*p != '/')
return ".";
while (s != p && *p == '/')
p--;
return allocStr(s, strlen(s) - strlen(p) + 1);
}
#ifndef HAVE_STRERROR
char *
strerror(int errno)
{
extern char *sys_errlist[];
return sys_errlist[errno];
}
#endif /* not HAVE_STRERROR */
int
next_status(char c, int *status)
{
switch (*status) {
case R_ST_NORMAL:
if (c == '<') {
*status = R_ST_TAG0;
return 0;
}
else if (c == '&') {
*status = R_ST_AMP;
return 1;
}
else
return 1;
break;
case R_ST_TAG0:
if (c == '!') {
*status = R_ST_CMNT1;
return 0;
}
*status = R_ST_TAG;
/* continues to next case */
case R_ST_TAG:
if (c == '>')
*status = R_ST_NORMAL;
else if (c == '=')
*status = R_ST_EQL;
return 0;
case R_ST_EQL:
if (c == '"')
*status = R_ST_DQUOTE;
else if (c == '\'')
*status = R_ST_QUOTE;
else if (IS_SPACE(c))
*status = R_ST_EQL;
else if (c == '>')
*status = R_ST_NORMAL;
else
*status = R_ST_VALUE;
return 0;
case R_ST_QUOTE:
if (c == '\'')
*status = R_ST_TAG;
return 0;
case R_ST_DQUOTE:
if (c == '"')
*status = R_ST_TAG;
return 0;
case R_ST_VALUE:
if (c == '>')
*status = R_ST_NORMAL;
else if (IS_SPACE(c))
*status = R_ST_TAG;
return 0;
case R_ST_AMP:
if (c == ';') {
*status = R_ST_NORMAL;
return 0;
}
else if (c != '#' && !IS_ALNUM(c) && c != '_') {
/* something's wrong! */
*status = R_ST_NORMAL;
return 0;
}
else
return 0;
case R_ST_CMNT1:
switch (c) {
case '-':
*status = R_ST_CMNT2;
break;
case '>':
*status = R_ST_NORMAL;
break;
case 'D':
case 'd':
/* could be a !doctype */
*status = R_ST_TAG;
break;
default:
*status = R_ST_IRRTAG;
}
return 0;
case R_ST_CMNT2:
switch (c) {
case '-':
*status = R_ST_CMNT;
break;
case '>':
*status = R_ST_NORMAL;
break;
default:
*status = R_ST_IRRTAG;
}
return 0;
case R_ST_CMNT:
if (c == '-')
*status = R_ST_NCMNT1;
return 0;
case R_ST_NCMNT1:
if (c == '-')
*status = R_ST_NCMNT2;
else
*status = R_ST_CMNT;
return 0;
case R_ST_NCMNT2:
switch (c) {
case '>':
*status = R_ST_NORMAL;
break;
case '-':
*status = R_ST_NCMNT2;
break;
default:
if (IS_SPACE(c))
*status = R_ST_NCMNT3;
else
*status = R_ST_CMNT;
break;
}
break;
case R_ST_NCMNT3:
switch (c) {
case '>':
*status = R_ST_NORMAL;
break;
case '-':
*status = R_ST_NCMNT1;
break;
default:
if (IS_SPACE(c))
*status = R_ST_NCMNT3;
else
*status = R_ST_CMNT;
break;
}
return 0;
case R_ST_IRRTAG:
if (c == '>')
*status = R_ST_NORMAL;
return 0;
}
/* notreached */
return 0;
}
int
read_token(Str buf, char **instr, int *status, int pre, int append)
{
char *p;
int prev_status;
if (!append)
Strclear(buf);
if (**instr == '\0')
return 0;
for (p = *instr; *p; p++) {
prev_status = *status;
next_status(*p, status);
switch (*status) {
case R_ST_NORMAL:
if (prev_status == R_ST_AMP && *p != ';') {
p--;
break;
}
if (prev_status == R_ST_NCMNT2 || prev_status == R_ST_NCMNT3 ||
prev_status == R_ST_IRRTAG || prev_status == R_ST_CMNT1) {
if (prev_status == R_ST_CMNT1 && !append && !pre)
Strclear(buf);
if (pre)
Strcat_char(buf, *p);
p++;
goto proc_end;
}
Strcat_char(buf, (!pre && IS_SPACE(*p)) ? ' ' : *p);
if (ST_IS_REAL_TAG(prev_status)) {
*instr = p + 1;
if (buf->length < 2 ||
buf->ptr[buf->length - 2] != '<' ||
buf->ptr[buf->length - 1] != '>')
return 1;
Strshrink(buf, 2);
}
break;
case R_ST_TAG0:
case R_ST_TAG:
if (prev_status == R_ST_NORMAL && p != *instr) {
*instr = p;
*status = prev_status;
return 1;
}
if (*status == R_ST_TAG0 && !REALLY_THE_BEGINNING_OF_A_TAG(p)) {
/* it seems that this '<' is not a beginning of a tag */
/*
* Strcat_charp(buf, "&lt;");
*/
Strcat_char(buf, '<');
*status = R_ST_NORMAL;
}
else
Strcat_char(buf, *p);
break;
case R_ST_EQL:
case R_ST_QUOTE:
case R_ST_DQUOTE:
case R_ST_VALUE:
case R_ST_AMP:
Strcat_char(buf, *p);
break;
case R_ST_CMNT:
case R_ST_IRRTAG:
if (pre)
Strcat_char(buf, *p);
else if (!append)
Strclear(buf);
break;
case R_ST_CMNT1:
case R_ST_CMNT2:
case R_ST_NCMNT1:
case R_ST_NCMNT2:
case R_ST_NCMNT3:
/* do nothing */
if (pre)
Strcat_char(buf, *p);
break;
}
}
proc_end:
*instr = p;
return 1;
}
Str
correct_irrtag(int status)
{
char c;
Str tmp = Strnew();
while (status != R_ST_NORMAL) {
switch (status) {
case R_ST_CMNT: /* required "-->" */
case R_ST_NCMNT1: /* required "->" */
c = '-';
break;
case R_ST_NCMNT2:
case R_ST_NCMNT3:
case R_ST_IRRTAG:
case R_ST_CMNT1:
case R_ST_CMNT2:
case R_ST_TAG:
case R_ST_TAG0:
case R_ST_EQL: /* required ">" */
case R_ST_VALUE:
c = '>';
break;
case R_ST_QUOTE:
c = '\'';
break;
case R_ST_DQUOTE:
c = '"';
break;
case R_ST_AMP:
c = ';';
break;
default:
return tmp;
}
next_status(c, &status);
Strcat_char(tmp, c);
}
return tmp;
}
/*
* RFC2617: 1.2 Access Authentication Framework
*
* The realm value (case-sensitive), in combination with the canonical root
* URL (the absoluteURI for the server whose abs_path is empty; see section
* 5.1.2 of RFC2616 ) of the server being accessed, defines the protection
* space. These realms allow the protected resources on a server to be
* partitioned into a set of protection spaces, each with its own
* authentication scheme and/or authorization database.
*
*/
static void
add_auth_pass_entry(const struct auth_pass *ent, int netrc, int override)
{
if ((ent->host || netrc) /* netrc accept default (host == NULL) */
&&(ent->is_proxy || ent->realm || netrc)
&& ent->uname && ent->pwd) {
struct auth_pass *newent = New(struct auth_pass);
memcpy(newent, ent, sizeof(struct auth_pass));
if (override) {
newent->next = passwords;
passwords = newent;
}
else {
if (passwords == NULL)
passwords = newent;
else if (passwords->next == NULL)
passwords->next = newent;
else {
struct auth_pass *ep = passwords;
for (; ep->next; ep = ep->next) ;
ep->next = newent;
}
}
}
/* ignore invalid entries */
}
static struct auth_pass *
find_auth_pass_entry(char *host, int port, char *realm, char *uname,
int is_proxy)
{
struct auth_pass *ent;
for (ent = passwords; ent != NULL; ent = ent->next) {
if (ent->is_proxy == is_proxy
&& (ent->bad != TRUE)
&& (!ent->host || !Strcasecmp_charp(ent->host, host))
&& (!ent->port || ent->port == port)
&& (!ent->uname || !uname || !Strcmp_charp(ent->uname, uname))
&& (!ent->realm || !realm || !Strcmp_charp(ent->realm, realm))
)
return ent;
}
return NULL;
}
int
find_auth_user_passwd(ParsedURL *pu, char *realm,
Str *uname, Str *pwd, int is_proxy)
{
struct auth_pass *ent;
if (pu->user && pu->pass) {
*uname = Strnew_charp(pu->user);
*pwd = Strnew_charp(pu->pass);
return 1;
}
ent = find_auth_pass_entry(pu->host, pu->port, realm, pu->user, is_proxy);
if (ent) {
*uname = ent->uname;
*pwd = ent->pwd;
return 1;
}
return 0;
}
void
add_auth_user_passwd(ParsedURL *pu, char *realm, Str uname, Str pwd,
int is_proxy)
{
struct auth_pass ent;
memset(&ent, 0, sizeof(ent));
ent.is_proxy = is_proxy;
ent.host = Strnew_charp(pu->host);
ent.port = pu->port;
ent.realm = Strnew_charp(realm);
ent.uname = uname;
ent.pwd = pwd;
add_auth_pass_entry(&ent, 0, 1);
}
void
invalidate_auth_user_passwd(ParsedURL *pu, char *realm, Str uname, Str pwd,
int is_proxy)
{
struct auth_pass *ent;
ent = find_auth_pass_entry(pu->host, pu->port, realm, NULL, is_proxy);
if (ent) {
ent->bad = TRUE;
}
return;
}
/* passwd */
/*
* machine <host>
* host <host>
* port <port>
* proxy
* path <file> ; not used
* realm <realm>
* login <login>
* passwd <passwd>
* password <passwd>
*/
static Str
next_token(Str arg)
{
Str narg = NULL;
char *p, *q;
if (arg == NULL || arg->length == 0)
return NULL;
p = arg->ptr;
q = p;
SKIP_NON_BLANKS(q);
if (*q != '\0') {
*q++ = '\0';
SKIP_BLANKS(q);
if (*q != '\0')
narg = Strnew_charp(q);
}
return narg;
}
static void
parsePasswd(FILE * fp, int netrc)
{
struct auth_pass ent;
Str line = NULL;
bzero(&ent, sizeof(struct auth_pass));
while (1) {
Str arg = NULL;
char *p;
if (line == NULL || line->length == 0)
line = Strfgets(fp);
if (line->length == 0)
break;
Strchop(line);
Strremovefirstspaces(line);
p = line->ptr;
if (*p == '#' || *p == '\0') {
line = NULL;
continue; /* comment or empty line */
}
arg = next_token(line);
if (!strcmp(p, "machine") || !strcmp(p, "host")
|| (netrc && !strcmp(p, "default"))) {
add_auth_pass_entry(&ent, netrc, 0);
bzero(&ent, sizeof(struct auth_pass));
if (netrc)
ent.port = 21; /* XXX: getservbyname("ftp"); ? */
if (strcmp(p, "default") != 0) {
line = next_token(arg);
ent.host = arg;
}
else {
line = arg;
}
}
else if (!netrc && !strcmp(p, "port") && arg) {
line = next_token(arg);
ent.port = atoi(arg->ptr);
}
else if (!netrc && !strcmp(p, "proxy")) {
ent.is_proxy = 1;
line = arg;
}
else if (!netrc && !strcmp(p, "path")) {
line = next_token(arg);
/* ent.file = arg; */
}
else if (!netrc && !strcmp(p, "realm")) {
/* XXX: rest of line becomes arg for realm */
line = NULL;
ent.realm = arg;
}
else if (!strcmp(p, "login")) {
line = next_token(arg);
ent.uname = arg;
}
else if (!strcmp(p, "password") || !strcmp(p, "passwd")) {
line = next_token(arg);
ent.pwd = arg;
}
else if (netrc && !strcmp(p, "machdef")) {
while ((line = Strfgets(fp))->length != 0) {
if (*line->ptr == '\n')
break;
}
line = NULL;
}
else if (netrc && !strcmp(p, "account")) {
/* ignore */
line = next_token(arg);
}
else {
/* ignore rest of line */
line = NULL;
}
}
add_auth_pass_entry(&ent, netrc, 0);
}
/* FIXME: gettextize? */
#define FILE_IS_READABLE_MSG "SECURITY NOTE: file %s must not be accessible by others"
FILE *
openSecretFile(char *fname)
{
char *efname;
struct stat st;
if (fname == NULL)
return NULL;
efname = expandPath(fname);
if (stat(efname, &st) < 0)
return NULL;
/* check permissions, if group or others readable or writable,
* refuse it, because it's insecure.
*
* XXX: disable_secret_security_check will introduce some
* security issues, but on some platform such as Windows
* it's not possible (or feasible) to disable group|other
* readable and writable.
* [w3m-dev 03368][w3m-dev 03369][w3m-dev 03370]
*/
if (disable_secret_security_check)
/* do nothing */ ;
else if ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
if (fmInitialized) {
message(Sprintf(FILE_IS_READABLE_MSG, fname)->ptr, 0, 0);
refresh();
}
else {
fputs(Sprintf(FILE_IS_READABLE_MSG, fname)->ptr, stderr);
fputc('\n', stderr);
}
sleep(2);
return NULL;
}
return fopen(efname, "r");
}
void
loadPasswd(void)
{
FILE *fp;
passwords = NULL;
fp = openSecretFile(passwd_file);
if (fp != NULL) {
parsePasswd(fp, 0);
fclose(fp);
}
/* for FTP */
fp = openSecretFile("~/.netrc");
if (fp != NULL) {
parsePasswd(fp, 1);
fclose(fp);
}
return;
}
/* get last modified time */
char *
last_modified(Buffer *buf)
{
TextListItem *ti;
struct stat st;
if (buf->document_header) {
for (ti = buf->document_header->first; ti; ti = ti->next) {
if (strncasecmp(ti->ptr, "Last-modified: ", 15) == 0) {
return ti->ptr + 15;
}
}
return "unknown";
}
else if (buf->currentURL.scheme == SCM_LOCAL) {
if (stat(buf->currentURL.file, &st) < 0)
return "unknown";
return ctime(&st.st_mtime);
}
return "unknown";
}
static char roman_num1[] = {
'i', 'x', 'c', 'm', '*',
};
static char roman_num5[] = {
'v', 'l', 'd', '*',
};
static Str
romanNum2(int l, int n)
{
Str s = Strnew();
switch (n) {
case 1:
case 2:
case 3:
for (; n > 0; n--)
Strcat_char(s, roman_num1[l]);
break;
case 4:
Strcat_char(s, roman_num1[l]);
Strcat_char(s, roman_num5[l]);
break;
case 5:
case 6:
case 7:
case 8:
Strcat_char(s, roman_num5[l]);
for (n -= 5; n > 0; n--)
Strcat_char(s, roman_num1[l]);
break;
case 9:
Strcat_char(s, roman_num1[l]);
Strcat_char(s, roman_num1[l + 1]);
break;
}
return s;
}
Str
romanNumeral(int n)
{
Str r = Strnew();
if (n <= 0)
return r;
if (n >= 4000) {
Strcat_charp(r, "**");
return r;
}
Strcat(r, romanNum2(3, n / 1000));
Strcat(r, romanNum2(2, (n % 1000) / 100));
Strcat(r, romanNum2(1, (n % 100) / 10));
Strcat(r, romanNum2(0, n % 10));
return r;
}
Str
romanAlphabet(int n)
{
Str r = Strnew();
int l;
char buf[14];
if (n <= 0)
return r;
l = 0;
while (n) {
buf[l++] = 'a' + (n - 1) % 26;
n = (n - 1) / 26;
}
l--;
for (; l >= 0; l--)
Strcat_char(r, buf[l]);
return r;
}
#ifndef SIGIOT
#define SIGIOT SIGABRT
#endif /* not SIGIOT */
static void
reset_signals(void)
{
#ifdef SIGHUP
mySignal(SIGHUP, SIG_DFL); /* terminate process */
#endif
mySignal(SIGINT, SIG_DFL); /* terminate process */
#ifdef SIGQUIT
mySignal(SIGQUIT, SIG_DFL); /* terminate process */
#endif
mySignal(SIGTERM, SIG_DFL); /* terminate process */
mySignal(SIGILL, SIG_DFL); /* create core image */
mySignal(SIGIOT, SIG_DFL); /* create core image */
mySignal(SIGFPE, SIG_DFL); /* create core image */
#ifdef SIGBUS
mySignal(SIGBUS, SIG_DFL); /* create core image */
#endif /* SIGBUS */
#ifdef SIGCHLD
mySignal(SIGCHLD, SIG_IGN);
#endif
#ifdef SIGPIPE
mySignal(SIGPIPE, SIG_IGN);
#endif
}
#ifndef FOPEN_MAX
#define FOPEN_MAX 1024 /* XXX */
#endif
static void
close_all_fds_except(int i, int f)
{
switch (i) { /* fall through */
case 0:
dup2(open(DEV_NULL_PATH, O_RDONLY), 0);
case 1:
dup2(open(DEV_NULL_PATH, O_WRONLY), 1);
case 2:
dup2(open(DEV_NULL_PATH, O_WRONLY), 2);
}
/* close all other file descriptors (socket, ...) */
for (i = 3; i < FOPEN_MAX; i++) {
if (i != f)
close(i);
}
}
void
setup_child(int child, int i, int f)
{
reset_signals();
mySignal(SIGINT, SIG_IGN);
#ifndef __MINGW32_VERSION
if (!child)
SETPGRP();
#endif /* __MINGW32_VERSION */
/*
* I don't know why but close_tty() sometimes interrupts loadGeneralFile() in loadImage()
* and corrupt image data can be cached in ~/.w3m.
*/
#if 0
close_tty();
#endif
close_all_fds_except(i, f);
QuietMessage = TRUE;
fmInitialized = FALSE;
TrapSignal = FALSE;
}
#ifndef __MINGW32_VERSION
pid_t
open_pipe_rw(FILE ** fr, FILE ** fw)
{
int fdr[2];
int fdw[2];
pid_t pid;
if (fr && pipe(fdr) < 0)
goto err0;
if (fw && pipe(fdw) < 0)
goto err1;
flush_tty();
pid = fork();
if (pid < 0)
goto err2;
if (pid == 0) {
/* child */
if (fr) {
close(fdr[0]);
dup2(fdr[1], 1);
}
if (fw) {
close(fdw[1]);
dup2(fdw[0], 0);
}
}
else {
if (fr) {
close(fdr[1]);
if (*fr == stdin)
dup2(fdr[0], 0);
else
*fr = fdopen(fdr[0], "r");
}
if (fw) {
close(fdw[0]);
if (*fw == stdout)
dup2(fdw[1], 1);
else
*fw = fdopen(fdw[1], "w");
}
}
return pid;
err2:
if (fw) {
close(fdw[0]);
close(fdw[1]);
}
err1:
if (fr) {
close(fdr[0]);
close(fdr[1]);
}
err0:
return (pid_t) - 1;
}
#endif /* __MINGW32_VERSION */
void
myExec(char *command)
{
mySignal(SIGINT, SIG_DFL);
execl("/bin/sh", "sh", "-c", command, NULL);
exit(127);
}
void
mySystem(char *command, int background)
{
#ifndef __MINGW32_VERSION
if (background) {
#ifndef __EMX__
flush_tty();
if (!fork()) {
setup_child(FALSE, 0, -1);
myExec(command);
}
#else
Str cmd = Strnew_charp("start /f ");
Strcat_charp(cmd, command);
system(cmd->ptr);
#endif
}
else
#endif /* __MINGW32_VERSION */
system(command);
}
Str
myExtCommand(char *cmd, char *arg, int redirect)
{
Str tmp = NULL;
char *p;
int set_arg = FALSE;
for (p = cmd; *p; p++) {
if (*p == '%' && *(p + 1) == 's' && !set_arg) {
if (tmp == NULL)
tmp = Strnew_charp_n(cmd, (int)(p - cmd));
Strcat_charp(tmp, arg);
set_arg = TRUE;
p++;
}
else {
if (tmp)
Strcat_char(tmp, *p);
}
}
if (!set_arg) {
if (redirect)
tmp = Strnew_m_charp("(", cmd, ") < ", arg, NULL);
else
tmp = Strnew_m_charp(cmd, " ", arg, NULL);
}
return tmp;
}
Str
myEditor(char *cmd, char *file, int line)
{
Str tmp = NULL;
char *p;
int set_file = FALSE, set_line = FALSE;
for (p = cmd; *p; p++) {
if (*p == '%' && *(p + 1) == 's' && !set_file) {
if (tmp == NULL)
tmp = Strnew_charp_n(cmd, (int)(p - cmd));
Strcat_charp(tmp, file);
set_file = TRUE;
p++;
}
else if (*p == '%' && *(p + 1) == 'd' && !set_line && line > 0) {
if (tmp == NULL)
tmp = Strnew_charp_n(cmd, (int)(p - cmd));
Strcat(tmp, Sprintf("%d", line));
set_line = TRUE;
p++;
}
else {
if (tmp)
Strcat_char(tmp, *p);
}
}
if (!set_file) {
if (tmp == NULL)
tmp = Strnew_charp(cmd);
if (!set_line && line > 1 && strcasestr(cmd, "vi"))
Strcat(tmp, Sprintf(" +%d", line));
Strcat_m_charp(tmp, " ", file, NULL);
}
return tmp;
}
#ifdef __MINGW32_VERSION
char *
expandName(char *name)
{
return getenv("HOME");
}
#else
char *
expandName(char *name)
{
char *p;
struct passwd *passent, *getpwnam(const char *);
Str extpath = NULL;
if (name == NULL)
return NULL;
p = name;
if (*p == '/') {
if ((*(p + 1) == '~' && IS_ALPHA(*(p + 2)))
&& personal_document_root) {
char *q;
p += 2;
q = strchr(p, '/');
if (q) { /* /~user/dir... */
passent = getpwnam(allocStr(p, q - p));
p = q;
}
else { /* /~user */
passent = getpwnam(p);
p = "";
}
if (!passent)
goto rest;
extpath = Strnew_m_charp(passent->pw_dir, "/",
personal_document_root, NULL);
if (*personal_document_root == '\0' && *p == '/')
p++;
}
else
goto rest;
if (Strcmp_charp(extpath, "/") == 0 && *p == '/')
p++;
Strcat_charp(extpath, p);
return extpath->ptr;
}
else
return expandPath(p);
rest:
return name;
}
#endif
int
is_localhost(const char *host)
{
if (!host ||
!strcasecmp(host, "localhost") || !strcmp(host, "127.0.0.1") ||
(HostName && !strcasecmp(host, HostName)) || !strcmp(host, "[::1]"))
return TRUE;
return FALSE;
}
char *
file_to_url(char *file)
{
Str tmp;
#ifdef SUPPORT_DOS_DRIVE_PREFIX
char *drive = NULL;
#endif
#ifdef SUPPORT_NETBIOS_SHARE
char *host = NULL;
#endif
if (!(file = expandPath(file)))
return NULL;
#ifdef SUPPORT_NETBIOS_SHARE
if (file[0] == '/' && file[1] == '/') {
char *p;
file += 2;
if (*file) {
p = strchr(file, '/');
if (p != NULL && p != file) {
host = allocStr(file, (p - file));
file = p;
}
}
}
#endif
#ifdef SUPPORT_DOS_DRIVE_PREFIX
if (IS_ALPHA(file[0]) && file[1] == ':') {
drive = allocStr(file, 2);
file += 2;
}
else
#endif
if (file[0] != '/') {
tmp = Strnew_charp(CurrentDir);
if (Strlastchar(tmp) != '/')
Strcat_char(tmp, '/');
Strcat_charp(tmp, file);
file = tmp->ptr;
}
tmp = Strnew_charp("file://");
#ifdef SUPPORT_NETBIOS_SHARE
if (host)
Strcat_charp(tmp, host);
#endif
#ifdef SUPPORT_DOS_DRIVE_PREFIX
if (drive)
Strcat_charp(tmp, drive);
#endif
Strcat_charp(tmp, file_quote(cleanupName(file)));
return tmp->ptr;
}
#ifdef USE_M17N
char *
url_unquote_conv(char *url, wc_ces charset)
#else
char *
url_unquote_conv0(char *url)
#endif
{
#ifdef USE_M17N
wc_uint8 old_auto_detect = WcOption.auto_detect;
#endif
Str tmp;
tmp = Str_url_unquote(Strnew_charp(url), FALSE, TRUE);
#ifdef USE_M17N
if (!charset || charset == WC_CES_US_ASCII)
charset = SystemCharset;
WcOption.auto_detect = WC_OPT_DETECT_ON;
tmp = convertLine(NULL, tmp, RAW_MODE, &charset, charset);
WcOption.auto_detect = old_auto_detect;
#endif
return tmp->ptr;
}
static char *tmpf_base[MAX_TMPF_TYPE] = {
"tmp", "src", "frame", "cache", "cookie", "hist",
};
static unsigned int tmpf_seq[MAX_TMPF_TYPE];
Str
tmpfname(int type, char *ext)
{
Str tmpf;
char *dir;
switch(type) {
case TMPF_HIST:
dir = rc_dir;
break;
case TMPF_DFL:
case TMPF_COOKIE:
case TMPF_SRC:
case TMPF_FRAME:
case TMPF_CACHE:
default:
dir = tmp_dir;
}
tmpf = Sprintf("%s/w3m%s%d-%d%s",
dir,
tmpf_base[type],
CurrentPid, tmpf_seq[type]++, (ext) ? ext : "");
pushText(fileToDelete, tmpf->ptr);
return tmpf;
}
static char *monthtbl[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static int
get_day(char **s)
{
Str tmp = Strnew();
int day;
char *ss = *s;
if (!**s)
return -1;
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
day = atoi(tmp->ptr);
if (day < 1 || day > 31) {
*s = ss;
return -1;
}
return day;
}
static int
get_month(char **s)
{
Str tmp = Strnew();
int mon;
char *ss = *s;
if (!**s)
return -1;
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
if (tmp->length > 0) {
mon = atoi(tmp->ptr);
}
else {
while (**s && IS_ALPHA(**s))
Strcat_char(tmp, *((*s)++));
for (mon = 1; mon <= 12; mon++) {
if (strncmp(tmp->ptr, monthtbl[mon - 1], 3) == 0)
break;
}
}
if (mon < 1 || mon > 12) {
*s = ss;
return -1;
}
return mon;
}
static int
get_year(char **s)
{
Str tmp = Strnew();
int year;
char *ss = *s;
if (!**s)
return -1;
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
if (tmp->length != 2 && tmp->length != 4) {
*s = ss;
return -1;
}
year = atoi(tmp->ptr);
if (tmp->length == 2) {
if (year >= 70)
year += 1900;
else
year += 2000;
}
return year;
}
static int
get_time(char **s, int *hour, int *min, int *sec)
{
Str tmp = Strnew();
char *ss = *s;
if (!**s)
return -1;
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
if (**s != ':') {
*s = ss;
return -1;
}
*hour = atoi(tmp->ptr);
(*s)++;
Strclear(tmp);
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
if (**s != ':') {
*s = ss;
return -1;
}
*min = atoi(tmp->ptr);
(*s)++;
Strclear(tmp);
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
*sec = atoi(tmp->ptr);
if (*hour < 0 || *hour >= 24 ||
*min < 0 || *min >= 60 || *sec < 0 || *sec >= 60) {
*s = ss;
return -1;
}
return 0;
}
static int
get_zone(char **s, int *z_hour, int *z_min)
{
Str tmp = Strnew();
int zone;
char *ss = *s;
if (!**s)
return -1;
if (**s == '+' || **s == '-')
Strcat_char(tmp, *((*s)++));
while (**s && IS_DIGIT(**s))
Strcat_char(tmp, *((*s)++));
if (!(tmp->length == 4 && IS_DIGIT(*ss)) &&
!(tmp->length == 5 && (*ss == '+' || *ss == '-'))) {
*s = ss;
return -1;
}
zone = atoi(tmp->ptr);
*z_hour = zone / 100;
*z_min = zone - (zone / 100) * 100;
return 0;
}
/* RFC 1123 or RFC 850 or ANSI C asctime() format string -> time_t */
time_t
mymktime(char *timestr)
{
char *s;
int day, mon, year, hour, min, sec, z_hour = 0, z_min = 0;
if (!(timestr && *timestr))
return -1;
s = timestr;
#ifdef DEBUG
fprintf(stderr, "mktime: %s\n", timestr);
#endif /* DEBUG */
while (*s && IS_ALPHA(*s))
s++;
while (*s && !IS_ALNUM(*s))
s++;
if (IS_DIGIT(*s)) {
/* RFC 1123 or RFC 850 format */
if ((day = get_day(&s)) == -1)
return -1;
while (*s && !IS_ALNUM(*s))
s++;
if ((mon = get_month(&s)) == -1)
return -1;
while (*s && !IS_DIGIT(*s))
s++;
if ((year = get_year(&s)) == -1)
return -1;
while (*s && !IS_DIGIT(*s))
s++;
if (!*s) {
hour = 0;
min = 0;
sec = 0;
}
else {
if (get_time(&s, &hour, &min, &sec) == -1)
return -1;
while (*s && !IS_DIGIT(*s) && *s != '+' && *s != '-')
s++;
get_zone(&s, &z_hour, &z_min);
}
}
else {
/* ANSI C asctime() format. */
while (*s && !IS_ALNUM(*s))
s++;
if ((mon = get_month(&s)) == -1)
return -1;
while (*s && !IS_DIGIT(*s))
s++;
if ((day = get_day(&s)) == -1)
return -1;
while (*s && !IS_DIGIT(*s))
s++;
if (get_time(&s, &hour, &min, &sec) == -1)
return -1;
while (*s && !IS_DIGIT(*s))
s++;
if ((year = get_year(&s)) == -1)
return -1;
}
#ifdef DEBUG
fprintf(stderr,
"year=%d month=%d day=%d hour:min:sec=%d:%d:%d zone=%d:%d\n", year,
mon, day, hour, min, sec, z_hour, z_min);
#endif /* DEBUG */
mon -= 3;
if (mon < 0) {
mon += 12;
year--;
}
day += (year - 1968) * 1461 / 4;
day += ((((mon * 153) + 2) / 5) - 672);
hour -= z_hour;
min -= z_min;
return (time_t) ((day * 60 * 60 * 24) +
(hour * 60 * 60) + (min * 60) + sec);
}
#ifdef USE_COOKIE
#ifdef INET6
#include <sys/socket.h>
#endif /* INET6 */
#ifndef __MINGW32_VERSION
#include <netdb.h>
#else
#include <winsock.h>
#endif
char *
FQDN(char *host)
{
char *p;
#ifndef INET6
struct hostent *entry;
#else /* INET6 */
int *af;
#endif /* INET6 */
if (host == NULL)
return NULL;
if (strcasecmp(host, "localhost") == 0)
return host;
for (p = host; *p && *p != '.'; p++) ;
if (*p == '.')
return host;
#ifndef INET6
if (!(entry = gethostbyname(host)))
return NULL;
return allocStr(entry->h_name, -1);
#else /* INET6 */
for (af = ai_family_order_table[DNS_order];; af++) {
int error;
struct addrinfo hints;
struct addrinfo *res, *res0;
char *namebuf;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = *af;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, NULL, &hints, &res0);
if (error) {
if (*af == PF_UNSPEC) {
/* all done */
break;
}
/* try next address family */
continue;
}
for (res = res0; res != NULL; res = res->ai_next) {
if (res->ai_canonname) {
/* found */
namebuf = strdup(res->ai_canonname);
freeaddrinfo(res0);
return namebuf;
}
}
freeaddrinfo(res0);
if (*af == PF_UNSPEC) {
break;
}
}
/* all failed */
return NULL;
#endif /* INET6 */
}
#endif /* USE_COOKIE */
void (*mySignal(int signal_number, void (*action) (int))) (int) {
#ifdef SA_RESTART
struct sigaction new_action, old_action;
sigemptyset(&new_action.sa_mask);
new_action.sa_handler = action;
if (signal_number == SIGALRM) {
#ifdef SA_INTERRUPT
new_action.sa_flags = SA_INTERRUPT;
#else
new_action.sa_flags = 0;
#endif
}
else {
new_action.sa_flags = SA_RESTART;
}
sigaction(signal_number, &new_action, &old_action);
return (old_action.sa_handler);
#else
return (signal(signal_number, action));
#endif
}
static char Base64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
Str
base64_encode(const char *src, size_t len)
{
Str dest;
const unsigned char *in, *endw, *s;
unsigned long j;
size_t k;
s = (unsigned char*)src;
k = len;
if (k % 3)
k += 3 - (k % 3);
k = k / 3 * 4;
if (!len || k + 1 < len)
return Strnew();
dest = Strnew_size(k);
if (dest->area_size <= k) {
Strfree(dest);
return Strnew();
}
in = s;
endw = s + len - 2;
while (in < endw) {
j = *in++;
j = j << 8 | *in++;
j = j << 8 | *in++;
Strcatc(dest, Base64Table[(j >> 18) & 0x3f]);
Strcatc(dest, Base64Table[(j >> 12) & 0x3f]);
Strcatc(dest, Base64Table[(j >> 6) & 0x3f]);
Strcatc(dest, Base64Table[j & 0x3f]);
}
if (s + len - in) {
j = *in++;
if (s + len - in) {
j = j << 8 | *in++;
j = j << 8;
Strcatc(dest, Base64Table[(j >> 18) & 0x3f]);
Strcatc(dest, Base64Table[(j >> 12) & 0x3f]);
Strcatc(dest, Base64Table[(j >> 6) & 0x3f]);
} else {
j = j << 8;
j = j << 8;
Strcatc(dest, Base64Table[(j >> 18) & 0x3f]);
Strcatc(dest, Base64Table[(j >> 12) & 0x3f]);
Strcatc(dest, '=');
}
Strcatc(dest, '=');
}
Strnulterm(dest);
return dest;
}