shithub: purgatorio

ref: 354b56d6bfdca067a6f04a39c4ce4bc9c0160861
dir: /appl/svc/webget/message.b/

View raw version
implement Message;

include "sys.m";
	sys: Sys;

include "string.m";
	S : String;

include "bufio.m";
	B : Bufio;
	Iobuf: import B;

include "message.m";
	msg: Message;

msglog: ref Sys->FD;

init(bufio: Bufio, smod: String)
{
	sys = load Sys Sys->PATH;
	S = smod;
	B = bufio;
}

sptab : con " \t";
crlf : con "\r\n";

Msg.newmsg() : ref Msg
{
	return ref Msg("", nil, nil, nil, 0);
}

# Read a message header from fd and return a Msg
# the header fields.
# If withprefix is true, read one line first and put it
# in the prefixline field of the Msg (without terminating \r\n)
# Return nil if there is a read error or eof before the
# header is completely read.
Msg.readhdr(io: ref Iobuf, withprefix: int) : (ref Msg, string)
{
	m := Msg.newmsg();
	l : list of Nameval = nil;
	needprefix := withprefix;
	for(;;) {
		line := getline(io);
		n := len line;
		if(n == 0) {
			if(withprefix && m.prefixline != "")
				break;
			return(nil, "msg read hdr error: no header");
		}
		if(line[n-1] != '\n')
			return (m, "msg read hdr error: incomplete header");
		if(n >= 2 && line[n-2] == '\r')
			line = line[0:n-2];
		else
			line = line[0:n-1];
		if(needprefix) {
			m.prefixline = line;
			needprefix = 0;
		}
		else {
			if(line == "")
				break;
			if(S->in(line[0], sptab)) {
				if(l == nil)
					continue;
				nv := hd l;
				l = Nameval(nv.name, nv.value + " " + S->drop(line, sptab)) :: tl l;
			}
			else {
				(nam, val) := S->splitl(line, ":");
				if(val == nil)
					continue;  # no colon
				l = Nameval(S->tolower(nam), S->drop(val[1:], sptab)) :: l;
			}
		}
	}
	nh := len l;
	if(nh > 0) {
		m.fields = array[nh] of Nameval;
		for(i := nh-1; i >= 0; i--) {
			m.fields[i] = hd l;
			l = tl l;
		}
	}
	return (m, "");
}

glbuf := array[300] of byte;

# Like io.gets('\n'), but assume Latin-1 instead of UTF encoding
getline(io: ref Iobuf): string
{
	imax := len glbuf - 1;
	for(i := 0; i < imax; ) {
		c := io.getb();
		if(c < 0)
			break;
		if(c < 128)
			glbuf[i++] = byte c;
		else
			i += sys->char2byte(c, glbuf, i);
		if(c == '\n')
			break;
		if(i == imax) {
			imax += 100;
			if(imax > 1000)
				break;	# Header lines aren't supposed to be > 1000
			newglbuf := array[imax] of byte;
			newglbuf[0:] = glbuf[0:i];
			glbuf = newglbuf;
		}
	}
	ans := string glbuf[0:i];
	return ans;
}

Bbufsize: con 8000;

# Read the body of the message, assuming the header has been processed.
# If content-length has been specified, read exactly that many bytes
# or until eof; else read until done.
# Return "" if all is OK, else return an error string.
Msg.readbody(m: self ref Msg, io: ref Iobuf) : string
{
	(clfnd, cl) := m.fieldval("content-length");
	if(clfnd) {
		clen := int cl;
		if(clen > 0) {
			m.body = array[clen] of byte;
			n := B->io.read(m.body, clen);
			m.bodylen = n;
			if(n != clen)
				return "short read";
		}
	}
	else {
		m.body = array[Bbufsize] of byte;
		curlen := 0;
		for(;;) {
			avail := len m.body - curlen;
			if(avail <= 0) {
				newa := array[len m.body + Bbufsize] of byte;
				if(curlen > 0)
					newa[0:] = m.body[0:curlen];
				m.body = newa;
				avail = Bbufsize;
			}
			n := B->io.read(m.body[curlen:], avail);
			if(n < 0)
				return sys->sprint("readbody error %r");
			if(n == 0)
				break;
			else
				curlen += n;
		}
		m.bodylen = curlen;
	}
	return "";
}

# Look for name (lowercase) in the fields of m
# and (1, field value) if found, or (0,"") if not.
# If multiple fields with the same name exist,
# the value is defined as the comma separated list
# of all such values.
Msg.fieldval(m: self ref Msg, name: string) : (int, string)
{
	n := len m.fields;
	ans := "";
	found := 0;
	for(i := 0; i < n; i++) {
		if(m.fields[i].name == name) {
			v := m.fields[i].value;
			if(found)
				ans = ans + ", " + v;
			else
				ans = v;
			found = 1;
		}
	}
	return (found, ans);
}

Msg.addhdrs(m: self ref Msg, hdrs: list of Nameval)
{
	nh := len hdrs;
	if(nh == 0)
		return;
	onh := len m.fields;
	newa := array[nh + onh] of Nameval;
	newa[0:] = m.fields;
	i := onh;
	while(hdrs != nil) {
		newa[i++] = hd hdrs;
		hdrs = tl hdrs;
	}
	m.fields = newa;
}

Msg.update(m: self ref Msg, name, value: string)
{
	for(i := 0; i < len m.fields; i++)
		if(m.fields[i].name == name) {
			m.fields[i] = Nameval(name, value);
			return;
		}
	m.addhdrs(Nameval(name, value) :: nil);
}

Msg.header(m: self ref Msg) : string
{
	s := "";
	for(i := 0; i < len m.fields; i++) {
		nv := m.fields[i];
		s += nv.name + ": " + nv.value + "\n";
	}
	return s;
}

Msg.writemsg(m: self ref Msg, io: ref Iobuf) : string
{
	n := 0;
	if(m.prefixline != nil) {
		n = B->io.puts(m.prefixline);
		if(n >= 0)
			n = B->io.puts(crlf);
	}
	for(i := 0; i < len m.fields; i++) {
		nv := m.fields[i];
		if(n >= 0)
			n = B->io.puts(nv.name);
		if(n >= 0)
			n = B->io.puts(": ");
		if(n >= 0)
			n = B->io.puts(nv.value);
		if(n >= 0)
			n = B->io.puts(crlf);
	}
	if(n >= 0)
		n = B->io.puts(crlf);
	if(n >= 0 && m.bodylen > 0)
		n = B->io.write(m.body, m.bodylen);
	if(n < 0)
		return sys->sprint("msg write error: %r");
	B->io.flush();
	return "";
}