summaryrefslogtreecommitdiff
path: root/appl/charon/http.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/charon/http.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/charon/http.b')
-rw-r--r--appl/charon/http.b1040
1 files changed, 1040 insertions, 0 deletions
diff --git a/appl/charon/http.b b/appl/charon/http.b
new file mode 100644
index 00000000..27c7d695
--- /dev/null
+++ b/appl/charon/http.b
@@ -0,0 +1,1040 @@
+implement Transport;
+
+include "common.m";
+include "transport.m";
+include "date.m";
+
+#D: Date;
+# sslhs: SSLHS;
+ssl3: SSL3;
+Context: import ssl3;
+# Inferno supported cipher suites:
+ssl_suites := array [] of {
+ byte 0, byte 16r03, # RSA_EXPORT_WITH_RC4_40_MD5
+ byte 0, byte 16r04, # RSA_WITH_RC4_128_MD5
+ byte 0, byte 16r05, # RSA_WITH_RC4_128_SHA
+ byte 0, byte 16r06, # RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ byte 0, byte 16r07, # RSA_WITH_IDEA_CBC_SHA
+ byte 0, byte 16r08, # RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r09, # RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r0A, # RSA_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r0B, # DH_DSS_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r0C, # DH_DSS_WITH_DES_CBC_SHA
+ byte 0, byte 16r0D, # DH_DSS_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r0E, # DH_RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r0F, # DH_RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r10, # DH_RSA_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r11, # DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r12, # DHE_DSS_WITH_DES_CBC_SHA
+ byte 0, byte 16r13, # DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ byte 0, byte 16r14, # DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r15, # DHE_RSA_WITH_DES_CBC_SHA
+ byte 0, byte 16r16, # DHE_RSA_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r17, # DH_anon_EXPORT_WITH_RC4_40_MD5
+ byte 0, byte 16r18, # DH_anon_WITH_RC4_128_MD5
+ byte 0, byte 16r19, # DH_anon_EXPORT_WITH_DES40_CBC_SHA
+ byte 0, byte 16r1A, # DH_anon_WITH_DES_CBC_SHA
+ byte 0, byte 16r1B, # DH_anon_WITH_3DES_EDE_CBC_SHA
+
+ byte 0, byte 16r1C, # FORTEZZA_KEA_WITH_NULL_SHA
+ byte 0, byte 16r1D, # FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA
+ byte 0, byte 16r1E, # FORTEZZA_KEA_WITH_RC4_128_SHA
+};
+
+ssl_comprs := array [] of {byte 0};
+
+# local copies from CU
+sys: Sys;
+U: Url;
+ Parsedurl: import U;
+S: String;
+C: Ctype;
+T: StringIntTab;
+CU: CharonUtils;
+ Netconn, ByteSource, Header, config, Nameval : import CU;
+
+ctype: array of byte; # local copy of C->ctype
+
+HTTPD: con 80; # Default IP port
+HTTPSD: con 443; # Default IP port for HTTPS
+
+# For Inferno, won't be able to read more than this at one go anyway
+BLOCKSIZE: con 1460;
+
+# HTTP/1.1 Spec says 5, but we've seen more than that in non-looping redirs
+# MAXREDIR: con 10;
+
+# tstate bits
+THTTP_1_0, TPersist, TProxy, TSSL: con (1<<iota);
+
+# Header fields (in order: general, request, response, entity)
+HCacheControl, HConnection, HDate, HPragma, HTransferEncoding,
+ HUpgrade, HVia,
+ HKeepAlive, # extension
+HAccept, HAcceptCharset, HAcceptEncoding, HAcceptLanguage,
+ HAuthorization, HExpect, HFrom, HHost, HIfModifiedSince,
+ HIfMatch, HIfNoneMatch, HIfRange, HIfUnmodifiedSince,
+ HMaxForwards, HProxyAuthorization, HRange, HReferer,
+ HUserAgent,
+ HCookie, # extension
+HAcceptRanges, HAge, HLocation, HProxyAuthenticate, HPublic,
+ HRetryAfter, HServer, HSetProxy, HVary, HWarning,
+ HWWWAuthenticate,
+ HContentDisposition, HSetCookie, HRefresh, # extensions
+ HWindowTarget, HPICSLabel, # more extensions
+HAllow, HContentBase, HContentEncoding, HContentLanguage,
+ HContentLength, HContentLocation, HContentMD5,
+ HContentRange, HContentType, HETag, HExpires,
+ HLastModified,
+ HXReqTime, HXRespTime, HXUrl, # our extensions, for cached entities
+ NumHfields: con iota;
+
+# (track above enumeration)
+hdrnames := array[] of {
+ "Cache-Control",
+ "Connection",
+ "Date",
+ "Pragma",
+ "Transfer-Encoding",
+ "Upgrade",
+ "Via",
+ "Keep-Alive",
+ "Accept",
+ "Accept-Charset",
+ "Accept-Encoding",
+ "Accept-Language",
+ "Authorization",
+ "Expect",
+ "From",
+ "Host",
+ "If-Modified-Since",
+ "If-Match",
+ "If-None-Match",
+ "If-Range",
+ "If-Unmodified-Since",
+ "Max-Forwards",
+ "Proxy-Authorization",
+ "Range",
+ "Refererer",
+ "User-Agent",
+ "Cookie",
+ "Accept-Ranges",
+ "Age",
+ "Location",
+ "Proxy-Authenticate",
+ "Public",
+ "Retry-After",
+ "Server",
+ "Set-Proxy",
+ "Vary",
+ "Warning",
+ "WWW-Authenticate",
+ "Content-Disposition",
+ "Set-Cookie",
+ "Refresh",
+ "Window-Target",
+ "PICS-Label",
+ "Allow",
+ "Content-Base",
+ "Content-Encoding",
+ "Content-Language",
+ "Content-Length",
+ "Content-Location",
+ "Content-MD5",
+ "Content-Range",
+ "Content-Type",
+ "ETag",
+ "Expires",
+ "Last-Modified",
+ "X-Req-Time",
+ "X-Resp-Time",
+ "X-Url"
+};
+
+# For fast lookup; track above, and keep sorted and lowercase
+hdrtable := array[] of { T->StringInt
+ ("accept", HAccept),
+ ("accept-charset", HAcceptCharset),
+ ("accept-encoding", HAcceptEncoding),
+ ("accept-language", HAcceptLanguage),
+ ("accept-ranges", HAcceptRanges),
+ ("age", HAge),
+ ("allow", HAllow),
+ ("authorization", HAuthorization),
+ ("cache-control", HCacheControl),
+ ("connection", HConnection),
+ ("content-base", HContentBase),
+ ("content-disposition", HContentDisposition),
+ ("content-encoding", HContentEncoding),
+ ("content-language", HContentLanguage),
+ ("content-length", HContentLength),
+ ("content-location", HContentLocation),
+ ("content-md5", HContentMD5),
+ ("content-range", HContentRange),
+ ("content-type", HContentType),
+ ("cookie", HCookie),
+ ("date", HDate),
+ ("etag", HETag),
+ ("expect", HExpect),
+ ("expires", HExpires),
+ ("from", HFrom),
+ ("host", HHost),
+ ("if-modified-since", HIfModifiedSince),
+ ("if-match", HIfMatch),
+ ("if-none-match", HIfNoneMatch),
+ ("if-range", HIfRange),
+ ("if-unmodified-since", HIfUnmodifiedSince),
+ ("keep-alive", HKeepAlive),
+ ("last-modified", HLastModified),
+ ("location", HLocation),
+ ("max-forwards", HMaxForwards),
+ ("pics-label", HPICSLabel),
+ ("pragma", HPragma),
+ ("proxy-authenticate", HProxyAuthenticate),
+ ("proxy-authorization", HProxyAuthorization),
+ ("public", HPublic),
+ ("range", HRange),
+ ("referer", HReferer),
+ ("refresh", HRefresh),
+ ("retry-after", HRetryAfter),
+ ("server", HServer),
+ ("set-cookie", HSetCookie),
+ ("set-proxy", HSetProxy),
+ ("transfer-encoding", HTransferEncoding),
+ ("upgrade", HUpgrade),
+ ("user-agent", HUserAgent),
+ ("vary", HVary),
+ ("via", HVia),
+ ("warning", HWarning),
+ ("window-target", HWindowTarget),
+ ("www-authenticate", HWWWAuthenticate),
+ ("x-req-time", HXReqTime),
+ ("x-resp-time", HXRespTime),
+ ("x-url", HXUrl)
+};
+
+HTTP_Header: adt {
+ startline: string;
+
+ # following four fields only filled in if this is a response header
+ protomajor: int;
+ protominor: int;
+ code: int;
+ reason: string;
+ iossl: int; # true for ssl
+
+ vals: array of string;
+ cookies: list of string;
+
+ new: fn() : ref HTTP_Header;
+ read: fn(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context, buf: array of byte) : (string, int, int);
+ write: fn(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context) : int;
+ usessl: fn(h: self ref HTTP_Header);
+ addval: fn(h: self ref HTTP_Header, key: int, val: string);
+ getval: fn(h: self ref HTTP_Header, key: int) : string;
+};
+
+mediatable: array of T->StringInt;
+
+agent : string;
+dbg := 0;
+warn := 0;
+sptab : con " \t";
+
+init(cu: CharonUtils)
+{
+ CU = cu;
+ sys = load Sys Sys->PATH;
+ S = load String String->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ C = cu->C;
+ T = load StringIntTab StringIntTab->PATH;
+# D = load Date CU->loadpath(Date->PATH);
+# if (D == nil)
+# CU->raise(sys->sprint("EXInternal: can't load Date: %r"));
+# D->init(cu);
+ ctype = C->ctype;
+ # sslhs = nil; # load on demand
+ ssl3 = nil; # load on demand
+ mediatable = CU->makestrinttab(CU->mnames);
+ agent = (CU->config).agentname;
+ dbg = int (CU->config).dbg['n'];
+ warn = dbg || int (CU->config).dbg['w'];
+}
+
+connect(nc: ref Netconn, bs: ref ByteSource)
+{
+ if(nc.scheme == "https")
+ nc.tstate |= TSSL;
+ if(config.httpminor == 0)
+ nc.tstate |= THTTP_1_0;
+ dialhost := nc.host;
+ dialport := string nc.port;
+ if(nc.scheme != "https" && config.httpproxy != nil && need_proxy(nc.host)) {
+ nc.tstate |= TProxy;
+ dialhost = config.httpproxy.host;
+ if(config.httpproxy.port != "")
+ dialport = config.httpproxy.port;
+ }
+ addr := "tcp!" + dialhost + "!" + dialport;
+ err := "";
+ if(dbg)
+ sys->print("http %d: dialing %s\n", nc.id, addr);
+ rv: int;
+ (rv, nc.conn) = sys->dial(addr, nil);
+ if(rv < 0) {
+ syserr := sys->sprint("%r");
+ if(S->prefix("cs: dialup", syserr))
+ err = syserr[4:];
+ else if(S->prefix("cs: dns: no translation found", syserr))
+ err = "unknown host";
+ else
+ err = sys->sprint("couldn't connect: %s", syserr);
+ }
+ else {
+ if(dbg)
+ sys->print("http %d: connected\n", nc.id);
+ if(nc.tstate&TSSL) {
+ #if(sslhs == nil) {
+ # sslhs = load SSLHS SSLHS->PATH;
+ # if(sslhs == nil)
+ # err = sys->sprint("can't load SSLHS: %r");
+ # else
+ # sslhs->init(2);
+ #}
+ #if(err == "")
+ # (err, nc.conn) = sslhs->client(nc.conn.dfd, addr);
+ if(nc.tstate&TProxy) # tunelling SSL through proxy
+ err = tunnel_ssl(nc);
+ vers := 0;
+ if(err == "") {
+ if(ssl3 == nil) {
+ m := load SSL3 SSL3->PATH;
+ if(m == nil)
+ err = "can't load SSL3 module";
+ else if((err = m->init()) == nil)
+ ssl3 = m;
+ }
+ if(config.usessl == CU->NOSSL)
+ err = "ssl is configured off";
+ else if((config.usessl & CU->SSLV23) == CU->SSLV23)
+ vers = 23;
+ else if(config.usessl & CU->SSLV2)
+ vers = 2;
+ else if(config.usessl & CU->SSLV3)
+ vers = 3;
+ }
+ if(err == "") {
+ nc.sslx = ssl3->Context.new();
+ if(config.devssl)
+ nc.sslx.use_devssl();
+ info := ref SSL3->Authinfo(ssl_suites, ssl_comprs, nil,
+ 0, nil, nil, nil);
+vers = 3;
+ (err, nc.vers) = nc.sslx.client(nc.conn.dfd, addr, vers, info);
+ }
+ }
+ }
+ if(err == "") {
+ nc.connected = 1;
+ nc.state = CU->NCgethdr;
+ }
+ else {
+ if(dbg)
+ sys->print("http %d: connection failed: %s\n", nc.id, err);
+ bs.err = err;
+#constate("connect", nc.conn);
+ closeconn(nc);
+ }
+}
+
+constate(msg: string, conn: Sys->Connection)
+{
+ fd := conn.dfd;
+ fdfd := -1;
+ if (fd != nil)
+ fdfd = fd.fd;
+ sys->print("connstate(%s, %d) ", msg, fdfd);
+ sfd := sys->open(conn.dir + "/status", Sys->OREAD);
+ if (sfd == nil) {
+ sys->print("cannot open %s/status: %r\n", conn.dir);
+ return;
+ }
+ buf := array [1024] of byte;
+ n := sys->read(sfd, buf, len buf);
+ s := sys->sprint("error: %r");
+ if (n > 0)
+ s = string buf[:n];
+ sys->print("%s status: %s\n", conn.dir, s);
+}
+
+tunnel_ssl(nc: ref Netconn) : string
+{
+ httpvers: string;
+ if(nc.state&THTTP_1_0)
+ httpvers = "1.0";
+ else
+ httpvers = "1.1";
+ req := "CONNECT " + nc.host + ":" + string nc.port + " HTTP/" + httpvers;
+ n := sys->fprint(nc.conn.dfd, "%s\r\n\r\n", req);
+ if(n < 0)
+ return sys->sprint("proxy: %r");
+ buf := array [Sys->ATOMICIO] of byte;
+ n = sys->read(nc.conn.dfd, buf, Sys->ATOMICIO);
+ if(n < 0)
+ return sys->sprint("proxy: %r");;
+ resp := string buf[0:n];
+ (m, s) := sys->tokenize(resp, " ");
+
+ if(m < 2)
+ return "proxy: " + resp;
+ if(hd tl s != "200"){
+ (nil, e) := sys->tokenize(resp, "\n\r");
+ return hd e;
+ }
+ return "";
+}
+
+need_proxy(h: string) : int
+{
+ doms := config.noproxydoms;
+ lh := len h;
+ for(; doms != nil; doms = tl doms) {
+ dom := hd doms;
+ ld := len dom;
+ if(lh >= ld && h[lh-ld:] == dom)
+ return 0; # domain is on the no proxy list
+ }
+ return 1;
+}
+
+writereq(nc: ref Netconn, bs: ref ByteSource)
+{
+ #
+ # Prepare the request
+ #
+ req := bs.req;
+ u := ref *req.url;
+ requ, httpvers: string;
+ #if(nc.tstate&TProxy)
+ if((nc.tstate&TProxy) && !(nc.tstate&TSSL)) {
+ u.frag = nil;
+ requ = u.tostring();
+ } else {
+ requ = u.path;
+ if(u.query != "")
+ requ += "?" + u.query;
+ }
+ if(nc.tstate&THTTP_1_0)
+ httpvers = "1.0";
+ else
+ httpvers = "1.1";
+ reqhdr := HTTP_Header.new();
+ if(nc.tstate&TSSL)
+ reqhdr.usessl();
+ reqhdr.startline = CU->hmeth[req.method] + " " + requ + " HTTP/" + httpvers;
+ if(u.port != "")
+ reqhdr.addval(HHost, u.host+ ":" + u.port);
+ else
+ reqhdr.addval(HHost, u.host);
+ reqhdr.addval(HUserAgent, agent);
+ reqhdr.addval(HAccept, "*/*; *");
+# if(cr != nil && (cr.status == CRRevalidate || cr.status == CRMustRevalidate)) {
+# if(cr.etag != "")
+# reqhdr.addval(HIfNoneMatch, cr.etag);
+# else
+# reqhdr.addval(HIfModifiedSince, D->dateconv(cr.notafter));
+# }
+ if(req.auth != "")
+ reqhdr.addval(HAuthorization, "Basic " + req.auth);
+ if(req.method == CU->HPost) {
+ reqhdr.addval(HContentLength, string (len req.body));
+ reqhdr.addval(HContentType, "application/x-www-form-urlencoded");
+ }
+ if((CU->config).docookies > 0) {
+ cookies := CU->getcookies(u.host, u.path, nc.tstate&TSSL);
+ if (cookies != nil)
+ reqhdr.addval(HCookie, cookies);
+ }
+ #
+ # Issue the request
+ #
+ err := "";
+ if(dbg > 1) {
+ sys->print("http %d: writing request:\n", nc.id);
+ reqhdr.write(sys->fildes(1), nil);
+ }
+ rv := reqhdr.write(nc.conn.dfd, nc.sslx);
+ if(rv >= 0 && req.method == CU->HPost) {
+ if(dbg > 1)
+ sys->print("http %d: writing body:\n%s\n", nc.id, string req.body);
+ if((nc.tstate&TSSL) && nc.sslx != nil)
+ rv = nc.sslx.write(req.body, len req.body);
+ else
+ rv = sys->write(nc.conn.dfd, req.body, len req.body);
+ }
+ if(rv < 0) {
+ err = "error writing to host";
+#constate("writereq", nc.conn);
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("http %d: error: %s", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+
+gethdr(nc: ref Netconn, bs: ref ByteSource)
+{
+ resph := HTTP_Header.new();
+ if(nc.tstate&TSSL)
+ resph.usessl();
+ hbuf := array[8000] of byte;
+ (err, i, j) := resph.read(nc.conn.dfd, nc.sslx, hbuf);
+ if(err != "") {
+#constate("gethdr", nc.conn);
+ if(!(nc.tstate&THTTP_1_0)) {
+ # try switching to http 1.0
+ if(dbg)
+ sys->print("http %d: switching to HTTP/1.0\n", nc.id);
+ nc.tstate |= THTTP_1_0;
+ }
+ }
+ else {
+ if(dbg) {
+ sys->print("http %d: got response header:\n", nc.id);
+ resph.write(sys->fildes(1), nil);
+ sys->print("http %d: %d bytes remaining from read\n", nc.id, j-i);
+ }
+ if(resph.protomajor == 1) {
+ if(!(nc.tstate&THTTP_1_0) && resph.protominor == 0) {
+ nc.tstate |= THTTP_1_0;
+ if(dbg)
+ sys->print("http %d: switching to HTTP/1.0\n", nc.id);
+ }
+ }
+ else if(warn)
+ sys->print("warning: unimplemented major protocol %d.%d\n",
+ resph.protomajor, resph.protominor);
+ if(j > i)
+ nc.tbuf = hbuf[i:j];
+ else
+ nc.tbuf = nil;
+ bs.hdr = hdrconv(resph, bs.req.url);
+ if(bs.hdr.length == 0 && (nc.tstate&THTTP_1_0))
+ closeconn(nc);
+ }
+ if(err != "") {
+ if(dbg)
+ sys->print("http %d: error %s\n", nc.id, err);
+ bs.err = err;
+ closeconn(nc);
+ }
+}
+
+# returns number of bytes transferred to bs.data
+# 0 => EOF
+# -1 => error
+getdata(nc: ref Netconn, bs: ref ByteSource): int
+{
+ if (bs.data == nil || bs.edata >= len bs.data) {
+ if(nc.tstate&THTTP_1_0) {
+ # hmm - when do non-eof'd HTTP1.1 connections close?
+ closeconn(nc);
+ }
+ return 0;
+ }
+ buf := bs.data[bs.edata:];
+ n := len buf;
+ if (nc.tbuf != nil) {
+ # initial overread of header
+ if (n >= len nc.tbuf) {
+ n = len nc.tbuf;
+ buf[:] = nc.tbuf;
+ nc.tbuf = nil;
+ return n;
+ }
+ buf[:] = nc.tbuf[:n];
+ nc.tbuf = nc.tbuf[n:];
+ return n;
+ }
+ if ((nc.tstate&TSSL) && nc.sslx != nil)
+ n = nc.sslx.read(buf, n);
+ else
+ n = sys->read(nc.conn.dfd, buf, n);
+ if(dbg > 1)
+ sys->print("http %d: read %d bytes\n", nc.id, n);
+ if (n <= 0) {
+#constate("getdata", nc.conn);
+ closeconn(nc);
+ if(n < 0)
+ bs.err = sys->sprint("%r");
+ }
+#else
+#sys->write(sys->fildes(1), buf[:n], n);
+ return n;
+ }
+
+#getdata(nc: ref Netconn, bs: ref ByteSource)
+#{
+# buf := bs.data;
+# n := 0;
+# if(nc.tbuf != nil) {
+# # initial data from overread of header
+# # Note: can have more data in nc.tbuf than was
+# # reported by the HTTP header
+# n = len nc.tbuf;
+# if (n > bs.hdr.length) {
+# n = bs.hdr.length;
+# nc.tbuf = nc.tbuf[0:n];
+# }
+# if(len buf <= n) {
+# if(warn && len buf < n)
+# sys->print("more initial data than specified length\n");
+# bs.data = nc.tbuf;
+# }
+# else
+# buf[0:] = nc.tbuf[:n];
+# nc.tbuf = nil;
+# }
+# if(n == 0) {
+# if((nc.tstate&TSSL) && nc.sslx != nil)
+# n = nc.sslx.read(buf[bs.edata:], len buf - bs.edata);
+# else
+# n = sys->read(nc.conn.dfd, buf[bs.edata:], len buf - bs.edata);
+# }
+# if(dbg > 1)
+# sys->print("http %d: read %d bytes\n", nc.id, n);
+# if(n <= 0) {
+# closeconn(nc);
+# if(n < 0)
+# bs.err = sys->sprint("%r");
+# }
+# else {
+# bs.edata += n;
+# if(bs.edata == len buf && bs.hdr.length != 100000000) {
+# if(nc.tstate&THTTP_1_0) {
+# closeconn(nc);
+# }
+# }
+# }
+# if(bs.err != "") {
+# if(dbg)
+# sys->print("http %d: error %s\n", nc.id, bs.err);
+# closeconn(nc);
+# }
+#}
+
+hdrconv(hh: ref HTTP_Header, u: ref Parsedurl) : ref Header
+{
+ hdr := Header.new();
+ hdr.code = hh.code;
+ hdr.actual = u;
+ s := hh.getval(HContentBase);
+ if(s != "")
+ hdr.base = U->parse(s);
+ else
+ hdr.base = hdr.actual;
+ s = hh.getval(HLocation);
+ if(s != "")
+ hdr.location = U->parse(s);
+ s = hh.getval(HContentLength);
+ if(s != "")
+ hdr.length = int s;
+ else
+ hdr.length = -1;
+ s = hh.getval(HContentType);
+ if(s != "")
+ setmtype(hdr, s);
+ hdr.msg = hh.reason;
+ hdr.refresh = hh.getval(HRefresh);
+ hdr.chal = hh.getval(HWWWAuthenticate);
+ s = hh.getval(HContentEncoding);
+ if(s != "") {
+ if(warn)
+ sys->print("warning: unhandled content encoding: %s\n", s);
+ # force "save as" dialog
+ hdr.mtype = CU->UnknownType;
+ }
+ hdr.warn = hh.getval(HWarning);
+ hdr.lastModified = hh.getval(HLastModified);
+ if((CU->config).docookies > 0) {
+ for (ckl := hh.cookies; ckl != nil; ckl = tl ckl)
+ CU->setcookie(u.host, u.path, hd ckl);
+ }
+ return hdr;
+}
+
+# Set hdr's media type and chset (if a text type).
+# If can't set media type, leave it alone (caller will guess).
+setmtype(hdr: ref CU->Header, s: string)
+{
+ (ty, parms) := S->splitl(S->tolower(s), ";");
+ (fnd, val) := T->lookup(mediatable, trim(ty));
+ if(fnd) {
+ hdr.mtype = val;
+ if(len parms > 0 && val >= CU->TextCss && val <= CU->TextXml) {
+ nvs := Nameval.namevals(parms[1:], ';');
+ s: string;
+ (fnd, s) = Nameval.find(nvs, "charset");
+ if(fnd)
+ hdr.chset = s;
+ }
+ }
+ else {
+ if(warn)
+ sys->print("warning: unknown media type in %s\n", s);
+ }
+}
+
+# Remove leading and trailing whitespace from s.
+trim(s: string) : string
+{
+ is := 0;
+ ie := len s;
+ while(is < ie) {
+ if(ctype[s[is]] != C->W)
+ break;
+ is++;
+ }
+ if(is == ie)
+ return "";
+ while(ie > is) {
+ if(ctype[s[ie-1]] != C->W)
+ break;
+ ie--;
+ }
+ if(is >= ie)
+ return "";
+ if(is == 0 && ie == len s)
+ return s;
+ return s[is:ie];
+}
+
+# If s is in double quotes, remove them
+remquotes(s: string) : string
+{
+ n := len s;
+ if(n >= 2 && s[0] == '"' && s[n-1] == '"')
+ return s[1:n-1];
+ return s;
+}
+
+HTTP_Header.new() : ref HTTP_Header
+{
+ return ref HTTP_Header("", 0, 0, 0, "", 0, array[NumHfields] of { * => "" }, nil);
+}
+
+HTTP_Header.usessl(h: self ref HTTP_Header)
+{
+ h.iossl = 1;
+}
+
+HTTP_Header.addval(h: self ref HTTP_Header, key: int, val: string)
+{
+ if (key == HSetCookie) {
+ h.cookies = val :: h.cookies;
+ return;
+ }
+ oldv := h.vals[key];
+ if(oldv != "") {
+ # check that hdr type allows list of things
+ case key {
+ HAccept or HAcceptCharset or HAcceptEncoding
+ or HAcceptLanguage or HAcceptRanges
+ or HCacheControl or HConnection or HContentEncoding
+ or HContentLanguage or HIfMatch or HIfNoneMatch
+ or HPragma or HPublic or HUpgrade or HVia
+ or HWarning or HWWWAuthenticate or HExpect =>
+ val = oldv + ", " + val;
+ HCookie =>
+ val = oldv + "; " + val;
+ * =>
+ if(warn)
+ sys->print("warning: multiple %s headers not allowed\n", hdrnames[key]);
+ }
+ }
+ h.vals[key] = val;
+}
+
+HTTP_Header.getval(h: self ref HTTP_Header, key: int) : string
+{
+ return h.vals[key];
+}
+
+# Read into supplied buf.
+# Returns (ok, start of non-header bytes, end of non-header bytes)
+# If bytes > 127 appear, assume Latin-1
+#
+# Header values added will always be trimmed (see trim() above).
+HTTP_Header.read(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context, buf: array of byte) : (string, int, int)
+{
+ i := 0;
+ j := 0;
+ aline : array of byte = nil;
+ eof := 0;
+ if(h.iossl && sslx != nil) {
+ (aline, eof, i, j) = ssl_getline(sslx, buf, i, j);
+ }
+ else {
+ (aline, eof, i, j) = CU->getline(fd, buf, i, j);
+ }
+ if(aline == nil) {
+ return ("header read got immediate eof", 0, 0);
+ }
+ h.startline = latin1tostring(aline);
+ if(dbg > 1)
+ sys->print("header read, startline=%s\n", h.startline);
+ (vers, srest) := S->splitl(h.startline, " ");
+ if(len srest > 0)
+ srest = srest[1:];
+ (scode, reason) := S->splitl(srest, " ");
+ ok := 1;
+ if(len vers >= 8 && vers[0:5] == "HTTP/") {
+ (smaj, vrest) := S->splitl(vers[5:], ".");
+ if(smaj == "" || len vrest <= 1)
+ ok = 0;
+ else {
+ h.protomajor = int smaj;
+ if(h.protomajor < 1)
+ ok = 0;
+ else
+ h.protominor = int vrest[1:];
+ }
+ if(len scode != 3)
+ ok = 0;
+ else {
+ h.code = int scode;
+ if(h.code < 100)
+ ok = 0;
+ }
+ if(len reason > 0)
+ reason = reason[1:];
+ h.reason = reason;
+ }
+ else
+ ok = 0;
+ if(!ok)
+ return (sys->sprint("header read failed to parse start line '%s'\n", string aline), 0, 0);
+
+ prevkey := -1;
+ while(len aline > 0) {
+ if(h.iossl && sslx != nil) {
+ (aline, eof, i, j) = ssl_getline(sslx, buf, i, j);
+ }
+ else {
+ (aline, eof, i, j) = CU->getline(fd, buf, i, j);
+ }
+ if(eof)
+ return ("header doesn't end with blank line", 0, 0);
+ if(len aline == 0)
+ break;
+ line := latin1tostring(aline);
+ if(dbg > 1)
+ sys->print("%s\n", line);
+ if(ctype[line[0]] == C->W) {
+ if(prevkey < 0) {
+ if(warn)
+ sys->print("warning: header continuation line at beginning: %s\n", line);
+ }
+ else
+ h.vals[prevkey] = h.vals[prevkey] + " " + trim(line);
+ }
+ else {
+ (nam, val) := S->splitl(line, ":");
+ if(val == nil) {
+ if(warn)
+ sys->print("warning: header line has no colon: %s\n", line);
+ }
+ else {
+ (fnd, key) := T->lookup(hdrtable, S->tolower(nam));
+ if(!fnd) {
+ if(warn)
+ sys->print("warning: unknown header field: %s\n", line);
+ }
+ else {
+ h.addval(key, trim(val[1:]));
+ prevkey = key;
+ }
+ }
+ }
+ }
+ return ("", i, j);
+}
+
+# Write in big hunks. Convert to Latin1.
+# Return last sys->write return value.
+HTTP_Header.write(h: self ref HTTP_Header, fd: ref sys->FD, sslx: ref SSL3->Context) : int
+{
+ # Expect almost all responses will fit in this sized buf
+ buf := array[sys->ATOMICIO] of byte;
+ i := 0;
+ buflen := len buf;
+ need := len h.startline + 2 + 2;
+ if(need > buflen) {
+ buf = CU->realloc(buf, need-buflen);
+ buflen = len buf;
+ }
+ i = copyaslatin1(buf, h.startline, i, 1);
+ for(key := 0; key < NumHfields; key++) {
+ val := h.vals[key];
+ if(val != "") {
+ # 4 extra for this line, 2 for final cr/lf
+ need = len val + len hdrnames[key] + 4 + 2;
+ if(i + need > buflen) {
+ buf = CU->realloc(buf, i+need-buflen);
+ buflen = len buf;
+ }
+ i = copyaslatin1(buf, hdrnames[key], i, 0);
+ buf[i++] = byte ':';
+ buf[i++] = byte ' ';
+ # perhaps should break up really long lines,
+ # but we aren't expecting any
+ i = copyaslatin1(buf, val, i, 1);
+ }
+ }
+ buf[i++] = byte '\r';
+ buf[i++] = byte '\n';
+ n := 0;
+ for(k := 0; k < i; ) {
+ if(h.iossl && sslx != nil) {
+ n = sslx.write(buf[k:], i-k);
+ }
+ else {
+ n = sys->write(fd, buf[k:], i-k);
+ }
+ if(n <= 0)
+ break;
+ k += n;
+ }
+ return n;
+}
+
+# For latin1tostring, so don't have to keep allocating it
+lbuf := array[300] of byte;
+
+# Assume we call this on 'lines', so they won't be too long
+latin1tostring(a: array of byte) : string
+{
+ imax := len lbuf - 1;
+ i := 0;
+ n := len a;
+ for(k := 0; k < n; k++) {
+ b := a[k];
+ if(b < byte 128)
+ lbuf[i++] = b;
+ else
+ i += sys->char2byte(int b, lbuf, i);
+ if(i >= imax) {
+ if(imax > 1000) {
+ if(warn)
+ sys->print("warning: header line too long\n");
+ break;
+ }
+ lbuf = CU->realloc(lbuf, 100);
+ imax = len lbuf - 1;
+ }
+ }
+ ans := string lbuf[0:i];
+ return ans;
+}
+
+# Copy s into a[i:], converting to Latin1.
+# Add cr/lf if addcrlf is true.
+# Assume caller has checked that a has enough room.
+copyaslatin1(a: array of byte, s: string, i: int, addcrlf: int) : int
+{
+ ns := len s;
+ for(k := 0; k < ns; k++) {
+ c := s[k];
+ if(c < 256)
+ a[i++] = byte c;
+ else {
+ if(warn)
+ sys->print("warning: non-latin1 char in header ignored: '%c'\n", c);
+ }
+ }
+ if(addcrlf) {
+ a[i++] = byte '\r';
+ a[i++] = byte '\n';
+ }
+ return i;
+}
+
+defaultport(scheme: string) : int
+{
+ if(scheme == "https")
+ return HTTPSD;
+ return HTTPD;
+}
+
+closeconn(nc: ref Netconn)
+{
+ nc.conn.dfd = nil;
+ nc.conn.cfd = nil;
+ nc.conn.dir = "";
+ nc.connected = 0;
+ nc.sslx = nil;
+}
+
+ssl_getline(sslx: ref SSL3->Context, buf: array of byte, bstart, bend: int)
+ :(array of byte, int, int, int)
+{
+ ans : array of byte = nil;
+ last : array of byte = nil;
+ eof := 0;
+mainloop:
+ for(;;) {
+ for(i := bstart; i < bend; i++) {
+ if(buf[i] == byte '\n') {
+ k := i;
+ if(k > bstart && buf[k-1] == byte '\r')
+ k--;
+ last = buf[bstart:k];
+ bstart = i+1;
+ break mainloop;
+ }
+ }
+ if(bend > bstart)
+ ans = append(ans, buf[bstart:bend]);
+ last = nil;
+ bstart = 0;
+ bend = sslx.read(buf, len buf);
+ if(bend <= 0) {
+ eof = 1;
+ bend = 0;
+ break mainloop;
+ }
+ }
+ return (append(ans, last), eof, bstart, bend);
+}
+
+# Append copy of second array to first, return (possibly new)
+# address of the concatenation.
+append(a: array of byte, b: array of byte) : array of byte
+{
+ if(b == nil)
+ return a;
+ na := len a;
+ nb := len b;
+ ans := realloc(a, nb);
+ ans[na:] = b;
+ return ans;
+}
+
+# Return copy of a, but incr bytes bigger
+realloc(a: array of byte, incr: int) : array of byte
+{
+ n := len a;
+ newa := array[n + incr] of byte;
+ if(a != nil)
+ newa[0:] = a;
+ return newa;
+}
+