Plan 9 from Bell Labs’s /usr/web/sources/extra/mothra/http.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <panel.h>
#include "mothra.h"
typedef struct Cache Cache;
struct Cache{
	int fd;			/* file descriptor on which to write cached data */
	ulong hash;		/* hash of url, used to compute cache file name */
	int modtime;		/* time at which cache entry was created */
	int type;		/* url->type of cached entry */
};
void httpheader(Url *, char *);
int httpresponse(char *);
static char *proxyserver;	/* name of proxy server */
void exitnow(void*, char*){
	noted(NDFLT);
}
void hashname(char *name, char *stem, Cache *c){
	sprint(name, "/sys/lib/mothra/cache/%s.%.8lux", stem, c->hash);
}
#define	CacheEnabled
/*
 * Returns fd of cached file, if found (else -1)
 * Fills in Cache data structure for caller
 * If stale is set, caller has determined that the existing
 * cache entry for this url is stale, so we shouldn't bother re-examining it.
 */
int cacheopen(Url *url, Cache *c, int stale){
#ifdef CacheEnabled
	int fd, n;
	char name[513], *s, *l;
	/*
	 * If we're using a proxy server or the url contains a ? or =,
	 * don't even bother.
	 */
	if(proxyserver || strchr(url->reltext, '?')!=0 || strchr(url->reltext, '=')!=0){
		c->fd=-1;
		return -1;
	}
	c->hash=0;
	for(s=url->fullname,n=0;*s;s++,n++) c->hash=c->hash*n+(*s&255);
	if(stale)
		fd=-1;
	else{
		hashname(name, "cache", c);
		fd=open(name, OREAD);
	}
	if(fd==-1){
		hashname(name, "write", c);
		c->fd=create(name, OWRITE, 0444);
		if(c->fd!=-1)
			fprint(c->fd, "%s %10ld\n", url->fullname, time(0));
		return -1;
	}
	c->fd=-1;
	for(l=name;l!=&name[512];l+=n){
		n=&name[512]-l;
		n=read(fd, l, n);
		if(n<=0) break;
	}
	*l='\0';
	s=strchr(name, ' ');
	if(s==0){
		close(fd);
		return -1;
	}
	*s='\0';
	if(strcmp(url->fullname, name)!=0){
		close(fd);
		return -1;
	}
	c->modtime=atol(++s);
	s=strchr(s, '\n');
	if(s==0){
		close(fd);
		return -1;
	}
	s++;
	if(strncmp(s, "type ", 5)!=0){
		close(fd);
		return -1;
	}
	c->type=atoi(s+5);
	s=strchr(s+5, '\n');
	if(s==0){
		close(fd);
		return -1;
	}
	
	seek(fd, s-name+1, 0);
	return fd;
#else
	c->fd=-1;
	return -1;
#endif
}
/*
 * Close url->fd and either rename the cache file or
 * remove it, depending on success
 */
void cacheclose(Cache *c, int success){
	char wname[513], cname[513], *celem;
	Dir *wdir;
	if(c->fd==-1) return;
	close(c->fd);
	hashname(wname, "write", c);
	if(!success){
		remove(wname);
		return;
	}
	if((wdir = dirstat(wname)) == 0)
		return;
	hashname(cname, "cache", c);
	if(access(cname, 0) == 0){
		if(remove(cname)==-1){
			remove(wname);
			free(wdir);
			return;
		}
		/*
		 * This looks implausible, but it's what the mv command does
		 */
		do; while(remove(cname)!=-1);
	}
	celem=strrchr(cname, '/');
	if(celem==0) celem=cname;
	else celem++;
	strcpy(wdir->name, celem);
	if(dirwstat(wname, wdir)==-1)
		remove(wname);
	free(wdir);
}
static char *wkday[]={
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static char *month[]={
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
/*
 * Sun, 06 Nov 1994 08:49:38 GMT
 * 123456789 123456789 123456789
 */
char *rfc1123date(long time){
	static char buf[50];
	Tm *t;
	t=gmtime(time);
	sprint(buf, "%s, %2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT",
		wkday[t->wday], t->mday, month[t->mon], t->year+1900,
		t->hour, t->min, t->sec);
	return buf;
}
/*
 * Given a url, return a file descriptor on which caller can
 * read an http document.  As a side effect, we parse the
 * http header and fill in some fields in the url.
 * The caller is responsible for processing redirection loops.
 * Method can be either GET or POST.  If method==post, body
 * is the text to be posted.
 */
int http(Url *url, int method, char *body){
	char *addr, *com;
	int fd, n, nnl, len;
	int ncom, m;
	int pfd[2];
	char buf[1024], *bp, *ebp;
	char line[1024+1], *lp, *elp;
	char authstr[128], *urlname;
	int gotresponse;
	int response;
	Cache cache;
	int cfd, cookiefd;
	static int firsttime=1;
	static int gotcookies;

	if(firsttime){
		proxyserver=getenv("httpproxy");
		gotcookies=(access("/mnt/webcookies/http", AREAD|AWRITE)==0);
		firsttime=0;
	}
	*authstr = 0;
Authorize:
	cfd=-1;
	cookiefd=-1;
	if(proxyserver && proxyserver[0]!='\0'){
		addr=strdup(proxyserver);
		urlname=url->fullname;
	}
	else{
		addr=emalloc(strlen(url->ipaddr)+100);
		sprint(addr, "tcp!%s!%d", url->ipaddr, url->port);
		urlname=url->reltext;
	}
	fd=dial(addr, 0, 0, 0);
	free(addr);
	if(fd==-1) goto ErrReturn;
	ncom=strlen(urlname)+sizeof(buf);
	com=emalloc(ncom+2);
	cache.fd=-1;
	switch(method){
	case GET:
		cfd=cacheopen(url, &cache, 0);
		if(cfd==-1)
			n=sprint(com,
				"GET %s HTTP/1.0\r\n%s"
				"Accept: */*\r\n"
				"User-agent: mothra/%s\r\n"
				"Host: %s\r\n",
				urlname, authstr, version, url->ipaddr);
		else
			n=sprint(com,
				"GET %s HTTP/1.0\r\n%s"
				"If-Modified-since: %s\r\n"
				"Accept: */*\r\n"
				"User-agent: mothra/%s\r\n"
				"Host: %s\r\n",
				urlname, authstr, rfc1123date(cache.modtime), version, url->ipaddr);
		break;
	case POST:
		len=strlen(body);
		n=sprint(com,
			"POST %s HTTP/1.0\r\n%s"
			"Content-type: application/x-www-form-urlencoded\r\n"
			"Content-length: %d\r\n"
			"User-agent: mothra/%s\r\n",
			urlname, authstr, len, version);
		break;
	}
	if(gotcookies && (cookiefd=open("/mnt/webcookies/http", ORDWR)) >= 0){
		if(fprint(cookiefd, "%s", url->fullname) > 0){
			while((m=read(cookiefd, buf, sizeof buf)) > 0){
				if(m+n>ncom){
					if(write(fd, com, n)!= n){
						free(com);
						goto fdErrReturn;
					}
					n=0;
					com[0] = '\0';
				}
				strncat(com, buf, m);
				n += m;
			}
		}else{
			close(cookiefd);
			cookiefd=-1;
		}
	}
	strcat(com, "\r\n");
	n += 2;
	switch(method){
	case GET:
		if(write(fd, com, n)!=n){
			free(com);
			goto fdErrReturn;
		}
		break;
	case POST:
		if(write(fd, com, n)!=n
		|| write(fd, body, len)!=len){
			free(com);
			goto fdErrReturn;
		}
		break;
	}
	free(com);
	if(pipe(pfd)==-1) goto fdErrReturn;
	n=read(fd, buf, 1024);
	if(n<=0){
	EarlyEof:
		if(n==0){
			fprint(2, "%s: EOF in header\n", url->fullname);
			werrstr("EOF in header");
		}
	pfdErrReturn:
		close(pfd[0]);
		close(pfd[1]);
	fdErrReturn:
		close(fd);
	ErrReturn:
		if(cookiefd>=0)
			close(cookiefd);
		cacheclose(&cache, 0);
		return -1;
	}
	bp=buf;
	ebp=buf+n;
	url->type=0;
	if(strncmp(buf, "HTTP/", 5)==0){	/* hack test for presence of header */
		SET(response);
		gotresponse=0;
		url->redirname[0]='\0';
		nnl=0;
		lp=line;
		elp=line+1024;
		while(nnl!=2){
			if(bp==ebp){
				n=read(fd, buf, 1024);
				if(n<=0) goto EarlyEof;
				ebp=buf+n;
				bp=buf;
			}
			if(*bp!='\r'){
				if(nnl==1 && (!gotresponse || (*bp!=' ' && *bp!='\t'))){
					*lp='\0';
					if(gotresponse){
						if(cookiefd>=0 && cistrncmp(line, "Set-Cookie:", 11) == 0)
							fprint(cookiefd, "%s\n", line);
						httpheader(url, line);
					}else{
						response=httpresponse(line);
						gotresponse=1;
					}
					lp=line;
				}
				if(*bp=='\n') nnl++;
				else{
					nnl=0;
					if(lp!=elp) *lp++=*bp;
				}
			}
			bp++;
		}
		if(gotresponse) switch(response){
		case 200:	/* OK */
		case 201:	/* Created */
		case 202:	/* Accepted */
			break;
		case 204:	/* No Content */
			werrstr("URL has no content");
			goto pfdErrReturn;
		case 301:	/* Moved Permanently */
		case 302:	/* Moved Temporarily */
			if(url->redirname[0]){
				url->type=FORWARD;
				werrstr("URL forwarded");
				goto pfdErrReturn;
			}
			break;
		case 304:	/* Not Modified */
			if(cfd!=-1){
				url->type=cache.type;
				close(pfd[0]);
				close(pfd[1]);
				close(fd);
				if(cookiefd>=0)
					close(cookiefd);
				return cfd;
			}
			werrstr("Not modified!");
			goto pfdErrReturn;
		case 400:	/* Bad Request */
			werrstr("Bad Request to server");
			goto pfdErrReturn;
		case 401:	/* Unauthorized */
		case 402:	/* ??? */
			if(*authstr == 0){
				close(pfd[0]);
				close(pfd[1]);
				close(fd);
				if(auth(url, authstr, sizeof(authstr)) == 0){
					if(cfd!=-1)
						close(cfd);
					goto Authorize;
				}
				goto ErrReturn;
			}
			break;
		case 403:	/* Forbidden */
			werrstr("Forbidden by server");
			goto pfdErrReturn;
		case 404:	/* Not Found */
			werrstr("Not found on server");
			goto pfdErrReturn;
		case 500:	/* Internal server error */
			werrstr("Server choked");
			goto pfdErrReturn;
		case 501:	/* Not implemented */
			werrstr("Server can't do it!");
			goto pfdErrReturn;
		case 502:	/* Bad gateway */
			werrstr("Bad gateway");
			goto pfdErrReturn;
		case 503:	/* Service unavailable */
			werrstr("Service unavailable");
			goto pfdErrReturn;
		}
	}
	if(cfd!=-1){
		close(cfd);
		cfd=cacheopen(url, &cache, 1);
	}
	if(cookiefd>=0){
		close(cookiefd);
		cookiefd=-1;
	}
	if(url->type==0) url->type=suffix2type(url->fullname);
	if(cache.fd!=-1) fprint(cache.fd, "type %d\n", url->type);
	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
	case -1:
		werrstr("Can't fork");
		goto pfdErrReturn;
	case 0:
		notify(exitnow); /* otherwise write on closed pipe below may cause havoc */
		close(pfd[0]);
		if(bp!=ebp){
			write(pfd[1], bp, ebp-bp);
			if(cache.fd!=-1) write(cache.fd, bp, ebp-bp);
		}
		while((n=read(fd, buf, 1024))>0){
			write(pfd[1], buf, n);
			if(cache.fd!=-1) write(cache.fd, buf, n);
		}
		cacheclose(&cache, 1);
		_exits(0);
	default:
		if(cache.fd!=-1) close(cache.fd);
		close(pfd[1]);
		close(fd);
		return pfd[0];
	}
}
/*
 * Process a header line for this url
 */
void httpheader(Url *url, char *line){
	char *name, *arg, *s, *arg2;
	name=line;
	while(*name==' ' || *name=='\t') name++;
	for(s=name;*s!=':';s++) if(*s=='\0') return;
	*s++='\0';
	while(*s==' ' || *s=='\t') s++;
	arg=s;
	while(*s!=' ' && *s!='\t' && *s!=';' && *s!='\0') s++;
	if(*s == ' ' || *s == '\t')
		arg2 = s+1;
	else
		arg2 = s;
	*s='\0';
	if(cistrcmp(name, "Content-Type")==0)
		url->type|=content2type(arg, url->reltext);
	else if(cistrcmp(name, "Content-Encoding")==0)
		url->type|=encoding2type(arg);
	else if(cistrcmp(name, "WWW-authenticate")==0){
		strncpy(url->authtype, arg, sizeof(url->authtype));
		strncpy(url->autharg, arg2, sizeof(url->autharg));
	}
	else if(cistrcmp(name, "URI")==0){
		if(*arg!='<') return;
		++arg;
		for(s=arg;*s!='>';s++) if(*s=='\0') return;
		*s='\0';
		strncpy(url->redirname, arg, sizeof(url->redirname));
	}
	else if(cistrcmp(name, "Location")==0)
		strncpy(url->redirname, arg, sizeof(url->redirname));
}
int httpresponse(char *line){
	while(*line!=' ' && *line!='\t' && *line!='\0') line++;
	return atoi(line);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].