xref: /plan9-contrib/sys/src/cmd/webfs/client.c (revision 6a9fc400c33447ef5e1cda7185cb4de2c8e8010e)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ip.h>
5 #include <plumb.h>
6 #include <thread.h>
7 #include <fcall.h>
8 #include <9p.h>
9 #include "dat.h"
10 #include "fns.h"
11 
12 int nclient;
13 Client **client;
14 
15 static void clientthread(void*);
16 int
17 newclient(int plumbed)
18 {
19 	int i;
20 	Client *c;
21 
22 	for(i=0; i<nclient; i++)
23 		if(client[i]->ref==0)
24 			return i;
25 
26 	c = emalloc(sizeof(Client));
27 	c->plumbed = plumbed;
28 	c->creq = chancreate(sizeof(Req*), 8);
29 	threadcreate(clientthread, c, STACK);
30 
31 	c->io = ioproc();
32 	c->num = nclient;
33 	c->ctl = globalctl;
34 	clonectl(&c->ctl);
35 	if(nclient%16 == 0)
36 		client = erealloc(client, (nclient+16)*sizeof(client[0]));
37 	client[nclient++] = c;
38 	return nclient-1;
39 }
40 
41 void
42 closeclient(Client *c)
43 {
44 	if(--c->ref == 0){
45 		if(c->bodyopened)
46 			if(c->url && c->url->close)
47 				(*c->url->close)(c);
48 		free(c->contenttype);
49 		c->contenttype = nil;
50 		free(c->postbody);
51 		c->postbody = nil;
52 		freeurl(c->url);
53 		c->url = nil;
54 		free(c->redirect);
55 		c->redirect = nil;
56 		c->npostbody = 0;
57 		c->havepostbody = 0;
58 		c->bodyopened = 0;
59 	}
60 }
61 
62 void
63 clonectl(Ctl *c)
64 {
65 	if(c->useragent)
66 		c->useragent = estrdup(c->useragent);
67 }
68 
69 void
70 clientbodyopen(Client *c, Req *r)
71 {
72 	char e[ERRMAX], *next;
73 	int i;
74 	Url *u;
75 
76 	next = nil;
77 	for(i=0; i<=c->ctl.redirectlimit; i++){
78 		if(c->url == nil){
79 			werrstr("nil url");
80 			goto Error;
81 		}
82 		if(c->url->open == nil){
83 			werrstr("unsupported url type");
84 			goto Error;
85 		}
86 		if(fsdebug)
87 			fprint(2, "try %s\n", c->url->url);
88 		if(c->url->open(c, c->url) < 0){
89 		Error:
90 			if(next)
91 				fprint(2, "next %s (but for error)\n", next);
92 			free(next);
93 			rerrstr(e, sizeof e);
94 			c->iobusy = 0;
95 			if(r != nil)
96 				r->fid->omode = -1;
97 			closeclient(c);	/* not opening */
98 			if(r != nil)
99 				respond(r, e);
100 			return;
101 		}
102 		if(!c->redirect)
103 			break;
104 		next = c->redirect;
105 		c->redirect = nil;
106 		if(i==c->ctl.redirectlimit){
107 			werrstr("redirect limit reached");
108 			goto Error;
109 		}
110 		if((u = parseurl(next, c->url)) == nil)
111 			goto Error;
112 		if(urldebug)
113 			fprint(2, "parseurl %s got scheme %d\n", next, u->ischeme);
114 		if(u->ischeme == USunknown){
115 			werrstr("redirect with unknown URL scheme");
116 			goto Error;
117 		}
118 		if(u->ischeme == UScurrent){
119 			werrstr("redirect to URL relative to current document");
120 			goto Error;
121 		}
122 		freeurl(c->url);
123 		c->url = u;
124 	}
125 	free(next);
126 	c->iobusy = 0;
127 	if(r != nil)
128 		respond(r, nil);
129 }
130 
131 void
132 plumburl(char *url, char *base)
133 {
134 	int i;
135 	Client *c;
136 
137 	i = newclient(1);
138 	c = client[i];
139 	c->ref++;
140 	if(base != nil)
141 		c->baseurl = parseurl(base, nil);
142 	c->url = parseurl(url, c->baseurl);
143 	sendp(c->creq, nil);
144 }
145 
146 void
147 clientbodyread(Client *c, Req *r)
148 {
149 	char e[ERRMAX];
150 
151 	if(c->url->read == nil){
152 		respond(r, "unsupported url type");
153 		return;
154 	}
155 	if(c->url->read(c, r) < 0){
156 		rerrstr(e, sizeof e);
157 		c->iobusy = 0;
158 		respond(r, e);
159 		return;
160 	}
161 	c->iobusy = 0;
162 	respond(r, nil);
163 }
164 
165 static void
166 clientthread(void *a)
167 {
168 	Client *c;
169 	Req *r;
170 
171 	c = a;
172 	if(c->plumbed) {
173 		recvp(c->creq);
174 		clientbodyopen(c, nil);
175 		replumb(c);
176 	}
177 	while((r = recvp(c->creq)) != nil){
178 		if(fsdebug)
179 			fprint(2, "clientthread %F\n", &r->ifcall);
180 		switch(r->ifcall.type){
181 		case Topen:
182 			if(c->plumbed) {
183 				c->plumbed = 0;
184 				c->ref--;			/* from plumburl() */
185 				respond(r, nil);
186 			}
187 			else
188 				clientbodyopen(c, r);
189 			break;
190 		case Tread:
191 			clientbodyread(c, r);
192 			break;
193 		case Tflush:
194 			respond(r, nil);
195 		}
196 		if(fsdebug)
197 			fprint(2, "clientthread finished req\n");
198 	}
199 }
200 
201 enum
202 {
203 	Bool,
204 	Int,
205 	String,
206 	XUrl,
207 	Fn,
208 };
209 
210 typedef struct Ctab Ctab;
211 struct Ctab {
212 	char *name;
213 	int type;
214 	void *offset;
215 };
216 
217 Ctab ctltab[] = {
218 	"acceptcookies",	Bool,		(void*)offsetof(Ctl, acceptcookies),
219 	"sendcookies",		Bool,		(void*)offsetof(Ctl, sendcookies),
220 	"redirectlimit",		Int,		(void*)offsetof(Ctl, redirectlimit),
221 	"useragent",		String,	(void*)offsetof(Ctl, useragent),
222 };
223 
224 Ctab globaltab[] = {
225 	"chatty9p",		Int,		&chatty9p,
226 	"fsdebug",		Int,		&fsdebug,
227 	"cookiedebug",		Int,		&cookiedebug,
228 	"urldebug",		Int,		&urldebug,
229 	"httpdebug",		Int,		&httpdebug,
230 };
231 
232 Ctab clienttab[] = {
233 	"baseurl",			XUrl,		(void*)offsetof(Client, baseurl),
234 	"url",				XUrl,		(void*)offsetof(Client, url),
235 };
236 
237 static Ctab*
238 findcmd(char *cmd, Ctab *tab, int ntab)
239 {
240 	int i;
241 
242 	for(i=0; i<ntab; i++)
243 		if(strcmp(tab[i].name, cmd) == 0)
244 			return &tab[i];
245 	return nil;
246 }
247 
248 static void
249 parseas(Req *r, char *arg, int type, void *a)
250 {
251 	Url *u;
252 	char e[ERRMAX];
253 
254 	switch(type){
255 	case Bool:
256 		if(strcmp(arg, "on")==0 || strcmp(arg, "1")==0)
257 			*(int*)a = 1;
258 		else
259 			*(int*)a = 0;
260 		break;
261 	case String:
262 		free(*(char**)a);
263 		*(char**)a = estrdup(arg);
264 		break;
265 	case XUrl:
266 		u = parseurl(arg, nil);
267 		if(u == nil){
268 			snprint(e, sizeof e, "parseurl: %r");
269 			respond(r, e);
270 			return;
271 		}
272 		freeurl(*(Url**)a);
273 		*(Url**)a = u;
274 		break;
275 	case Int:
276 		if(strcmp(arg, "on")==0)
277 			*(int*)a = 1;
278 		else
279 			*(int*)a = atoi(arg);
280 		break;
281 	}
282 	respond(r, nil);
283 }
284 
285 int
286 ctlwrite(Req *r, Ctl *ctl, char *cmd, char *arg)
287 {
288 	void *a;
289 	Ctab *t;
290 
291 	if((t = findcmd(cmd, ctltab, nelem(ctltab))) == nil)
292 		return 0;
293 	a = (void*)((ulong)ctl+(int)t->offset);
294 	parseas(r, arg, t->type, a);
295 	return 1;
296 }
297 
298 int
299 clientctlwrite(Req *r, Client *c, char *cmd, char *arg)
300 {
301 	void *a;
302 	Ctab *t;
303 
304 	if((t = findcmd(cmd, clienttab, nelem(clienttab))) == nil)
305 		return 0;
306 	a = (void*)((ulong)c+(int)t->offset);
307 	parseas(r, arg, t->type, a);
308 	return 1;
309 }
310 
311 int
312 globalctlwrite(Req *r, char *cmd, char *arg)
313 {
314 	void *a;
315 	Ctab *t;
316 
317 	if((t = findcmd(cmd, globaltab, nelem(globaltab))) == nil)
318 		return 0;
319 	a = t->offset;
320 	parseas(r, arg, t->type, a);
321 	return 1;
322 }
323 
324 static void
325 ctlfmt(Ctl *c, char *s)
326 {
327 	int i;
328 	void *a;
329 	char *t;
330 
331 	for(i=0; i<nelem(ctltab); i++){
332 		a = (void*)((ulong)c+(int)ctltab[i].offset);
333 		switch(ctltab[i].type){
334 		case Bool:
335 			s += sprint(s, "%s %s\n", ctltab[i].name, *(int*)a ? "on" : "off");
336 			break;
337 		case Int:
338 			s += sprint(s, "%s %d\n", ctltab[i].name, *(int*)a);
339 			break;
340 		case String:
341 			t = *(char**)a;
342 			if(t != nil)
343 				s += sprint(s, "%s %.*s%s\n", ctltab[i].name, utfnlen(t, 100), t, strlen(t)>100 ? "..." : "");
344 			break;
345 		}
346 	}
347 }
348 
349 void
350 ctlread(Req *r, Client *c)
351 {
352 	char buf[1024];
353 
354 	sprint(buf, "%11d \n", c->num);
355 	ctlfmt(&c->ctl, buf+strlen(buf));
356 	readstr(r, buf);
357 	respond(r, nil);
358 }
359 
360 void
361 globalctlread(Req *r)
362 {
363 	char buf[1024], *s;
364 	int i;
365 
366 	s = buf;
367 	for(i=0; i<nelem(globaltab); i++)
368 		s += sprint(s, "%s %d\n", globaltab[i].name, *(int*)globaltab[i].offset);
369 	ctlfmt(&globalctl, s);
370 	readstr(r, buf);
371 	respond(r, nil);
372 }
373