http.c revision 37573
1217355Sjkim/*-
2217355Sjkim * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3217355Sjkim * All rights reserved.
4217355Sjkim *
5217355Sjkim * Redistribution and use in source and binary forms, with or without
6217355Sjkim * modification, are permitted provided that the following conditions
7217355Sjkim * are met:
8245582Sjkim * 1. Redistributions of source code must retain the above copyright
9217355Sjkim *    notice, this list of conditions and the following disclaimer
10217355Sjkim *    in this position and unchanged.
11217355Sjkim * 2. Redistributions in binary form must reproduce the above copyright
12217355Sjkim *    notice, this list of conditions and the following disclaimer in the
13217355Sjkim *    documentation and/or other materials provided with the distribution.
14217355Sjkim * 3. The name of the author may not be used to endorse or promote products
15217355Sjkim *    derived from this software without specific prior written permission
16217355Sjkim *
17217355Sjkim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18217355Sjkim * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19217355Sjkim * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20217355Sjkim * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21217355Sjkim * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22217355Sjkim * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23217355Sjkim * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24217355Sjkim * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25217355Sjkim * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26217355Sjkim * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27217355Sjkim *
28217355Sjkim *	$Id: http.c,v 1.1.1.1 1998/07/09 16:52:41 des Exp $
29217355Sjkim */
30217355Sjkim
31217355Sjkim#include <sys/param.h>
32217355Sjkim#include <sys/errno.h>
33217355Sjkim#include <sys/socket.h>
34217355Sjkim#include <sys/types.h>
35217355Sjkim
36217355Sjkim#include <netinet/in.h>
37217355Sjkim
38217355Sjkim#include <err.h>
39217355Sjkim#include <ctype.h>
40217355Sjkim#include <netdb.h>
41217355Sjkim#include <stdio.h>
42217355Sjkim#include <stdlib.h>
43217355Sjkim#include <string.h>
44217355Sjkim#include <unistd.h>
45217365Sjkim
46217355Sjkim#include "fetch.h"
47217355Sjkim#include "httperr.c"
48217355Sjkim
49217355Sjkim#ifndef NDEBUG
50217355Sjkim#define DEBUG(x) do x; while (0)
51217355Sjkim#else
52217355Sjkim#define DEBUG(x) do { } while (0)
53217355Sjkim#endif
54217355Sjkim
55217355Sjkimextern char *__progname;
56217355Sjkim
57217355Sjkimextern int fprint64(FILE *f, const unsigned char *buf);
58217355Sjkim
59217355Sjkim#define ENDL "\r\n"
60217355Sjkim
61217355Sjkimstruct cookie
62217355Sjkim{
63217355Sjkim    FILE *real_f;
64217355Sjkim#define ENC_NONE 0
65217355Sjkim#define ENC_CHUNKED 1
66217355Sjkim    int encoding;			/* 1 = chunked, 0 = none */
67217355Sjkim#define HTTPCTYPELEN 59
68217355Sjkim    char content_type[HTTPCTYPELEN+1];
69217355Sjkim    char *buf;
70217355Sjkim    int b_cur, eof;
71217355Sjkim    unsigned b_len, chunksize;
72217355Sjkim};
73217355Sjkim
74217355Sjkimstatic const char *
75217355Sjkim_http_errstring(int e)
76217355Sjkim{
77217355Sjkim    struct httperr *p = _http_errlist;
78217355Sjkim
79217355Sjkim    while ((p->num != -1) && (p->num != e))
80217355Sjkim	p++;
81217355Sjkim
82217355Sjkim    return p->string;
83217355Sjkim}
84217355Sjkim
85217355Sjkimstatic char *
86217355Sjkim_http_fillbuf(struct cookie *c)
87217355Sjkim{
88217355Sjkim    char *ln;
89217355Sjkim    unsigned int len;
90217355Sjkim
91217355Sjkim    if (c->eof)
92217355Sjkim	return NULL;
93217355Sjkim
94217355Sjkim    if (c->encoding == ENC_NONE) {
95217355Sjkim	c->buf = fgetln(c->real_f, &(c->b_len));
96217355Sjkim	c->b_cur = 0;
97217355Sjkim    } else if (c->encoding == ENC_CHUNKED) {
98217355Sjkim	if (c->chunksize == 0) {
99217355Sjkim	    ln = fgetln(c->real_f, &len);
100217355Sjkim	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
101217355Sjkim			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
102217355Sjkim	    sscanf(ln, "%x", &(c->chunksize));
103217355Sjkim	    if (!c->chunksize) {
104217355Sjkim		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
105217355Sjkim			      "end of last chunk\033[m\n"));
106217355Sjkim		c->eof = 1;
107217355Sjkim		return NULL;
108217355Sjkim	    }
109217355Sjkim	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
110217355Sjkim			  "new chunk: %X\033[m\n", c->chunksize));
111217355Sjkim	}
112217355Sjkim	c->buf = fgetln(c->real_f, &(c->b_len));
113217355Sjkim	if (c->b_len > c->chunksize)
114217355Sjkim	    c->b_len = c->chunksize;
115217355Sjkim	c->chunksize -= c->b_len;
116217355Sjkim	c->b_cur = 0;
117217355Sjkim    }
118217355Sjkim    else return NULL; /* unknown encoding */
119217355Sjkim    return c->buf;
120217355Sjkim}
121217355Sjkim
122217355Sjkimstatic int
123217355Sjkim_http_readfn(struct cookie *c, char *buf, int len)
124217355Sjkim{
125217355Sjkim    int l, pos = 0;
126217355Sjkim    while (len) {
127217355Sjkim	/* empty buffer */
128217355Sjkim	if (!c->buf || (c->b_cur == c->b_len))
129217355Sjkim	    if (!_http_fillbuf(c))
130217355Sjkim		break;
131217355Sjkim
132217355Sjkim	l = c->b_len - c->b_cur;
133217355Sjkim	if (len < l) l = len;
134217355Sjkim	memcpy(buf + pos, c->buf + c->b_cur, l);
135217355Sjkim	c->b_cur += l;
136217355Sjkim	pos += l;
137217355Sjkim	len -= l;
138217355Sjkim    }
139217355Sjkim
140217355Sjkim    if (ferror(c->real_f))
141217355Sjkim	return -1;
142217355Sjkim    else return pos;
143217355Sjkim}
144217355Sjkim
145217355Sjkimstatic int
146217355Sjkim_http_writefn(struct cookie *c, const char *buf, int len)
147217355Sjkim{
148217355Sjkim    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
149217355Sjkim    return r ? r : -1;
150217355Sjkim}
151217355Sjkim
152217355Sjkimstatic int
153217355Sjkim_http_closefn(struct cookie *c)
154217355Sjkim{
155217355Sjkim    int r = fclose(c->real_f);
156217355Sjkim    free(c);
157217355Sjkim    return (r == EOF) ? -1 : 0;
158217355Sjkim}
159217355Sjkim
160217355Sjkimchar *
161217355SjkimfetchContentType(FILE *f)
162217355Sjkim{
163217355Sjkim    /*
164217355Sjkim     * We have no way of making sure this really *is* one of our cookies,
165217355Sjkim     * so just check for a null pointer and hope for the best.
166217355Sjkim     */
167217355Sjkim    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
168217355Sjkim}
169217355Sjkim
170217355SjkimFILE *
171217355SjkimfetchGetHTTP(url_t *URL, char *flags)
172217355Sjkim{
173217355Sjkim    int sd = -1, err, i, enc = ENC_NONE;
174217355Sjkim    struct cookie *c;
175217355Sjkim    char *ln, *p, *q;
176217355Sjkim    FILE *f, *cf;
177217355Sjkim    size_t len;
178217355Sjkim
179217355Sjkim    /* allocate cookie */
180217355Sjkim    if ((c = calloc(1, sizeof(struct cookie))) == NULL)
181217355Sjkim	return NULL;
182217355Sjkim
183217355Sjkim    /* check port */
184217355Sjkim    if (!URL->port)
185217355Sjkim	URL->port = 80; /* default HTTP port */
186217355Sjkim
187217355Sjkim    /* attempt to connect to proxy server */
188217355Sjkim    if (getenv("HTTP_PROXY")) {
189217355Sjkim	char *px, host[MAXHOSTNAMELEN];
190217355Sjkim	int port = 3128; /* XXX I think 3128 is default... check? */
191217355Sjkim	size_t len;
192217355Sjkim
193217355Sjkim	/* measure length */
194217355Sjkim	px = getenv("HTTP_PROXY");
195217355Sjkim	len = strcspn(px, ":");
196217355Sjkim
197217355Sjkim	/* get port (atoi is a little too tolerant perhaps?) */
198217355Sjkim	if (px[len] == ':')
199217355Sjkim	    port = atoi(px+len+1);
200217355Sjkim
201217355Sjkim	/* get host name */
202217355Sjkim	if (len >= MAXHOSTNAMELEN)
203217355Sjkim	    len = MAXHOSTNAMELEN - 1;
204217355Sjkim	strncpy(host, px, len);
205217355Sjkim	host[len] = 0;
206217355Sjkim
207217355Sjkim	/* connect */
208217355Sjkim	sd = fetchConnect(host, port);
209217355Sjkim    }
210217355Sjkim
211217355Sjkim    /* if no proxy is configured or could be contacted, try direct */
212217355Sjkim    if (sd < 0) {
213217355Sjkim	if ((sd = fetchConnect(URL->host, URL->port)) < 0)
214217355Sjkim	    goto ouch;
215217355Sjkim    }
216217355Sjkim
217    /* reopen as stream */
218    if ((f = fdopen(sd, "r+")) == NULL)
219	goto ouch;
220    c->real_f = f;
221
222    /* send request (proxies require absolute form, so use that) */
223    fprintf(f, "GET http://%s:%d/%s HTTP/1.1" ENDL,
224	    URL->host, URL->port, URL->doc);
225
226    /* start sending headers away */
227    if (URL->user[0] || URL->pwd[0]) {
228	fprintf(f, "Authorization: Basic ");
229	fprint64(f, (const unsigned char *)URL->user);
230	fputc(':', f);
231	fprint64(f, (const unsigned char *)URL->pwd);
232	fputs(ENDL, f);
233    }
234    fprintf(f, "Host: %s:%d" ENDL, URL->host, URL->port);
235    fprintf(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
236    fprintf(f, "Connection: close" ENDL ENDL);
237
238    /* get response */
239    if ((ln = fgetln(f, &len)) == NULL)
240	goto fouch;
241    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
242		  (int)len-2, (int)len-2, ln));
243
244    /* we can't use strchr() and friends since ln isn't NUL-terminated */
245    p = ln;
246    while ((p < ln + len) && !isspace(*p))
247	p++;
248    while ((p < ln + len) && !isdigit(*p))
249	p++;
250    if (!isdigit(*p))
251	goto fouch;
252    err = atoi(p);
253    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", err));
254
255    /* add code to handle redirects later */
256    if (err != 200) {
257	fetchLastErrCode = err;
258	fetchLastErrText = _http_errstring(err);
259	goto fouch;
260    }
261
262    /* browse through header */
263    while (1) {
264	if ((ln = fgetln(f, &len)) == NULL)
265	    goto fouch;
266	if ((ln[0] == '\r') || (ln[0] == '\n'))
267	    break;
268	DEBUG(fprintf(stderr, "header:   [\033[1m%*.*s\033[m]\n",
269		      (int)len-2, (int)len-2, ln));
270#define XFERENC "Transfer-Encoding:"
271	if (strncasecmp(ln, XFERENC, sizeof(XFERENC)-1) == 0) {
272	    p = ln + sizeof(XFERENC) - 1;
273	    while ((p < ln + len) && isspace(*p))
274		p++;
275	    for (q = p; (q < ln + len) && !isspace(*q); q++)
276		/* VOID */ ;
277	    *q = 0;
278	    if (strcasecmp(p, "chunked") == 0)
279		enc = ENC_CHUNKED;
280	    DEBUG(fprintf(stderr, "xferenc:  [\033[1m%s\033[m]\n", p));
281#undef XFERENC
282#define CONTTYPE "Content-Type:"
283	} else if (strncasecmp(ln, CONTTYPE, sizeof(CONTTYPE)-1) == 0) {
284	    p = ln + sizeof(CONTTYPE) - 1;
285	    while ((p < ln + len) && isspace(*p))
286		p++;
287	    for (i = 0; p < ln + len; p++)
288		if (i < HTTPCTYPELEN)
289		    c->content_type[i++] = *p;
290	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
291	    DEBUG(fprintf(stderr, "conttype: [\033[1m%s\033[m]\n",
292			  c->content_type));
293#undef CONTTYPE
294	}
295    }
296
297    /* only body remains */
298    c->encoding = enc;
299    cf = funopen(c,
300		 (int (*)(void *, char *, int))_http_readfn,
301		 (int (*)(void *, const char *, int))_http_writefn,
302		 (fpos_t (*)(void *, fpos_t, int))NULL,
303		 (int (*)(void *))_http_closefn);
304    if (cf == NULL)
305	goto fouch;
306    return cf;
307
308ouch:
309    if (sd >= 0)
310	close(sd);
311    free(c);
312    return NULL;
313fouch:
314    fclose(f);
315    free(c);
316    return NULL;
317}
318
319FILE *
320fetchPutHTTP(url_t *URL, char *flags)
321{
322    warnx("fetchPutHTTP(): not implemented");
323    return NULL;
324}
325