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