#include "i.h"
typedef struct NGchanitem NGchanitem;
typedef struct ImageCache ImageCache;
// Item sent allong ngchan
struct NGchanitem {
int req;
ByteSource* bs;
Netconn* nc;
Channel* anschan;
};
// In-memory cache of CImages
struct ImageCache
{
CImage* imhd; // head (LRU) of cache chain (linked through CImage.next)
CImage* imtl; // tail MRU) of cache chain
int n; // size of chain
int memused; // current total of image mem used by cached images
int memlimit; // keep memused less than this
int nlimit; // keep n less than this
};
// Some constants
enum {
NCTimeout = 100000, // free NC slot after 100 seconds
UBufsize = 40960, // initial buffer size for unknown lengths
UEBufsize = 1024, // initial buffer size for unknown lengths, error responses
Maxnthreads = 10, // max allowed config.nthreads
};
Rune crlf[] = L"\r\n";
Rune sptab[] = L" \t";
// globals
int bytesourceid = 0; // for allocating bytesource ids
int netconnid = 0; // for allocating netconn ids
int dbg; // config.dbg['d']
int warn; // dbg || config.dbg['w']
int dbgev; // config.dbg['e']
int dbgproto; // are we debugging netget protocol?
int aborting; // in the process of aborting a get
Netconn** netconns; // array of netconns
int nnetconns; // current number of slots in netconns
Channel* ngchan; // Channel of NGchanitem
Channel* reqanschan; // Channel of ByteSource*
Channel* abortanschan; // Channel of ByteSource*
Config config;
ResourceState startres;
ImageCache imcache;
// Track HTTP methods in i.h
// (upper-case, since that's required in HTTP requests)
Rune* hmeth[] = { L"GET", L"POST" };
// Track media types in charon.h
// keep in alphabetical order
Rune* mnames[] = {
L"application/msword",
L"application/octet-stream",
L"application/pdf",
L"application/postscript",
L"application/rtf",
L"application/vnd.framemaker",
L"application/vnd.ms-excel",
L"application/vnd.ms-powerpoint",
L"application/x-unknown",
L"audio/32kadpcm",
L"audio/basic",
L"image/cgm",
L"image/g3fax",
L"image/gif",
L"image/ief",
L"image/jpeg",
L"image/png",
L"image/tiff",
L"image/x-bit",
L"image/x-bit2",
L"image/x-bitmulti",
L"image/x-xbitmap",
L"model/vrml",
L"multipart/digest",
L"multipart/mixed",
L"text/css",
L"text/enriched",
L"text/html",
L"text/javascript",
L"text/plain",
L"text/richtext",
L"text/sgml",
L"text/tab-separated-values",
L"text/xml",
L"video/mpeg",
L"video/quicktime"
};
// Track Charsets in i.h
//(cf rfc1945 for other names, those we don't bother handling)
Rune* chsetnames[] = {
L"unknown", L"us-ascii", L"iso-8859-1", L"unicode-1-1-utf-8", L"unicode-1-1"
};
// Track Netconn states in i.h
Rune* ncstatenames[] = {
L"free", L"idle", L"connect", L"gethdr", L"getdata",
L"done", L"err"
};
// Track major status values in i.h
Rune* hsnames[] = {
L"none", L"information", L"ok", L"redirect", L"request error", L"server error"
};
// File extension to Media type lookup table
// (keep sorted on file extension)
static StringInt fileexttable[] = {
{L"ai", ApplPostscript},
{L"au", AudioBasic},
{L"bit", ImageXBit},
{L"bit2", ImageXBit2},
{L"bitm", ImageXBitmulti},
{L"eps", ApplPostscript},
{L"gif", ImageGif},
{L"htm", TextHtml},
{L"html", TextHtml},
{L"jpe", ImageJpeg},
{L"jpeg", ImageJpeg},
{L"jpg", ImageJpeg},
{L"pdf", ApplPdf},
{L"ps", ApplPostscript},
{L"text", TextPlain},
{L"tif", ImageTiff},
{L"tiff", ImageTiff},
{L"txt", TextPlain}
};
#define NFILEEXTS (sizeof(fileexttable)/sizeof(StringInt))
// Beginning of a JPEG file
static uchar jpmagic[] = {0xFF, 0xD8, 0xFF, 0xE0,
0, 0, 'J', 'F', 'I', 'F', 0};
// Color lookup table
static StringInt color_tab[] = {
{L"aqua", 0x00FFFF},
{L"black", Black},
{L"blue", Blue},
{L"fuchsia", 0xFF00FF},
{L"gray", 0x808080},
{L"green", 0x008000},
{L"lime", 0x00FF00},
{L"maroon", 0x800000},
{L"navy", Navy},
{L"olive", 0x808000},
{L"purple", 0x800080},
{L"red", Red},
{L"silver", 0xC0C0C0},
{L"teal", 0x008080},
{L"white", White},
{L"yellow", 0xFFFF00}
};
#define NCOLORS (sizeof(color_tab)/sizeof(StringInt))
// track Error codes in i.h
static Rune* errphrases[] = {
L"(no error)",
L"Stopped",
L"Unsupported scheme",
L"Unexpected HTTP answer code",
L"Redirect loop",
L"Can't get file status",
L"Read error",
L"Write error",
L"Unexpected end of file",
L"Couldn't connect",
L"FTP protocol error",
L"FTP login failed",
L"HTTP protocol error",
L"Authorization failed",
L"Unsupported image type",
L"Not enough memory",
L"Image encoding is bad"
};
#define NERRPHRASES (sizeof(errphrases)/sizeof(Rune*))
static StringInt *targetmap;
static int targetmapsize;
static int ntargets;
static void runnetconn(void *arg);
static int strhash(Rune* s);
static Transport* gettransport(int scheme);
static Netconn* newnetconn(int id);
static void makefree(Netconn* nc);
static ByteSource* newbytesource(void);
static void imcacheinit(void);
static void setconfig(int argc, char** argv);
static int setopt(Rune* key, int keylen, Rune* val, int vallen);
static void targetmapinit(void);
static int rnhandler(void* ureg, char* note);
static void sendprocintr(int pid);
static int countrefgo(void);
// Initialize Chutils globals
void
iutilsinit(int argc, char** argv)
{
startres = curresstate();
// Setconfig needs urlinit() to have been done.
// Rest of inits are done after setconfig, in case their
// actions depend on configuration settings.
// guiinit() is done in i.c.
urlinit();
setconfig(argc, argv);
transportinit();
lexinit();
buildinit();
layoutinit();
imginit();
eventinit();
imcacheinit();
targetmapinit();
gcinit();
threadnotify(rnhandler, 1);
aborting = 0;
nnetconns = 10;
netconns = emallocz(sizeof(Netconn)*nnetconns);
dbgproto = config.dbg['p'];
dbg = config.dbg['d'];
warn = config.dbg['w'];
dbgev = config.dbg['e'];
ngchan = chancreate(sizeof(NGchanitem), 0);
reqanschan = chancreate(sizeof(ByteSource*), 0);
abortanschan = chancreate(sizeof(ByteSource*), 0);
if(ngchan == nil || reqanschan == nil || abortanschan == nil)
fatalerror("can't create channels");
}
// Make a ByteSource for given request, and make sure
// that it is on the queue of some Netconn.
// If don't have a transport for the request's scheme,
// the returned bs will have err set.
ByteSource*
startreq(ReqInfo* req)
{
ByteSource* bs;
ByteSource* bsans;
NGchanitem ngi;
bs = newbytesource();
bs->req = req;
bs->refgo = 1;
bs->refnc = 1;
if(config.showprogress)
sendprogress(bs->id, Pstart, 0, req->url->url);
ngi.req = NGstartreq;
ngi.bs = bs;
ngi.nc = nil;
ngi.anschan = reqanschan;
if(send(ngchan, &ngi) < 0)
finish();
if(recv(reqanschan, &bsans) < 0)
finish();
return bs;
}
// Wait for some ByteSource to
// have a state change that go hasn't seen yet.
// There's a race between go doing a freebs
// and netconn returning a statechange;
// fix that race by only returning bs's that
// still have refgo == 1.
ByteSource*
waitreq(void)
{
ByteSource* bs;
NGchanitem ngi;
ngi.req = NGwaitreq;
do {
ngi.bs = nil;
ngi.nc = nil;
ngi.anschan = reqanschan;
if(send(ngchan, &ngi) < 0)
finish();
if(recv(reqanschan, &bs) < 0)
finish();
if(bs == nil)
return nil;
} while(bs->refgo == 0);
return bs;
}
// Notify netget that goproc is finished with bs.
// See waitreq comment about waitfreq/freebs race.
void
freebs(ByteSource* bs)
{
NGchanitem ngi;
bs->refgo = 0;
ngi.req = NGfreebs;
ngi.bs = bs;
ngi.nc = nil;
ngi.anschan = reqanschan;
if(send(ngchan, &ngi) < 0)
finish();
if(recv(reqanschan, &bs) < 0)
finish();
}
// Tell netget to abort all gets.
// This involves sending interrupts to any runnetconns that
// may be stuck in I/O.
// All bs's will then be returned to waitreq with an "aborted"
// error, after which this function can return.
void
abortgo(void)
{
NGchanitem ngi;
if(dbg)
trace("abort go\n");
ngi.req = NGabort;
ngi.bs = nil;
ngi.nc = nil;
ngi.anschan = abortanschan;
if(send(ngchan, &ngi) < 0)
finish();
if(recv(abortanschan, nil) < 0)
finish();
}
int
forkrunnetconn(Netconn *nc, void **rncargs)
{
int id;
assert(nc->rnpid == 0 && nc->rntid == 0);
if(config.nthreads > 0)
id = proccreate(runnetconn, (void*)rncargs, STACKSIZE);
else
id = threadcreate(runnetconn, (void*)rncargs, STACKSIZE);
if(id < 0)
return -1;
nc->rntid = id;
return 0;
}
// This runs as a separate thread.
// It acts as a monitor to synchronize access to the Netconn data
// structures, as a dispatcher to start runnetconn's as needed to
// process work on Netconn queues, and as a notifier to let goproc
// know when any ByteSources have advanced their state.
void
netget(void* arg)
{
int msg;
int n;
int i;
int toldgo;
ByteSource* bs;
ByteSource* bsnil;
Netconn* nc;
Channel* c;
Channel* pendingc;
int waitpending;
int maxconn;
Netconn* x;
int minqlen;
Rune* sport;
int port;
int freen;
int scheme;
Rune* host;
int err;
int totlen;
ByteSource* sendtopending;
NGchanitem ngi;
Transport* tpt;
int gncs[Maxnthreads];
void* rncargs[2];
USED(arg);
waitpending = 0;
maxconn = config.nthreads;
if(maxconn < 1)
maxconn = 1;
if(maxconn > Maxnthreads)
maxconn = Maxnthreads;
for(n = 0; n < nnetconns; n++)
netconns[n] = newnetconn(n);
bsnil = nil;
pendingc = nil;
sendtopending = nil;
toldgo = 0;
while(1) {
mainloop_start:
if(aborting) {
if(toldgo && countrefgo() == 0) {
if(send(abortanschan, nil) < 0)
finish();
aborting = 0;
}
}
if(recv(ngchan, &ngi) < 0)
finish();
msg = ngi.req;
bs = ngi.bs;
nc = ngi.nc;
c = ngi.anschan;
switch(msg) {
case NGstartreq:
// bs has req filled in, and is otherwise in its initial state.
// Find a suitable Netconn and add bs to its queue of work,
// then send nil along c to let goproc continue.
if(dbgproto)
trace("Startreq BS=%d for %U\n", bs->id, bs->req->url);
scheme = bs->req->url->scheme;
host = Strndup(bs->req->url->host, bs->req->url->nhost);
err = 0;
tpt = nil;
if(aborting)
err = ERRaborted;
else if(scheme < 0 || scheme >= TRANSMAX || transports[scheme] == nil)
err = ERRunsupscheme;
else
tpt = transports[scheme];
if(err)
bs->err = err;
else {
sport = Strndup(bs->req->url->port, bs->req->url->nport);
if(sport == nil)
port = (tpt->defaultport)(scheme);
else
port = Strtol(sport, nil, 10);
i = 0;
freen = -1;
for(n = 0; n < nnetconns && (i < maxconn || freen == -1); n++) {
nc = netconns[n];
if(nc->state == NCfree) {
if(freen == -1)
freen = n;
}
else if(!Strcmp(nc->host, host) && nc->port == port
&& nc->scheme == scheme && i < maxconn) {
gncs[i++] = n;
}
}
if(i < maxconn) {
// use a new netconn for this bs
if(freen == -1) {
// need to make netconns bigger
netconns = (Netconn**)erealloc(netconns, (freen + 10) * sizeof(Netconn*));
for(i = nnetconns; i < nnetconns+10; i++)
netconns[i] = newnetconn(i);
freen = nnetconns;
nnetconns += 10;
}
nc = netconns[freen];
nc->rnpid = 0;
nc->rntid = 0;
nc->host = host;
nc->port = port;
nc->scheme = scheme;
nc->qlen = 0;
nc->ngcur = 0;
nc->gocur = 0;
nc->reqsent = 0;
nc->pipeline = 0;
nc->connected = 0;
}
else {
// use existing netconn with fewest outstanding requests
nc = netconns[gncs[0]];
if(maxconn > 1) {
minqlen = nc->qlen - nc->gocur;
for(i = 1; i < maxconn; i++) {
x = netconns[gncs[i]];
if(x->qlen - x->gocur < minqlen) {
nc = x;
minqlen = x->qlen - x->gocur;
}
}
}
}
if(nc->qlen == nc->qalloclen) {
// need to make queue bigger
nc->queue = (ByteSource**)erealloc(nc->queue, (nc->qalloclen + 10) * sizeof(ByteSource*));
nc->qalloclen += 10;
}
nc->queue[nc->qlen++] = bs;
bs->net = nc;
if(dbgproto)
trace("Chose NC=%d for BS %d, qlen=%d\n", nc->id, bs->id, nc->qlen);
if(nc->state == NCfree || nc->state == NCidle) {
if(nc->connected) {
nc->state = NCgethdr;
if(dbgproto)
trace("NC %d: starting runnetconn in gethdr state\n", nc->id);
}
else {
nc->state = NCconnect;
if(dbgproto)
trace("NC %d: starting runnetconn in connect state\n", nc->id);
}
rncargs[0] = nc;
rncargs[1] = tpt;
if(forkrunnetconn(nc, rncargs) < 0){
trace("Can't start runnetconn!\n");
return;
}
}
}
if(send(c, &bsnil) < 0)
finish();
break;
case NGwaitreq:
// goproc wants to be notified when some ByteSource
// changes to a state that the goproc hasn't seen yet.
// Send such a ByteSource along return channel c.
if(dbgproto)
trace("Waitreq\n");
assert(!waitpending);
if(aborting) {
if(dbgproto)
trace("Send <abort> to waitreq\n");
send(c, &bsnil);
toldgo = 1;
goto mainloop_start;
}
for(n = 0; n < nnetconns; n++) {
nc = netconns[n];
if(nc->state == NCfree)
continue;
if(nc->gocur < nc->qlen) {
bs = nc->queue[nc->gocur];
if(!bs->refgo)
continue;
if(bs->err ||
(bs->hdr != nil && !bs->seenhdr) ||
(bs->edata > bs->lim) || (nc->gocur < nc->ngcur)) {
if(dbgproto)
trace("Send BS %d to waitreq\n", bs->id);
if(send(c, &bs) < 0)
finish();
goto mainloop_start;
}
}
}
if(dbgproto)
trace("Waitpending\n");
waitpending = 1;
pendingc = c;
break;
case NGfreebs:
if(dbgproto)
trace("Freebs BS=%d\n", bs->id);
nc = bs->net;
if(bs->refnc == 0) {
if(nc != nil)
nc->queue[nc->gocur] = nil;
}
if(nc != nil) {
// can be nil if no transport was found
nc->gocur++;
if(dbgproto)
trace("NC %d: gocur=%d, ngcur=%d, qlen=%d\n",
nc->id, nc->gocur, nc->ngcur, nc->qlen);
if(nc->gocur == nc->qlen && nc->ngcur == nc->qlen) {
if(!nc->connected)
makefree(nc);
}
}
if(send(c, &bsnil) < 0)
finish();
break;
case NGstatechg:
// Some runnetconn is telling us tht it changed the
// state of nc. Send a nil along c to let it continue.
if(dbgproto)
trace("Statechg NC=%d, state=%S\n", nc->id, ncstatenames[nc->state]);
sendtopending = nil;
if(!aborting && waitpending && nc->gocur < nc->qlen) {
bs = nc->queue[nc->gocur];
if(dbgproto) {
totlen = 0;
if(bs->hdr != nil)
totlen = bs->hdr->length;
trace("BS %d: havehdr=%d seenhdr=%d edata=%d lim=%d, length=%d\n",
bs->id, bs->hdr != nil, bs->seenhdr, bs->edata, bs->lim, totlen);
if(bs->err)
trace(" err=%S\n", errphrase(bs->err));
}
if(bs->refgo &&
(bs->err ||
(bs->hdr != nil && !bs->seenhdr) ||
(bs->edata > bs->lim)))
sendtopending = bs;
}
if(nc->state == NCdone || nc->state == NCerr) {
if(dbgproto)
trace("NC %d: runnetconn finishing\n", nc->id);
if(nc->ngcur >= nc->qlen) {
trace("NC %d has ngcur=%d, qlen=%d\n", nc->id, nc->ngcur, nc->qlen);
}
assert(nc->ngcur < nc->qlen);
bs = nc->queue[nc->ngcur];
bs->refnc = 0;
if(bs->refgo == 0)
nc->queue[nc->ngcur] = nil;
nc->ngcur++;
if(dbgproto)
trace("NC %d: ngcur=%d\n", nc->id, nc->ngcur);
nc->state = NCidle;
nc->rnpid = 0;
nc->rntid = 0;
if(dbgproto)
trace("NC %d: idle\n", nc->id);
if(aborting) {
/* free ByteSources! */
makefree(nc);
}
else if(nc->ngcur < nc->qlen) {
if(nc->connected) {
nc->state = NCgethdr;
if(dbgproto)
trace("NC %d: starting runnetconn in gethdr state\n", nc->id);
}
else {
nc->state = NCconnect;
if(dbgproto)
trace("NC %d: starting runnetconn in connect state\n", nc->id);
}
rncargs[0] = nc;
rncargs[1] = transports[nc->scheme];
if(forkrunnetconn(nc, rncargs) < 0){
trace("Can't start runnetconn!\n");
return;
}
}
else if(!nc->connected)
makefree(nc);
}
if(dbgproto)
trace("Send reply to chan 0x%p\n", c);
send(c, &bsnil);
if(dbgproto)
trace("done sending reply to chan 0x%p\n", c);
if(sendtopending != nil) {
if(dbgproto)
trace("Send BS %d to pending waitreq\n", bs->id);
send(pendingc, &sendtopending);
waitpending = 0;
}
break;
case NGabort:
if(dbgproto)
trace("NGabort received\n");
aborting = 1;
if(waitpending) {
if(dbgproto)
trace("Send <abort> to pending waitreq\n");
send(pendingc, &bsnil);
waitpending = 0;
toldgo = 1;
}
else
toldgo = !countrefgo();
for(n = 0; n < nnetconns; n++) {
nc = netconns[n];
if(nc->rntid != 0) {
if(config.nthreads != 0)
sendprocintr(nc->rnpid);
/* else we can't interrupt */
}
}
break;
}
}
}
// Return number of ByteSources in netget queues
// that have refgo
static int
countrefgo(void)
{
int ans, i, n;
Netconn* nc;
ByteSource* bs;
ans = 0;
for(n = 0; n < nnetconns; n++) {
nc = netconns[n];
if(nc->state != NCfree) {
for(i = 0; i < nc->qlen; i++) {
bs = nc->queue[i];
if(bs == nil)
continue;
if(bs->refgo)
ans++;
}
}
}
return ans;
}
static void
sendprocintr(int pid)
{
int fd;
char buf[100];
if(dbgproto)
trace("proc %d sending rnintr to proc %d\n", getpid(), pid);
sprint(buf, "/proc/%d/note", pid);
fd = open(buf, OWRITE);
if(fd >= 0) {
write(fd, "rnintr", 6);
close(fd);
}
}
static int
rnhandler(void* ureg, char* note)
{
USED(ureg);
if(strncmp(note, "rnintr", 6) == 0) {
if(dbgproto)
trace("proc %d got rnintr\n", getpid());
return 1;
}
return 0;
}
// A separate proc, to handle ngcur request of transport.
// The arguments should be: {Netconn*, Transport*}.
// If config.nthreads == 0, it is run as thread rather than proc
// (for debugging).
static void
runnetconn(void* arg)
{
Netconn* nc;
Transport* tpt;
int totlen;
Channel* ach;
ByteSource* bs;
ByteSource* bsans;
NGchanitem ngi;
void** args;
if(config.nthreads != 0)
meminit(GRnet);
args = (void**)arg;
nc = (Netconn*)args[0];
nc->rnpid = getpid();
tpt = (Transport*)args[1];
ach = chancreate(sizeof(ByteSource*), 0);
assert(nc->ngcur < nc->qlen);
bs = nc->queue[nc->ngcur];
ngi.req = NGstatechg;
ngi.bs = nil;
ngi.nc = nc;
ngi.anschan = ach;
bs->data = nil;
if(nc->state == NCconnect) {
(tpt->connect)(nc, bs);
if(bs->err || aborting)
goto finished;
nc->state = NCgethdr;
}
assert(nc->state == NCgethdr && nc->connected);
if(config.showprogress)
sendprogress(bs->id, Pconnected, 0, nil);
// Write enough requests so that nc->ngcur header
//will be next to be retrieved
while(nc->reqsent < nc->qlen) {
(tpt->writereq)(nc, nc->queue[nc->reqsent]);
if(aborting)
goto finished;
if(nc->queue[nc->reqsent]->err) {
if(nc->reqsent > nc->ngcur) {
nc->queue[nc->reqsent]->err = 0;
break;
}
else
goto finished;
}
nc->reqsent++;
if(nc->reqsent >= nc->ngcur + 1 || !nc->pipeline)
break;
}
assert(nc->reqsent > nc->ngcur);
// Get the header
(tpt->gethdr)(nc, bs);
if(bs->err || aborting)
goto finished;
assert(bs->hdr != nil);
if(config.showprogress)
sendprogress(bs->id, Phavehdr, 0, nil);
totlen = bs->hdr->length;
if(totlen > 0) {
nc->state = NCgetdata;
if(send(ngchan, &ngi) == -1)
goto cleanup;
if(recv(ach, &bsans) == -1)
goto cleanup;
bs->data = (uchar*)emalloc(totlen);
bs->dalloclen = totlen;
while(bs->edata < totlen) {
(tpt->getdata)(nc, bs);
if(bs->err || aborting)
goto finished;
if(config.showprogress)
sendprogress(bs->id, Phavedata, 100*bs->edata/totlen, nil);
if(send(ngchan, &ngi) == -1)
goto cleanup;
if(recv(ach, &bsans) == -1)
goto cleanup;
}
}
else if(totlen == -1) {
// Unknown length.
// To simplify consumer semantics, we want bs->data to
// not change underfoot, so for now, simply accumlate
// everything before telling consumer about a state change.
//
// Report progress percentage based on current totlen (wrong
// of course, but at least shows trend)
if(bs->hdr->code == HCOk || bs->hdr->code == HCOkNonAuthoritative)
totlen = UBufsize;
else
totlen = UEBufsize;
nc->state = NCgetdata;
bs->hdr->length = 100000000; // avoid BS free during following loop
if(send(ngchan, &ngi) == -1)
goto cleanup;
if(recv(ach, &bsans) == -1)
goto cleanup;
bs->data = (uchar*)emalloc(totlen);
bs->dalloclen = totlen;
while(1) {
(tpt->getdata)(nc, bs);
if(aborting)
goto finished;
if(config.showprogress)
sendprogress(bs->id, Phavedata, 100*bs->edata/totlen, nil);
if(bs->err) {
// assume EOF
bs->data = (uchar*)erealloc(bs->data, bs->edata);
bs->dalloclen = bs->edata;
bs->err = 0;
bs->hdr->length = bs->edata;
nc->connected = 0;
break;
}
if(bs->edata == totlen) {
totlen *= 2;
bs->data = (uchar*)erealloc(bs->data, totlen);
bs->dalloclen = totlen;
}
}
}
nc->state = NCdone;
if(config.showprogress)
sendprogress(bs->id, Phavedata, 100, nil);
finished:
if(aborting)
bs->err = ERRaborted;
if(bs->err) {
nc->state = NCerr;
nc->connected = 0;
/*
* If we're aborting, then the event loop is stuck in abortgo
* waiting for us to finish. Don't update the progress meter,
* since that requires having the event loop.
*/
if(!aborting && config.showprogress)
sendprogress(bs->id, Perr, 0, errphrase(bs->err));
}
if(send(ngchan, &ngi) == -1)
goto cleanup;
recv(ach, &bsans);
if(dbgproto)
trace("runnetconn in proc %d exiting\n", getpid());
threadexits("");
cleanup:
if(aborting)
goto finished;
if(dbgproto)
trace("runnetconn in proc %d exiting\n", getpid());
threadexits("");
}
// Allocate a new Netconn, set its id and all other fields to "unused" state
static Netconn*
newnetconn(int id)
{
Netconn* nc;
nc = (Netconn*)emalloc(sizeof(Netconn));
nc->id = id;
nc->qalloclen = 10;
nc->queue = (ByteSource**)emalloc(nc->qalloclen*sizeof(ByteSource*));
makefree(nc);
return nc;
}
// Reset the fields of nc so that it is free to service another connection to anywhere.
static void
makefree(Netconn* nc)
{
if(dbgproto)
trace("NC %d: free\n", nc->id);
nc->state = NCfree;
nc->rnpid = 0;
nc->host = nil;
nc->port = 0;
nc->dfd = -1;
nc->cfd = -1;
nc->qlen = 0;
nc->gocur = 0;
nc->ngcur = 0;
nc->reqsent = 0;
nc->pipeline = 0;
nc->connected = 0;
nc->tstate = 0;
nc->tn1 = 0;
nc->tn2 = 0;
nc->tbuf = nil;
nc->tbuflen = 0;
memset(nc->queue, 0, nc->qalloclen*sizeof(ByteSource*));
}
// Allocate a new ByteSource, and set its fields to initial state.
static ByteSource*
newbytesource(void)
{
ByteSource* bs;
bs = (ByteSource*)emallocz(sizeof(ByteSource));
bs->id = bytesourceid++;
return bs;
}
// Return an ByteSource that is completely filled, from string s.
ByteSource*
newstringbytesource(Rune* s)
{
ByteSource* bs;
Header* hdr;
uchar* a;
int n;
n = Strlen(s);
a = (uchar*)emalloc(n*sizeof(Rune));
memmove(a, s, n*sizeof(Rune));
hdr = newheader();
hdr->code = HCOk;
hdr->length = n;
hdr->mtype = TextHtml;
hdr->chset = Unicode;
bs = newbytesource();
bs->hdr = hdr;
bs->data = a;
bs->dalloclen = n*sizeof(Rune);
bs->edata = bs->dalloclen;
bs->refgo = 1;
bs->seenhdr = 1;
return bs;
}
// Allocate a new ReqInfo structure, fill it in, and return.
ReqInfo*
newreqinfo(ParsedUrl* url, int method, uchar* body, int bodylen, Rune* auth, int target)
{
ReqInfo* r;
r = (ReqInfo*)emalloc(sizeof(ReqInfo));
r->url = url;
r->method = method;
r->body = body;
r->bodylen = bodylen;
r->auth = auth;
r->target = target;
return r;
}
// Allocate a new MaskedImage, with im for the image field.
MaskedImage*
newmaskedimage(Image* im)
{
MaskedImage* m;
m = (MaskedImage*)emallocz(sizeof(MaskedImage));
m->im = im;
return m;
}
// Allocate a new CImage, with the give src, width, and height.
// Start with a reference count of 1.
CImage*
newcimage(ParsedUrl* src, int width, int height)
{
CImage* c;
c = (CImage*)emalloc(sizeof(CImage));
c->src = src;
c->actual = nil;
if(src != nil)
c->imhash = strhash(src->host) + strhash(src->path);
else
c->imhash = 0;
c->width = width;
c->height = height;
c->refcnt = 1;
c->next = nil;
c->mims = nil;
c->mimslen = 0;
return c;
}
// Decrement the refcnt, and free when it reaches 0.
void
freecimage(CImage* c)
{
int i;
MaskedImage* m;
if(--c->refcnt <= 0) {
if(c->mims != nil) {
for(i = 0; i < c->mimslen; i++) {
m = c->mims[i];
if(m->im != nil)
freeimage(m->im);
if(m->mask != nil)
freeimage(m->mask);
}
}
}
}
// Return true if Cimages a and b represent the same image.
// As well as matching the src urls, the specified widths and heights must match too.
// (Widths and heights are specified if at least one of those is not zero.)
int
cimagematch(CImage* a, CImage* b)
{
if(a->imhash == b->imhash) {
if(urlequal(a->src, b->src)) {
return (a->width == 0 || b->width == 0 || a->width == b->width) &&
(a->height == 0 || b->height == 0 || a->height == b->height);
// (above is not quite enough: should also check that don't have
// situation where one has width set, not height, and the other has reverse,
// but it is unusual for an image to have a spec in only one dimension anyway)
}
}
return 0;
}
// Return approximate number of bytes due to Images in ci
int
cimagebytes(CImage* ci)
{
int tot, i;
MaskedImage* mim;
Image* dim;
tot = 0;
for(i = 0; i < ci->mimslen; i++) {
mim = ci->mims[i];
dim = mim->im;
if(dim != nil)
tot += ((dim->r.max.x-dim->r.min.x)>>(3-log2[dim->depth])) *
(dim->r.max.y-dim->r.min.y);
dim = mim->mask;
if(dim != nil)
tot += ((dim->r.max.x-dim->r.min.x)>>(3-log2[dim->depth])) *
(dim->r.max.y-dim->r.min.y);
}
return tot;
}
// Call this after initial windows have been made,
// so that resetlimits() will exclude the images for those
// windows from the available memory.
static void
imcacheinit(void)
{
memset(&imcache, 0, sizeof(imcache));
imcacheresetlimits();
}
// Call resetlimits when amount of non-image-cache image
// memory might have changed significantly (e.g., on main window resize).
void
imcacheresetlimits(void)
{
int avail;
ResourceState rs;
rs = curresstate();
avail = rs.memavail;
avail = 6*avail/10; // allow 40% slop for nonimage data and other applications
imcache.memlimit = config.imagecachemem;
if(imcache.memlimit > avail)
imcache.memlimit = avail;
imcache.nlimit = config.imagecachenum;
imcacheneed(0); // if resized, perhaps need to shed some images
}
// Look for a CImage matching ci, and if found, move it
// to the tail position (i.e., MRU)
CImage*
imcachelook(CImage* ci)
{
CImage* ans;
CImage* prev;
CImage* i;
ans = nil;
prev = nil;
for(i = imcache.imhd; i != nil; i = i->next) {
if(cimagematch(i, ci)) {
if(imcache.imtl != i) {
if(prev != nil)
prev->next = i->next;
else
imcache.imhd = i->next;
i->next = nil;
imcache.imtl->next = i;
imcache.imtl = i;
}
ans = i;
break;
}
prev = i;
}
return ans;
}
// Call this to add ci as MRU of cache chain (should only call if
// it is known that a ci with same image isn't already there).
// Update imcache.memused.
// Assume imcacheneed has been called to ensure that neither
// memlimit nor nlimit will be exceeded.
void
imcacheadd(CImage* ci)
{
ci->next = nil;
if(imcache.imhd == nil)
imcache.imhd = ci;
else
imcache.imtl->next = ci;
imcache.imtl = ci;
imcache.memused += cimagebytes(ci);
imcache.n++;
}
// Delete least-recently-used image in image cache
// and update memused and n.
void
imcachedeletelru(void)
{
CImage* ci;
ci = imcache.imhd;
if(ci != nil) {
imcache.imhd = ci->next;
if(imcache.imhd == nil) {
imcache.imtl = nil;
imcache.memused = 0;
}
else
imcache.memused -= cimagebytes(ci);
freecimage(ci);
imcache.n--;
}
}
void
imcacheclear(void)
{
while(imcache.imhd != nil)
imcachedeletelru();
}
int
imcacheneed(int nbytes)
{
while(imcache.n >=imcache.nlimit || imcache.memused + nbytes > imcache.memlimit) {
if(imcache.imhd == nil)
return 0;
imcachedeletelru();
}
return 1;
}
// Return new Header with default values for fields
Header*
newheader(void)
{
Header* h;
h = (Header*)emalloc(sizeof(Header));
h->code = HCOk;
h->actual = nil;
h->base = nil;
h->location = nil;
h->length = -1;
h->mtype = UnknownType;
h->chset = UnknownCharset;
h->msg = nil;
h->refresh = nil;
h->chal = nil;
h->warn = nil;
return h;
}
// Set the mtype (and possibly chset) fields of h based on (in order):
// first bytes of file, if unambigous
// file name extension
// first bytes of file, even if ambigous (guess)
// if all else fails, then leave as UnknownType.
// If it's a text type, also set the chset.
// (HTTP Transport will try to use Content-Type first, and call this if that
// doesn't work; other Transports will have to rely on this "guessing" function.)
void
setmediatype(Header* h, Rune* name, uchar* first, int firstlen)
{
int n;
int mt;
int i;
Rune* s;
int val;
Rune* ext;
Rune* file;
uchar b;
int nctl;
int ntophalf;
n = firstlen;
mt = UnknownType;
for(i = 0; i < n; i++)
if(!isspace(first[i]))
break;
if(n - i >= 6) {
s = toStr(first, 6, US_Ascii);
if(!Strcmp(s, L"<html>") || !Strcmp(s, L"<head>") || !Strcmp(s, L"<title")) {
h->mtype = TextHtml;
}
else if(!Strcmp(s, L"<!doct")) {
mt = TextHtml;
}
else if(!Strcmp(s, L"gif87a") || !Strcmp(s, L"gif89a")) {
if(i == 0)
mt = ImageGif;
}
else if(!Strcmp(s, L"#defin")) {
h->mtype = ImageXXBitmap;
}
if(h->mtype == UnknownType) {
if(i == 0 && n >= sizeof(jpmagic)) {
for(; i < sizeof(jpmagic); i++)
if(jpmagic[i] > (uchar)0 && first[i] != jpmagic[i])
break;
if(i == sizeof(jpmagic))
mt = ImageJpeg;
}
}
}
if(mt == UnknownType && name != nil) {
// Try file name extension
file = Strrclass(name, L"/");
if(file == nil)
file = name;
else
file++;
if(*file != 0) {
ext = Strrclass(file, L".");
if(ext != nil) {
ext++;
if(lookup(fileexttable, NFILEEXTS, ext, Strlen(ext), &val))
mt = val;
}
}
}
if(mt == UnknownType || (mt >= TextHtml && mt <= TextSgml)) {
// Try file statistics
nctl = 0;
ntophalf = 0;
if(n > 1024)
n = 1024;
for(i = 0; i < n; i++) {
b = first[i];
if(iscntrl(b))
nctl++;
if(b >= 128)
ntophalf++;
}
if(nctl < n/100 && ntophalf < n/10) {
if(mt == UnknownType)
mt = TextPlain;
if(ntophalf == 0)
h->chset = US_Ascii;
else
h->chset = ISO_8859_1;
}
else if(mt == UnknownType) {
if(n == 0) {
mt = TextHtml;
h->chset = ISO_8859_1;
}
else
mt = ApplOctets;
}
else
h->chset = ISO_8859_1;
}
h->mtype = mt;
}
void
printheader(Header* h)
{
Rune* mtype;
Rune* chset;
mtype = L"?";
if(h->mtype >= 0 && h->mtype < NMEDIATYPES)
mtype = mnames[h->mtype];
chset = L"?";
if(h->chset >= 0 && h->chset < NCHARSETS)
chset = chsetnames[h->chset];
trace("code=%d (%S) length=%d mtype=%S chset=%S\n",
h->code, hcphrase(h->code), h->length, mtype, chset);
if(h->base != nil)
trace(" base=%U\n", h->base);
if(h->location != nil)
trace(" location=%U\n", h->location);
if(h->refresh != nil)
trace(" refresh=%S\n", h->refresh);
if(h->chal != nil)
trace(" chal=%S\n", h->chal);
if(h->warn != nil)
trace(" warn=%S\n", h->warn);
}
#define PAGESIZE 4096
ResourceState
curresstate(void)
{
int n;
static int fd = -1;
char* p;
ResourceState r;
uchar buf[SMALLBUFSIZE];
r.ms = (int)cputime();
r.mem = 1*1024*1024;
r.memavail = 10*1024*1024;
if(fd < 0)
fd = open("/dev/swap", OREAD);
if(fd >= 0) {
seek(fd, 0, 0);
n = read(fd, buf, (sizeof buf) - 1);
if(n > 3) {
buf[n] = 0;
r.mem = strtol((char*)buf, &p, 10) * PAGESIZE;
if(p != nil && *p == '/') {
r.memavail = strtol((char*)(p+1), &p, 10) * PAGESIZE;
}
}
}
return r;
}
ResourceState
resstatesince(ResourceState rnew, ResourceState rold)
{
ResourceState r;
r.ms = rnew.ms - rold.ms;
r.mem = rnew.mem - rold.mem;
r.memavail = rnew.memavail - rold.memavail;
return r;
}
void
resstateprint(ResourceState r, char* msg)
{
int d, dms;
d = r.ms/1000;
dms = r.ms % 1000;
trace("%s:\n\ttime: %d.%#.3ds; memory: %dK, available memory %dK\n",
msg, d, dms, r.mem/1024, r.memavail/1024);
}
// Decide what to do based on Header and whether this is
// for the main entity or not, and the number of redirections-so-far.
// Return value is "use"; other return values put in *perror, *pchallenge, and *predir.
// Action to do is:
// If use==1, use the entity else drain its byte source.
// If *perror != nil, mesg was put in progress bar
// If (pchallenge != nil, get auth info and make new request with auth
// Else if *predir != nil, make a new request with redir for url
//
// (if challenge or redir is non-nil, use will be 0)
int
hdraction(ByteSource* bs, int ismain, int nredirs,
int* perror, Rune** pchallenge, ParsedUrl** predir)
{
int use;
int error;
Rune* challenge;
ParsedUrl* redir;
Header* h;
int code;
use = 1;
error = 0;
challenge = nil;
redir = nil;
h = bs->hdr;
assert(h != nil);
bs->seenhdr = 1;
code = h->code;
switch(code/100) {
case HSOk:
if(code != HCOk)
error = ERRunexphscode;
break;
case HSRedirect:
if(h->location != nil) {
redir = h->location;
if(redir->scheme == NOSCHEME)
redir = makeabsoluteurl(redir, h->base);
if(dbg)
trace("redirect %U to %U\n", h->actual, redir);
if(nredirs >= Maxredir) {
redir = nil;
error = ERRredirloop;
}
else
use = 0;
}
break;
case HSError:
if(code == HCUnauthorized && h->chal != nil) {
challenge = h->chal;
use = 0;
}
else {
error = code;
use = ismain;
}
break;
case HSServererr:
error = code;
use = ismain;
break;
default:
error = ERRunexphscode;
use = 0;
break;
}
if(error && config.showprogress)
sendprogress(bs->id, Perr, 0, errphrase(error));
*perror = error;
*pchallenge = challenge;
*predir = redir;
return use;
}
void
sendprogress(int bsid, int state, int pcnt, Rune* s)
{
EV prog;
prog.tag = EVprogresstag;
prog.genframeid = -1; // TODO: pass in correct frameid
prog.u.progress.bsid = bsid;
prog.u.progress.state = state;
prog.u.progress.pcnt = pcnt;
prog.u.progress.s = s;
send(evchan, &prog);
}
// Read a line up to and including cr/lf (be tolerant and allowing missing cr).
// Look first in buf[*pbstart:*pbend], and if that isn't sufficient to get whole line,
// refill buf from fd as needed.
// Since most of the time the needed line will be in the passed buffer, we sometimes
// return pointers into that buffer. We only allocated the answer if the line straddles
// buffer reads.
//
// Return values:
// getline returns 1 if got eof, else 0.
// *pans points to line, *panslen points to its length (not including cr/lf).
// *pbstart and *pbend are updated to new valid portion of buf (after cr/lf).
int
getline(int fd, uchar* buf, int bufsize, int* pbstart, int* pbend,
uchar** pans, int* panslen)
{
int i, k, eof, bstart, bend;
int anslen, lastlen;
uchar* ans;
uchar* last;
ans = nil;
anslen = 0;
eof = 0;
bstart = *pbstart;
bend = *pbend;
while(1) {
for(i = bstart; i < bend; i++) {
if(buf[i] == '\n') {
k = i;
if(k > bstart && buf[k - 1] == '\r')
k--;
last = buf+bstart;
lastlen = k-bstart;
bstart = i + 1;
goto mainloop_done;
}
}
k = bend-bstart;
if(k > 0) {
if(anslen > 0)
ans = (uchar*)erealloc(ans, anslen+k);
else
ans = (uchar*)emalloc(k);
memmove(ans+anslen, buf+bstart, k);
anslen += k;
}
last = nil;
lastlen = 0;
bstart = 0;
bend = read(fd, buf, bufsize);
if(bend <= 0) {
eof = 1;
bend = 0;
break;
}
}
mainloop_done:
if(anslen == 0) {
ans = last;
anslen = lastlen;
}
else {
if(lastlen > 0) {
ans = (uchar*)erealloc(ans, anslen+lastlen);
memmove(ans+anslen, last, lastlen);
anslen += lastlen;
}
}
*pans = ans;
*panslen = anslen;
*pbstart = bstart;
*pbend = bend;
//trace("*pans=%d, *panslen=%d, *pbstart=%d, *pbend=%d, eof=%d\n", ans, anslen, bstart, bend, eof);
//if(anslen > 0) { write(1, ans, anslen); trace("\n"); }
return eof;
}
// Look (linearly) through a for s[0:slen] (case insensitive); return its index if found, else -1.
int
Strlookup(Rune** a, int n, Rune* s, int slen)
{
int i;
for(i = 0; i < n; i++)
if(!Strncmpci(s, slen, a[i]))
return i;
return -1;
}
// Return string representation of HTTP code.
// Note: returned values are constants, and should not be freed.
Rune*
hcphrase(int code)
{
Rune* ans;
switch(code) {
case HCContinue:
ans = L"Continue";
break;
case HCSwitchProto:
ans = L"Switching Protcols";
break;
case HCOk:
ans = L"Ok";
break;
case HCCreated:
ans = L"Created";
break;
case HCAccepted:
ans = L"Accepted";
break;
case HCOkNonAuthoritative:
ans = L"Non-Authoratative Information";
break;
case HCNoContent:
ans = L"No content";
break;
case HCResetContent:
ans = L"Reset content";
break;
case HCPartialContent:
ans = L"Partial content";
break;
case HCMultipleChoices:
ans = L"Multiple choices";
break;
case HCMovedPerm:
ans = L"Moved permanently";
break;
case HCMovedTemp:
ans = L"Moved temporarily";
break;
case HCSeeOther:
ans = L"See other";
break;
case HCNotModified:
ans = L"Not modified";
break;
case HCUseProxy:
ans = L"Use proxy";
break;
case HCBadRequest:
ans = L"Bad request";
break;
case HCUnauthorized:
ans = L"Unauthorized";
break;
case HCPaymentRequired:
ans = L"Payment required";
break;
case HCForbidden:
ans = L"Forbidden";
break;
case HCNotFound:
ans = L"Not found";
break;
case HCMethodNotAllowed:
ans = L"Method not allowed";
break;
case HCNotAcceptable:
ans = L"Not Acceptable";
break;
case HCProxyAuthRequired:
ans = L"Proxy authentication required";
break;
case HCRequestTimeout:
ans = L"Request timed-out";
break;
case HCConflict:
ans = L"Conflict";
break;
case HCGone:
ans = L"Gone";
break;
case HCLengthRequired:
ans = L"Length required";
break;
case HCPreconditionFailed:
ans = L"Precondition failed";
break;
case HCRequestTooLarge:
ans = L"Request entity too large";
break;
case HCRequestURITooLarge:
ans = L"Request-URI too large";
break;
case HCUnsupportedMedia:
ans = L"Unsupported media type";
break;
case HCRangeInvalid:
ans = L"Requested range not valid";
break;
case HCExpectFailed:
ans = L"Expectation failed";
break;
case HCServerError:
ans = L"Internal server error";
break;
case HCNotImplemented:
ans = L"Not implemented";
break;
case HCBadGateway:
ans = L"Bad gateway";
break;
case HCServiceUnavailable:
ans = L"Service unavailable";
break;
case HCGatewayTimeout:
ans = L"Gateway time-out";
break;
case HCVersionUnsupported:
ans = L"HTTP version not supported";
break;
case HCRedirectionFailed:
ans = L"Redirection failed";
break;
default:
ans = L"Unknown code";
break;
}
return ans;
}
// Error codes can be either one of the ERR... values or one of the HC... values
Rune*
errphrase(int code)
{
if(code >=0 && code < NERRPHRASES)
return errphrases[code];
return hcphrase(code);
}
// Make a StringInt table out of a[0:n], mapping each string
// to its index. Check that entries are in alphabetical order.
// These tables use the real malloc(), and are never freed.
StringInt*
makestrinttab(Rune** a, int n)
{
StringInt* ans;
int i;
ans = (StringInt*)malloc(n * sizeof(StringInt));
for(i = 0; i < n; i++) {
ans[i].key = a[i];
ans[i].val = i;
assert(i == 0 || Strcmp(a[i], a[i - 1]) >= 0);
}
return ans;
}
// Use targetmap array to keep track of name <-> targetid mapping.
// Use real malloc(), and never free
static void
targetmapinit(void)
{
targetmapsize = 10;
targetmap = (StringInt*)malloc(targetmapsize*sizeof(StringInt));
memset(targetmap, 0, targetmapsize*sizeof(StringInt));
targetmap[0].key = L"_top";
targetmap[0].val = FTtop;
targetmap[1].key = L"_self";
targetmap[1].val = FTself;
targetmap[2].key = L"_parent";
targetmap[2].val = FTparent;
targetmap[3].key = L"_blank";
targetmap[3].val = FTblank;
ntargets = 4;
}
int
targetid(Rune* s)
{
int i;
int n;
n = Strlen(s);
if(n == 0)
return FTself;
for(i = 0; i < ntargets; i++)
if(Strcmp(s, targetmap[i].key) == 0)
return targetmap[i].val;
if(i >= targetmapsize) {
targetmapsize += 10;
targetmap = (StringInt*)realloc(targetmap, targetmapsize*sizeof(StringInt));
}
targetmap[i].key = (Rune*)malloc((n+1)*sizeof(Rune));
memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
targetmap[i].val = i;
ntargets++;
return i;
}
Rune*
targetname(int targid)
{
int i;
for(i = 0; i < ntargets; i++)
if(targetmap[i].val == targid)
return targetmap[i].key;
return L"?";
}
// Use logtime when only care about time stamps
void
logtime(char* msg, int data)
{
trace("%s: %d %d\n", msg, (int)cputime() - startres.ms, data);
}
// Convert HTML color spec to RGB value, returning dflt if can't.
// Argument is supposed to be a valid HTML color, or "".
// Return the RGB value of the color, using dflt if s
// is nil or an invalid color.
int
color(Rune* s, int dflt)
{
int v;
Rune* rest;
if(s == nil)
return dflt;
if(lookup(color_tab, NCOLORS, s, Strlen(s), &v))
return v;
if(s[0] == '#')
s++;
v = Strtol(s, &rest, 16);
if(*rest == 0)
return v;
return dflt;
}
static int
strhash(Rune* s)
{
int hash;
enum { prime = 8388617 };
hash = 0;
if(s != nil) {
while(*s != 0)
hash = ((hash%prime) << 7) + *s++;
}
return hash;
}
// Set up config global to defaults, then try to read user-specifiic
// config data from /usr/<username>/lib/iconfig, then try to
// override from command line arguments.
static void
setconfig(int argc, char** argv)
{
Rune* user;
int fd;
int n;
Rune* line;
int linelen;
Rune* key;
int keylen;
Rune* val;
int vallen;
int i;
int j;
uchar* aline;
int alinelen;
int eof;
int cfgio;
Rune* a;
Rune* b;
char* s;
uchar buf[BIGBUFSIZE];
config.userdir = Strdup(L"/");
config.starturl = makeurl(L"file:/lib/i/start.html", 0);
config.homeurl = copyurl(config.starturl);
config.httpproxy = nil;
config.defaultwidth = 800;
config.defaultheight = 800;
config.x = 0;
config.y = 0;
config.nocache = 0;
config.maxstale = 0;
config.imagelvl = ImgFull;
config.imagecachenum = 60;
config.imagecachemem = 100000000;
config.docookies = 0;
config.doscripts = 0;
config.saveauthinfo = 0;
config.showprogress = 1;
config.usecci = 0;
config.httpminor = 0;
config.agentname = Strdup(L"i/2.0 (Plan 9)");
config.nthreads = 1;
config.dbgfile = nil;
user = nil;
fd = open("/dev/user", OREAD);
if(fd != -1) {
n = read(fd, buf, sizeof(buf));
if(n > 0)
user = toStr(buf, n, UTF_8);
close(fd);
}
if(user != nil) {
config.userdir = Strdup3(L"/usr/", user, L"/lib");
snprint((char*)buf, sizeof(buf), "%S/iconfig", config.userdir);
cfgio = open((char*)buf, OREAD);
if(cfgio != -1) {
i = 0;
j = 0;
while(1) {
eof = getline(cfgio, buf, sizeof(buf), &i, &j, &aline, &alinelen);
if(eof)
break;
if(alinelen == 0)
continue;
line = toStr(aline, alinelen, UTF_8);
linelen = Strlen(line);
if(alinelen > 0 && line[0] != '#') {
splitl(line, linelen, L" \t=", &key, &keylen, &val, &vallen);
if(keylen != 0) {
if(vallen != 0) {
a = Strnclass(val, L"^ \t=", vallen);
if(a == nil)
vallen = 0;
else {
vallen -= (a-val);
val = a;
a = Strnclass(val, L"#\r\n", vallen);
if(a != nil)
vallen = a-val;
}
}
if(!setopt(key, keylen, val, vallen))
trace("couldn't set option from line '%S'\n", line);
}
}
}
close(cfgio);
}
}
argc--;
argv++;
while(argc > 0) {
s = argv[0];
if(*s == 0)
continue;
else if(s[0] == '-') {
a = toStr((uchar*)(s+1), strlen(s+1), UTF_8);
b = nil;
if(argc > 1) {
b = toStr((uchar*)argv[1], strlen(argv[1]), UTF_8);
if(b[0] != '-') {
argc--;
argv++;
}
}
if(!setopt(a, Strlen(a), b, Strlen(b)))
trace("couldn't set option from arg '%s'\n", s);
}
else {
if(argc == 1) {
a = toStr((uchar*)s, strlen(s), UTF_8);
if(!setopt(L"starturl", 8, a, Strlen(a)))
trace("couldn't set starturl from arg '%s'\n", s);
}
else
trace("ignoring unknown option %s\n", s);
}
argc--;
argv++;
}
}
static int
setopt(Rune* key, int keylen, Rune* val, int vallen)
{
int ok;
int v;
int c;
int i;
Rune* s;
ok = 1;
if(vallen == 0) {
s = nil;
v = 1;
}
else {
s = Strndup(val, vallen);
v = Strtol(s, nil, 0);
}
if(!Strncmpci(key, keylen, L"userdir")) {
config.userdir = Strdup(s);
}
else if(!Strncmpci(key, keylen, L"starturl")) {
if(vallen != 0) {
config.starturl = makeurl(s, 1);
assert(config.starturl != nil);
}
else
ok = 0;
}
else if(!Strncmpci(key, keylen, L"homeurl")) {
if(vallen != 0) {
config.homeurl = makeurl(s, 1);
}
else
ok = 0;
}
else if(!Strncmpci(key, keylen, L"httpproxy")) {
if(vallen != 0) {
config.httpproxy = makeurl(s, 1);
}
else
config.httpproxy = nil;
}
else if(!Strncmpci(key, keylen, L"defaultwidth") || !Strncmpci(key, keylen, L"width")) {
if(v > 200)
config.defaultwidth = v;
else
ok = 0;
}
else if(!Strncmpci(key, keylen, L"defaultheight") || !Strncmpci(key, keylen, L"height")) {
if(v > 100)
config.defaultheight = v;
else
ok = 0;
}
else if(!Strncmpci(key, keylen, L"x")) {
config.x = v;
}
else if(!Strncmpci(key, keylen, L"y")) {
config.y = v;
}
else if(!Strncmpci(key, keylen, L"nocache")) {
config.nocache = v;
}
else if(!Strncmpci(key, keylen, L"maxstale")) {
config.maxstale = v;
}
else if(!Strncmpci(key, keylen, L"imagelvl")) {
config.imagelvl = v;
}
else if(!Strncmpci(key, keylen, L"imagecachenum")) {
config.imagecachenum = v;
}
else if(!Strncmpci(key, keylen, L"imagecachemem")) {
config.imagecachemem = v;
}
else if(!Strncmpci(key, keylen, L"docookies")) {
config.docookies = v;
}
else if(!Strncmpci(key, keylen, L"doscripts")) {
config.doscripts = v;
}
else if(!Strncmpci(key, keylen, L"saveauthinfo")) {
config.saveauthinfo = v;
}
else if(!Strncmpci(key, keylen, L"showprogress")) {
config.showprogress = v;
}
else if(!Strncmpci(key, keylen, L"usecci")){
config.usecci = v;
}
else if(!Strncmpci(key, keylen, L"http")) {
if(!Strncmpci(val, vallen, L"1.1"))
config.httpminor = 1;
else
config.httpminor = 0;
}
else if(!Strncmpci(key, keylen, L"agentname")) {
config.agentname = Strdup(s);
}
else if(!Strncmpci(key, keylen, L"nthreads")) {
config.nthreads = v;
}
else if(!Strncmpci(key, keylen, L"dbgfile")) {
config.dbgfile = Strdup(s);
}
else if(!Strncmpci(key, keylen, L"dbg")) {
for(i = 0; i < vallen; i++) {
c = val[i];
if(c < sizeof(config.dbg))
config.dbg[c]++;
else {
ok = 0;
break;
}
}
}
else
ok = 0;
return ok;
}
|