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