xref: /inferno-os/appl/svc/webget/wgutils.b (revision fbc1184c08d18d5ac0f8763a058e015e95353341)
1implement WebgetUtils;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7
8include "string.m";
9
10include "bufio.m";
11
12include "dial.m";
13
14include "imagefile.m";
15	readgif, readjpg, readxbitmap: RImagefile;
16
17include "image2enc.m";
18	image2enc: Image2enc;
19
20include "message.m";
21
22include "url.m";
23
24include "wgutils.m";
25	Iobuf: import B;
26
27include "strinttab.m";
28	T: StringIntTab;
29
30Msg, Nameval: import M;
31ParsedUrl: import U;
32
33logfd: ref Sys->FD;
34
35# return from acceptmatch; and conv arg to readbody
36BadConv, NoConv, Gif2xcompressed, Jpeg2xcompressed, Xbm2xcompressed: con iota;
37
38# Both extensions and Content-Types can be in same table.
39# This array should be kept sorted
40mtypes := array[] of { T->StringInt
41	("ai", ApplPostscript),
42	("application/html", TextHtml),
43	("application/pdf", ApplPdf),
44	("application/postscript", ApplPostscript),
45	("application/rtf", ApplRtf),
46	("application/soap+xml", TextPlain),
47	("application/x-html", TextHtml),
48	("au", AudioBasic),
49	("audio/au", AudioBasic),
50	("audio/basic", AudioBasic),
51	("bit", ImageXCompressed),
52	("bit2", ImageXCompressed2),
53	("eps", ApplPostscript),
54	("gif", ImageGif),
55	("htm", TextHtml),
56	("html", TextHtml),
57	("image/gif", ImageGif),
58	("image/ief", ImageIef),
59	("image/jpeg", ImageJpeg),
60	("image/tiff", ImageTiff),
61	("image/x-compressed", ImageXCompressed),
62	("image/x-compressed2", ImageXCompressed2),
63	("image/x-xbitmap", ImageXXBitmap),
64	("jpe", ImageJpeg),
65	("jpeg", ImageJpeg),
66	("jpg", ImageJpeg),
67	("pdf", ApplPdf),
68	("ps", ApplPostscript),
69	("text", TextPlain),
70	("text/html", TextHtml),
71	("text/plain", TextPlain),
72	("text/x-html", TextHtml),
73	("text/xml", TextXml),
74	("tif", ImageTiff),
75	("tiff", ImageTiff),
76	("txt", TextPlain),
77	("video/mpeg", VideoMpeg),
78	("video/quicktime", VideoQuicktime),
79};
80
81# following array must track media type def in wgutils.m
82mnames := array[] of {
83	"application/x-unknown",
84	"text/plain",
85	"text/html",
86	"application/postscript",
87	"application/rtf",
88	"application/pdf",
89	"image/jpeg",
90	"image/gif",
91	"image/ief",
92	"image/tiff",
93	"image/x-compressed",
94	"image/x-compressed2",
95	"image/x-xbitmap",
96	"audio/basic",
97	"video/mpeg",
98	"video/quicktime",
99	"application/soap+xml",
100	"text/xml"
101};
102
103init(m: Message, s: String, b: Bufio, u: Url, di: Dial, lfd: ref Sys->FD)
104{
105	sys = load Sys Sys->PATH;
106
107	M = m;
108	S = s;
109	B = b;
110	U = u;
111	DI = di;
112	logfd = lfd;
113	T = load StringIntTab StringIntTab->PATH;
114	readgif = load RImagefile RImagefile->READGIFPATH;
115	readjpg = load RImagefile RImagefile->READJPGPATH;
116	readxbitmap = load RImagefile RImagefile->READXBMPATH;
117	image2enc = load Image2enc Image2enc->PATH;
118	if(T == nil || readgif == nil || readjpg == nil || readxbitmap == nil || image2enc == nil) {
119		sys->fprint(sys->fildes(2), "webget: failed to load T, readgif, readjpg, readxbitmap, or imageremap: %r\n");
120		return;
121	}
122	readgif->init(B);
123	readjpg->init(B);
124	readxbitmap->init(B);
125}
126
127# Return msg with error provoked by bad user action
128usererr(r: ref Req, msg: string) : ref Msg
129{
130	m := Msg.newmsg();
131	m.prefixline = sys->sprint("ERROR %s %s\n", r.reqid, msg);
132	m.bodylen = 0;
133	return m;
134}
135
136okprefix(r: ref Req, mrep: ref Msg)
137{
138	(nil, sctype) := mrep.fieldval("content-type");
139	if(sctype == "")
140		sctype = "text/html";
141	else
142		sctype = canon_mtype(sctype);
143	(nil, cloc) := mrep.fieldval("content-location");
144	if(cloc == "")
145		cloc = "unknown";
146	mrep.prefixline = "OK " + string mrep.bodylen + " " + r.reqid + " " + sctype + " " + cloc +"\n";
147}
148
149canon_mtype(s: string) : string
150{
151	# lowercase, and remove possible parameter
152	ls := S->tolower(s);
153	(ts, nil) := S->splitl(ls, "; ");
154	return ts;
155}
156
157mediatype(s: string, dflt: int) : int
158{
159	(fnd, val) := T->lookup(mtypes, canon_mtype(s));
160	if(!fnd)
161		val = dflt;
162	return val;
163}
164
165acceptmatch(ctype: int, accept: string) : int
166{
167	conv := BadConv;
168	(nil,l) := sys->tokenize(accept, ",");
169	while(l != nil) {
170		a := S->drop(hd l, " \t");
171		mt := mediatype(a, -1);
172		match := (ctype == mt) || (a == "*/*")
173			|| ((ctype == ImageXCompressed || ctype == ImageXCompressed2)
174			   && (mt == ImageJpeg || mt == ImageGif || mt == ImageXXBitmap));
175		if(match) {
176			if(ctype == ImageGif)
177				conv = Gif2xcompressed;
178			else if(ctype == ImageJpeg)
179				conv = Jpeg2xcompressed;
180			else if(ctype == ImageXXBitmap)
181				conv = Xbm2xcompressed;
182			else
183				conv = NoConv;
184			break;
185		}
186		l = tl l;
187	}
188	return conv;
189}
190
191# Get the body of the message whose header is in mrep,
192# if io != nil.
193# First check that the content type is acceptable.
194# Image types will get converted into Inferno compressed format.
195# If there is an error, return error string, else "".
196# If no error, mrep will contain content-encoding, content-location,
197# and content-type fields (guessed if they weren't orignally there).
198
199getdata(io: ref Iobuf, m: ref Msg, accept: string, url: ref ParsedUrl) : string
200{
201	(efnd, etype) := m.fieldval("content-encoding");
202	if(efnd)
203		return "content is encoded: " + etype;
204	ctype := UnknownType;
205	(tfnd, sctype) := m.fieldval("content-type");
206	if(tfnd)
207		ctype = mediatype(sctype, UnknownType);
208	else {
209		# try to guess type from extension
210		sctype = "(unknown)";
211		(nil, name) := S->splitr(url.path, "/");
212		if(name != "") {
213			(f, ext) := S->splitr(name, ".");
214			if(f != "" && ext != "") {
215				ctype = mediatype(ext, UnknownType);
216				if(ctype != UnknownType) {
217					sctype = mnames[ctype];
218					m.update("content-type", sctype);
219				}
220			}
221		}
222	}
223	transform := acceptmatch(ctype, accept);
224	if(transform == BadConv)
225		return "Unacceptable media type: " + sctype;
226	(clfnd, cloc) := m.fieldval("content-location");
227	if(!clfnd) {
228		cloc = url.tostring();
229		m.update("content-location", cloc);
230	}
231	if(transform != NoConv) {
232		rawimg: ref RImagefile->Rawimage;
233		err: string;
234		if(transform == Gif2xcompressed)
235			(rawimg, err) = readgif->read(io);
236		else if(transform == Jpeg2xcompressed)
237			(rawimg, err) = readjpg->read(io);
238		else if(transform == Xbm2xcompressed)
239			(rawimg, err) = readxbitmap->read(io);
240		# if gif file has multiple images, we are supposed to animate,
241		# but the first one should be there
242		if(err != "" && err != "ReadGIF: can't handle multiple images in file")
243			return "error converting image file: " + err;
244		(data, mask, e) := image2enc->image2enc(rawimg, 1);
245		if(e != "")
246			return "error remapping and encoding image file: " + e;
247		if(mask == nil)
248			sctype = "image/x-compressed";
249		else {
250			sctype = "image/x-compressed2";
251			newdata := array[len data + len mask] of byte;
252			newdata[0:] = data[0:];
253			newdata[len data:] = mask[0:];
254			data = newdata;
255		}
256		m.body = data;
257		m.bodylen = len data;
258		m.update("content-type", sctype);
259		m.update("content-length", string m.bodylen);
260	}
261	else {
262		# io will be nil if m came from cache
263		if(io != nil) {
264			e := m.readbody(io);
265			if(e != "")
266				return "reading body: " + e;
267		}
268	}
269	return "";
270}
271
272# Change an accept spec from webget client into one we can send
273# to http server.  This means image/x-compressed must be
274# changed into image formats we can handle: i.e., gif or jpeg
275fixaccept(a: string) : string
276{
277	(nil,l) := sys->tokenize(a, ",");
278	ans := "";
279	sep := "";
280	while(l != nil) {
281		s := S->drop(hd l, " \t");
282		if(s == "image/x-compressed")
283			ans += sep + "image/gif,image/jpeg,image/x-xbitmap";
284		else
285			ans += sep + s;
286		sep = ",";
287		l = tl l;
288	}
289	if(ans == "")
290		ans = "*/*";
291	return ans;
292}
293
294log(c: ref Fid, msg: string)
295{
296	if(logfd != nil) {
297		# don't use print in case msg is longer than buf
298		s := "";
299		if(c != nil)
300			s += (string c.fid) + ": ";
301		s += msg + "\n";
302		b := array of byte s;
303		sys->write(logfd, b, len b);
304	}
305}
306