xref: /inferno-os/appl/svc/webget/message.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Message;
2
3include "sys.m";
4	sys: Sys;
5
6include "string.m";
7	S : String;
8
9include "bufio.m";
10	B : Bufio;
11	Iobuf: import B;
12
13include "message.m";
14	msg: Message;
15
16msglog: ref Sys->FD;
17
18init(bufio: Bufio, smod: String)
19{
20	sys = load Sys Sys->PATH;
21	S = smod;
22	B = bufio;
23}
24
25sptab : con " \t";
26crlf : con "\r\n";
27
28Msg.newmsg() : ref Msg
29{
30	return ref Msg("", nil, nil, nil, 0);
31}
32
33# Read a message header from fd and return a Msg
34# the header fields.
35# If withprefix is true, read one line first and put it
36# in the prefixline field of the Msg (without terminating \r\n)
37# Return nil if there is a read error or eof before the
38# header is completely read.
39Msg.readhdr(io: ref Iobuf, withprefix: int) : (ref Msg, string)
40{
41	m := Msg.newmsg();
42	l : list of Nameval = nil;
43	needprefix := withprefix;
44	for(;;) {
45		line := getline(io);
46		n := len line;
47		if(n == 0) {
48			if(withprefix && m.prefixline != "")
49				break;
50			return(nil, "msg read hdr error: no header");
51		}
52		if(line[n-1] != '\n')
53			return (m, "msg read hdr error: incomplete header");
54		if(n >= 2 && line[n-2] == '\r')
55			line = line[0:n-2];
56		else
57			line = line[0:n-1];
58		if(needprefix) {
59			m.prefixline = line;
60			needprefix = 0;
61		}
62		else {
63			if(line == "")
64				break;
65			if(S->in(line[0], sptab)) {
66				if(l == nil)
67					continue;
68				nv := hd l;
69				l = Nameval(nv.name, nv.value + " " + S->drop(line, sptab)) :: tl l;
70			}
71			else {
72				(nam, val) := S->splitl(line, ":");
73				if(val == nil)
74					continue;  # no colon
75				l = Nameval(S->tolower(nam), S->drop(val[1:], sptab)) :: l;
76			}
77		}
78	}
79	nh := len l;
80	if(nh > 0) {
81		m.fields = array[nh] of Nameval;
82		for(i := nh-1; i >= 0; i--) {
83			m.fields[i] = hd l;
84			l = tl l;
85		}
86	}
87	return (m, "");
88}
89
90glbuf := array[300] of byte;
91
92# Like io.gets('\n'), but assume Latin-1 instead of UTF encoding
93getline(io: ref Iobuf): string
94{
95	imax := len glbuf - 1;
96	for(i := 0; i < imax; ) {
97		c := io.getb();
98		if(c < 0)
99			break;
100		if(c < 128)
101			glbuf[i++] = byte c;
102		else
103			i += sys->char2byte(c, glbuf, i);
104		if(c == '\n')
105			break;
106		if(i == imax) {
107			imax += 100;
108			if(imax > 1000)
109				break;	# Header lines aren't supposed to be > 1000
110			newglbuf := array[imax] of byte;
111			newglbuf[0:] = glbuf[0:i];
112			glbuf = newglbuf;
113		}
114	}
115	ans := string glbuf[0:i];
116	return ans;
117}
118
119Bbufsize: con 8000;
120
121# Read the body of the message, assuming the header has been processed.
122# If content-length has been specified, read exactly that many bytes
123# or until eof; else read until done.
124# Return "" if all is OK, else return an error string.
125Msg.readbody(m: self ref Msg, io: ref Iobuf) : string
126{
127	(clfnd, cl) := m.fieldval("content-length");
128	if(clfnd) {
129		clen := int cl;
130		if(clen > 0) {
131			m.body = array[clen] of byte;
132			n := B->io.read(m.body, clen);
133			m.bodylen = n;
134			if(n != clen)
135				return "short read";
136		}
137	}
138	else {
139		m.body = array[Bbufsize] of byte;
140		curlen := 0;
141		for(;;) {
142			avail := len m.body - curlen;
143			if(avail <= 0) {
144				newa := array[len m.body + Bbufsize] of byte;
145				if(curlen > 0)
146					newa[0:] = m.body[0:curlen];
147				m.body = newa;
148				avail = Bbufsize;
149			}
150			n := B->io.read(m.body[curlen:], avail);
151			if(n < 0)
152				return sys->sprint("readbody error %r");
153			if(n == 0)
154				break;
155			else
156				curlen += n;
157		}
158		m.bodylen = curlen;
159	}
160	return "";
161}
162
163# Look for name (lowercase) in the fields of m
164# and (1, field value) if found, or (0,"") if not.
165# If multiple fields with the same name exist,
166# the value is defined as the comma separated list
167# of all such values.
168Msg.fieldval(m: self ref Msg, name: string) : (int, string)
169{
170	n := len m.fields;
171	ans := "";
172	found := 0;
173	for(i := 0; i < n; i++) {
174		if(m.fields[i].name == name) {
175			v := m.fields[i].value;
176			if(found)
177				ans = ans + ", " + v;
178			else
179				ans = v;
180			found = 1;
181		}
182	}
183	return (found, ans);
184}
185
186Msg.addhdrs(m: self ref Msg, hdrs: list of Nameval)
187{
188	nh := len hdrs;
189	if(nh == 0)
190		return;
191	onh := len m.fields;
192	newa := array[nh + onh] of Nameval;
193	newa[0:] = m.fields;
194	i := onh;
195	while(hdrs != nil) {
196		newa[i++] = hd hdrs;
197		hdrs = tl hdrs;
198	}
199	m.fields = newa;
200}
201
202Msg.update(m: self ref Msg, name, value: string)
203{
204	for(i := 0; i < len m.fields; i++)
205		if(m.fields[i].name == name) {
206			m.fields[i] = Nameval(name, value);
207			return;
208		}
209	m.addhdrs(Nameval(name, value) :: nil);
210}
211
212Msg.header(m: self ref Msg) : string
213{
214	s := "";
215	for(i := 0; i < len m.fields; i++) {
216		nv := m.fields[i];
217		s += nv.name + ": " + nv.value + "\n";
218	}
219	return s;
220}
221
222Msg.writemsg(m: self ref Msg, io: ref Iobuf) : string
223{
224	n := 0;
225	if(m.prefixline != nil) {
226		n = B->io.puts(m.prefixline);
227		if(n >= 0)
228			n = B->io.puts(crlf);
229	}
230	for(i := 0; i < len m.fields; i++) {
231		nv := m.fields[i];
232		if(n >= 0)
233			n = B->io.puts(nv.name);
234		if(n >= 0)
235			n = B->io.puts(": ");
236		if(n >= 0)
237			n = B->io.puts(nv.value);
238		if(n >= 0)
239			n = B->io.puts(crlf);
240	}
241	if(n >= 0)
242		n = B->io.puts(crlf);
243	if(n >= 0 && m.bodylen > 0)
244		n = B->io.write(m.body, m.bodylen);
245	if(n < 0)
246		return sys->sprint("msg write error: %r");
247	B->io.flush();
248	return "";
249}
250