http.c revision 62812
1189251Ssam/*-
2214734Srpaulo * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3214734Srpaulo * All rights reserved.
4189251Ssam *
5252726Srpaulo * Redistribution and use in source and binary forms, with or without
6252726Srpaulo * modification, are permitted provided that the following conditions
7189251Ssam * are met:
8189251Ssam * 1. Redistributions of source code must retain the above copyright
9189251Ssam *    notice, this list of conditions and the following disclaimer
10189251Ssam *    in this position and unchanged.
11189251Ssam * 2. Redistributions in binary form must reproduce the above copyright
12189251Ssam *    notice, this list of conditions and the following disclaimer in the
13189251Ssam *    documentation and/or other materials provided with the distribution.
14189251Ssam * 3. The name of the author may not be used to endorse or promote products
15189251Ssam *    derived from this software without specific prior written permission
16189251Ssam *
17189251Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18189251Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19189251Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20189251Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21189251Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22189251Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23189251Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24189251Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25189251Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26189251Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27189251Ssam *
28189251Ssam * $FreeBSD: head/lib/libfetch/http.c 62811 2000-07-08 08:08:58Z des $
29189251Ssam */
30189251Ssam
31189251Ssam/*
32189251Ssam * The base64 code in this file is based on code from MIT fetch, which
33189251Ssam * has the following copyright and license:
34189251Ssam *
35189251Ssam *-
36189251Ssam * Copyright 1997 Massachusetts Institute of Technology
37189251Ssam *
38189251Ssam * Permission to use, copy, modify, and distribute this software and
39189251Ssam * its documentation for any purpose and without fee is hereby
40189251Ssam * granted, provided that both the above copyright notice and this
41189251Ssam * permission notice appear in all copies, that both the above
42189251Ssam * copyright notice and this permission notice appear in all
43189251Ssam * supporting documentation, and that the name of M.I.T. not be used
44189251Ssam * in advertising or publicity pertaining to distribution of the
45189251Ssam * software without specific, written prior permission.	 M.I.T. makes
46189251Ssam * no representations about the suitability of this software for any
47189251Ssam * purpose.  It is provided "as is" without express or implied
48189251Ssam * warranty.
49189251Ssam *
50189251Ssam * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
51189251Ssam * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
52189251Ssam * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53189251Ssam * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
54189251Ssam * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
55189251Ssam * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
56189251Ssam * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
57189251Ssam * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
58189251Ssam * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59189251Ssam * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
60189251Ssam * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61189251Ssam * SUCH DAMAGE. */
62189251Ssam
63189251Ssam#include <sys/param.h>
64189251Ssam#include <sys/socket.h>
65189251Ssam
66189251Ssam#include <err.h>
67252726Srpaulo#include <ctype.h>
68252726Srpaulo#include <locale.h>
69252726Srpaulo#include <netdb.h>
70252726Srpaulo#include <stdarg.h>
71252726Srpaulo#include <stdio.h>
72252726Srpaulo#include <stdlib.h>
73252726Srpaulo#include <string.h>
74252726Srpaulo#include <time.h>
75189251Ssam#include <unistd.h>
76252726Srpaulo
77252726Srpaulo#include "fetch.h"
78189251Ssam#include "common.h"
79189251Ssam#include "httperr.h"
80189251Ssam
81189251Ssamextern char *__progname;
82189251Ssam
83189251Ssam#define ENDL "\r\n"
84189251Ssam
85189251Ssam#define HTTP_OK		200
86189251Ssam#define HTTP_PARTIAL	206
87189251Ssam#define HTTP_MOVED	302
88189251Ssam
89189251Ssamstruct cookie
90189251Ssam{
91189251Ssam    FILE *real_f;
92189251Ssam#define ENC_NONE 0
93189251Ssam#define ENC_CHUNKED 1
94189251Ssam    int encoding;			/* 1 = chunked, 0 = none */
95189251Ssam#define HTTPCTYPELEN 59
96189251Ssam    char content_type[HTTPCTYPELEN+1];
97189251Ssam    char *buf;
98189251Ssam    int b_cur, eof;
99189251Ssam    unsigned b_len, chunksize;
100189251Ssam};
101189251Ssam
102189251Ssam/*
103189251Ssam * Send a formatted line; optionally echo to terminal
104189251Ssam */
105189251Ssamstatic int
106189251Ssam_http_cmd(FILE *f, char *fmt, ...)
107189251Ssam{
108189251Ssam    va_list ap;
109189251Ssam
110189251Ssam    va_start(ap, fmt);
111189251Ssam    vfprintf(f, fmt, ap);
112189251Ssam#ifndef NDEBUG
113189251Ssam    fprintf(stderr, "\033[1m>>> ");
114189251Ssam    vfprintf(stderr, fmt, ap);
115189251Ssam    fprintf(stderr, "\033[m");
116189251Ssam#endif
117189251Ssam    va_end(ap);
118189251Ssam
119189251Ssam    return 0; /* XXX */
120189251Ssam}
121189251Ssam
122189251Ssam/*
123189251Ssam * Fill the input buffer, do chunk decoding on the fly
124189251Ssam */
125189251Ssamstatic char *
126189251Ssam_http_fillbuf(struct cookie *c)
127189251Ssam{
128189251Ssam    char *ln;
129189251Ssam    unsigned int len;
130189251Ssam
131189251Ssam    if (c->eof)
132189251Ssam	return NULL;
133189251Ssam
134189251Ssam    if (c->encoding == ENC_NONE) {
135189251Ssam	c->buf = fgetln(c->real_f, &(c->b_len));
136189251Ssam	c->b_cur = 0;
137189251Ssam    } else if (c->encoding == ENC_CHUNKED) {
138189251Ssam	if (c->chunksize == 0) {
139189251Ssam	    ln = fgetln(c->real_f, &len);
140189251Ssam	    if (len <= 2)
141189251Ssam		return NULL;
142189251Ssam	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
143189251Ssam			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
144189251Ssam	    sscanf(ln, "%x", &(c->chunksize));
145189251Ssam	    if (!c->chunksize) {
146189251Ssam		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
147189251Ssam			      "end of last chunk\033[m\n"));
148189251Ssam		c->eof = 1;
149189251Ssam		return NULL;
150189251Ssam	    }
151189251Ssam	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
152189251Ssam			  "new chunk: %X\033[m\n", c->chunksize));
153189251Ssam	}
154189251Ssam	c->buf = fgetln(c->real_f, &(c->b_len));
155189251Ssam	if (c->b_len > c->chunksize)
156189251Ssam	    c->b_len = c->chunksize;
157189251Ssam	c->chunksize -= c->b_len;
158189251Ssam	c->b_cur = 0;
159189251Ssam    }
160189251Ssam    else return NULL; /* unknown encoding */
161189251Ssam    return c->buf;
162189251Ssam}
163189251Ssam
164189251Ssam/*
165189251Ssam * Read function
166189251Ssam */
167189251Ssamstatic int
168189251Ssam_http_readfn(struct cookie *c, char *buf, int len)
169189251Ssam{
170189251Ssam    int l, pos = 0;
171189251Ssam    while (len) {
172189251Ssam	/* empty buffer */
173189251Ssam	if (!c->buf || (c->b_cur == c->b_len))
174189251Ssam	    if (!_http_fillbuf(c))
175189251Ssam		break;
176189251Ssam
177189251Ssam	l = c->b_len - c->b_cur;
178189251Ssam	if (len < l) l = len;
179189251Ssam	memcpy(buf + pos, c->buf + c->b_cur, l);
180189251Ssam	c->b_cur += l;
181189251Ssam	pos += l;
182189251Ssam	len -= l;
183252726Srpaulo    }
184252726Srpaulo
185252726Srpaulo    if (ferror(c->real_f))
186252726Srpaulo	return -1;
187252726Srpaulo    else return pos;
188252726Srpaulo}
189252726Srpaulo
190252726Srpaulo/*
191252726Srpaulo * Write function
192252726Srpaulo */
193252726Srpaulostatic int
194252726Srpaulo_http_writefn(struct cookie *c, const char *buf, int len)
195252726Srpaulo{
196252726Srpaulo    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
197252726Srpaulo    return r ? r : -1;
198252726Srpaulo}
199252726Srpaulo
200252726Srpaulo/*
201189251Ssam * Close function
202252726Srpaulo */
203189251Ssamstatic int
204189251Ssam_http_closefn(struct cookie *c)
205189251Ssam{
206189251Ssam    int r = fclose(c->real_f);
207189251Ssam    free(c);
208189251Ssam    return (r == EOF) ? -1 : 0;
209189251Ssam}
210189251Ssam
211189251Ssam/*
212189251Ssam * Extract content type from cookie
213189251Ssam */
214189251Ssamchar *
215189251SsamfetchContentType(FILE *f)
216189251Ssam{
217189251Ssam    /*
218189251Ssam     * We have no way of making sure this really *is* one of our cookies,
219189251Ssam     * so just check for a null pointer and hope for the best.
220189251Ssam     */
221189251Ssam    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
222189251Ssam}
223189251Ssam
224189251Ssam/*
225189251Ssam * Base64 encoding
226189251Ssam */
227189251Ssamint
228189251Ssam_http_base64(char *dst, char *src, int l)
229189251Ssam{
230189251Ssam    static const char base64[] =
231189251Ssam	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
232189251Ssam	"abcdefghijklmnopqrstuvwxyz"
233189251Ssam	"0123456789+/";
234189251Ssam    int t, r = 0;
235189251Ssam
236189251Ssam    while (l >= 3) {
237189251Ssam	t = (src[0] << 16) | (src[1] << 8) | src[2];
238189251Ssam	dst[0] = base64[(t >> 18) & 0x3f];
239189251Ssam	dst[1] = base64[(t >> 12) & 0x3f];
240189251Ssam	dst[2] = base64[(t >> 6) & 0x3f];
241189251Ssam	dst[3] = base64[(t >> 0) & 0x3f];
242189251Ssam	src += 3; l -= 3;
243189251Ssam	dst += 4; r += 4;
244189251Ssam    }
245189251Ssam
246189251Ssam    switch (l) {
247189251Ssam    case 2:
248189251Ssam	t = (src[0] << 16) | (src[1] << 8);
249189251Ssam	dst[0] = base64[(t >> 18) & 0x3f];
250189251Ssam	dst[1] = base64[(t >> 12) & 0x3f];
251189251Ssam	dst[2] = base64[(t >> 6) & 0x3f];
252189251Ssam	dst[3] = '=';
253189251Ssam	dst += 4;
254189251Ssam	r += 4;
255189251Ssam	break;
256189251Ssam    case 1:
257189251Ssam	t = src[0] << 16;
258189251Ssam	dst[0] = base64[(t >> 18) & 0x3f];
259189251Ssam	dst[1] = base64[(t >> 12) & 0x3f];
260189251Ssam	dst[2] = dst[3] = '=';
261189251Ssam	dst += 4;
262189251Ssam	r += 4;
263189251Ssam	break;
264189251Ssam    case 0:
265189251Ssam	break;
266189251Ssam    }
267189251Ssam
268189251Ssam    *dst = 0;
269189251Ssam    return r;
270189251Ssam}
271189251Ssam
272189251Ssam/*
273189251Ssam * Encode username and password
274189251Ssam */
275189251Ssamchar *
276189251Ssam_http_auth(char *usr, char *pwd)
277189251Ssam{
278189251Ssam    int len, lup;
279189251Ssam    char *uandp, *str = NULL;
280189251Ssam
281189251Ssam    lup = strlen(usr) + 1 + strlen(pwd);/* length of "usr:pwd" */
282189251Ssam    uandp = (char*)malloc(lup + 1);
283189251Ssam    if (uandp) {
284189251Ssam	len = ((lup + 2) / 3) * 4;	/* length of base64 encoded "usr:pwd" incl. padding */
285189251Ssam	str = (char*)malloc(len + 1);
286189251Ssam	if (str) {
287189251Ssam	    strcpy(uandp, usr);
288189251Ssam	    strcat(uandp, ":");
289189251Ssam	    strcat(uandp, pwd);
290189251Ssam	    _http_base64(str, uandp, lup);
291189251Ssam	}
292189251Ssam	free(uandp);
293189251Ssam    }
294189251Ssam    return str;
295189251Ssam}
296189251Ssam
297189251Ssam/*
298189251Ssam * Connect to server or proxy
299189251Ssam */
300189251SsamFILE *
301189251Ssam_http_connect(struct url *URL, char *flags)
302189251Ssam{
303189251Ssam    int direct, sd = -1, verbose;
304189251Ssam#ifdef INET6
305189251Ssam    int af = AF_UNSPEC;
306189251Ssam#else
307189251Ssam    int af = AF_INET;
308189251Ssam#endif
309189251Ssam    size_t len;
310189251Ssam    char *px;
311189251Ssam    FILE *f;
312189251Ssam
313189251Ssam    direct = (flags && strchr(flags, 'd'));
314189251Ssam    verbose = (flags && strchr(flags, 'v'));
315189251Ssam    if ((flags && strchr(flags, '4')))
316189251Ssam	af = AF_INET;
317189251Ssam    else if ((flags && strchr(flags, '6')))
318189251Ssam	af = AF_INET6;
319189251Ssam
320189251Ssam    /* check port */
321189251Ssam    if (!URL->port) {
322189251Ssam	struct servent *se;
323189251Ssam
324189251Ssam	if (strcasecmp(URL->scheme, "ftp") == 0)
325189251Ssam	    if ((se = getservbyname("ftp", "tcp")) != NULL)
326189251Ssam		URL->port = ntohs(se->s_port);
327189251Ssam	    else
328189251Ssam		URL->port = 21;
329189251Ssam	else
330189251Ssam	    if ((se = getservbyname("http", "tcp")) != NULL)
331189251Ssam		URL->port = ntohs(se->s_port);
332189251Ssam	    else
333189251Ssam		URL->port = 80;
334189251Ssam    }
335189251Ssam
336189251Ssam    /* attempt to connect to proxy server */
337189251Ssam    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
338189251Ssam	char host[MAXHOSTNAMELEN];
339189251Ssam	int port = 0;
340189251Ssam
341189251Ssam	/* measure length */
342189251Ssam#ifdef INET6
343189251Ssam	if (px[0] != '[' ||
344189251Ssam	    (len = strcspn(px, "]")) >= strlen(px) ||
345189251Ssam	    (px[++len] != '\0' && px[len] != ':'))
346189251Ssam#endif
347189251Ssam	    len = strcspn(px, ":");
348189251Ssam
349189251Ssam	/* get port (XXX atoi is a little too tolerant perhaps?) */
350189251Ssam	if (px[len] == ':') {
351189251Ssam	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
352189251Ssam		|| strlen(px+len+1) > 5) {
353189251Ssam		/* XXX we should emit some kind of warning */
354189251Ssam	    }
355189251Ssam	    port = atoi(px+len+1);
356189251Ssam	    if (port < 1 || port > 65535) {
357189251Ssam		/* XXX we should emit some kind of warning */
358189251Ssam	    }
359189251Ssam	}
360189251Ssam	if (!port) {
361189251Ssam#if 0
362189251Ssam	    /*
363189251Ssam	     * commented out, since there is currently no service name
364189251Ssam	     * for HTTP proxies
365189251Ssam	     */
366189251Ssam	    struct servent *se;
367189251Ssam
368189251Ssam	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
369189251Ssam		port = ntohs(se->s_port);
370189251Ssam	    else
371189251Ssam#endif
372189251Ssam		port = 3128;
373189251Ssam	}
374189251Ssam
375189251Ssam	/* get host name */
376189251Ssam#ifdef INET6
377189251Ssam	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
378189251Ssam	    px++;
379189251Ssam	    len -= 2;
380189251Ssam	}
381189251Ssam#endif
382189251Ssam	if (len >= MAXHOSTNAMELEN)
383189251Ssam	    len = MAXHOSTNAMELEN - 1;
384189251Ssam	strncpy(host, px, len);
385189251Ssam	host[len] = 0;
386189251Ssam
387189251Ssam	/* connect */
388189251Ssam	sd = _fetch_connect(host, port, af, verbose);
389189251Ssam    }
390189251Ssam
391189251Ssam    /* if no proxy is configured or could be contacted, try direct */
392189251Ssam    if (sd == -1) {
393189251Ssam	if (strcasecmp(URL->scheme, "ftp") == 0)
394189251Ssam	    goto ouch;
395189251Ssam	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
396189251Ssam	    goto ouch;
397189251Ssam    }
398189251Ssam
399189251Ssam    /* reopen as stream */
400189251Ssam    if ((f = fdopen(sd, "r+")) == NULL)
401189251Ssam	goto ouch;
402189251Ssam
403189251Ssam    return f;
404189251Ssam
405214734Srpauloouch:
406214734Srpaulo    if (sd >= 0)
407214734Srpaulo	close(sd);
408214734Srpaulo    _http_seterr(999); /* XXX do this properly RSN */
409214734Srpaulo    return NULL;
410214734Srpaulo}
411189251Ssam
412189251Ssam/*
413189251Ssam * Check a header line
414189251Ssam */
415189251Ssamchar *
416189251Ssam_http_match(char *str, char *hdr)
417189251Ssam{
418189251Ssam    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
419189251Ssam	/* nothing */;
420214734Srpaulo    if (*str || *hdr != ':')
421214734Srpaulo	return NULL;
422214734Srpaulo    while (*hdr && isspace(*++hdr))
423214734Srpaulo	/* nothing */;
424214734Srpaulo    return hdr;
425214734Srpaulo}
426214734Srpaulo
427214734Srpaulo/*
428189251Ssam * Send a HEAD or GET request
429189251Ssam */
430189251Ssamint
431189251Ssam_http_request(FILE *f, char *op, struct url *URL, char *flags)
432189251Ssam{
433189251Ssam    int e, verbose;
434189251Ssam    char *ln, *p;
435189251Ssam    size_t len;
436189251Ssam    char *host;
437189251Ssam#ifdef INET6
438189251Ssam    char hbuf[MAXHOSTNAMELEN + 1];
439189251Ssam#endif
440189251Ssam
441189251Ssam    verbose = (flags && strchr(flags, 'v'));
442189251Ssam
443189251Ssam    host = URL->host;
444189251Ssam#ifdef INET6
445189251Ssam    if (strchr(URL->host, ':')) {
446189251Ssam	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
447189251Ssam	host = hbuf;
448189251Ssam    }
449189251Ssam#endif
450189251Ssam
451189251Ssam    /* send request (proxies require absolute form, so use that) */
452189251Ssam    if (verbose)
453189251Ssam	_fetch_info("requesting %s://%s:%d%s",
454189251Ssam		    URL->scheme, host, URL->port, URL->doc);
455189251Ssam    _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
456189251Ssam	      op, URL->scheme, host, URL->port, URL->doc);
457189251Ssam
458189251Ssam    /* start sending headers away */
459189251Ssam    if (URL->user[0] || URL->pwd[0]) {
460189251Ssam	char *auth_str = _http_auth(URL->user, URL->pwd);
461189251Ssam	if (!auth_str)
462189251Ssam	    return 999; /* XXX wrong */
463189251Ssam	_http_cmd(f, "Authorization: Basic %s" ENDL, auth_str);
464189251Ssam	free(auth_str);
465189251Ssam    }
466189251Ssam    if (p = getenv("HTTP_PROXY_AUTH")) {
467189251Ssam	char *auth;
468189251Ssam
469189251Ssam	/* skip leading "basic:*:", if present */
470189251Ssam	if (strncmp(p, "basic:*:", 6 + 2) == 0)
471189251Ssam	    p += 6 + 2;
472189251Ssam	auth = strchr(p, ':');
473189251Ssam	if (auth != NULL) {
474189251Ssam	    int len = auth - p;
475189251Ssam	    char *user;
476189251Ssam	    char *auth_str;
477189251Ssam
478189251Ssam	    if ((user = (char*)malloc(len + 1)) == NULL) {
479189251Ssam		free(auth);
480189251Ssam		return 999; /* XXX wrong */
481189251Ssam	    }
482189251Ssam	    strncpy(user, p, len);
483189251Ssam	    user[len] = 0;
484189251Ssam	    auth++;
485189251Ssam	    auth_str = _http_auth(user, auth);
486189251Ssam	    free(user);
487189251Ssam	    if (auth_str == NULL)
488189251Ssam		return 999; /* XXX wrong */
489252726Srpaulo	    _http_cmd(f, "Proxy-Authorization: Basic %s" ENDL, auth_str);
490252726Srpaulo	    free(auth_str);
491252726Srpaulo	} else {
492252726Srpaulo	    return 999; /* XXX wrong */
493252726Srpaulo	}
494252726Srpaulo    }
495252726Srpaulo    _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
496252726Srpaulo    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
497189251Ssam    if (URL->offset)
498189251Ssam	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
499189251Ssam    _http_cmd(f, "Connection: close" ENDL ENDL);
500189251Ssam
501189251Ssam    /* get response */
502189251Ssam    if ((ln = fgetln(f, &len)) == NULL)
503189251Ssam	return 999;
504189251Ssam    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
505189251Ssam		  (int)len-2, (int)len-2, ln));
506189251Ssam
507189251Ssam    /* we can't use strchr() and friends since ln isn't NUL-terminated */
508189251Ssam    p = ln;
509189251Ssam    while ((p < ln + len) && !isspace(*p))
510189251Ssam	p++;
511189251Ssam    while ((p < ln + len) && !isdigit(*p))
512189251Ssam	p++;
513189251Ssam    if (!isdigit(*p))
514189251Ssam	return 999;
515189251Ssam
516189251Ssam    e = atoi(p);
517189251Ssam    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
518189251Ssam    return e;
519189251Ssam}
520189251Ssam
521189251Ssam/*
522189251Ssam * Retrieve a file by HTTP
523189251Ssam */
524189251SsamFILE *
525189251SsamfetchGetHTTP(struct url *URL, char *flags)
526189251Ssam{
527189251Ssam    int e, enc = ENC_NONE, i, noredirect;
528189251Ssam    struct cookie *c;
529189251Ssam    char *ln, *p, *q;
530189251Ssam    FILE *f, *cf;
531189251Ssam    size_t len;
532189251Ssam    off_t pos = 0;
533189251Ssam
534189251Ssam    noredirect = (flags && strchr(flags, 'A'));
535189251Ssam
536189251Ssam    /* allocate cookie */
537189251Ssam    if ((c = calloc(1, sizeof *c)) == NULL)
538189251Ssam	return NULL;
539189251Ssam
540    /* connect */
541    if ((f = _http_connect(URL, flags)) == NULL) {
542	free(c);
543	return NULL;
544    }
545    c->real_f = f;
546
547    e = _http_request(f, "GET", URL, flags);
548    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)
549	&& (e != HTTP_MOVED || noredirect)) {
550	_http_seterr(e);
551	free(c);
552	fclose(f);
553	return NULL;
554    }
555
556    /* browse through header */
557    while (1) {
558	if ((ln = fgetln(f, &len)) == NULL)
559	    goto fouch;
560	if ((ln[0] == '\r') || (ln[0] == '\n'))
561	    break;
562	while (isspace(ln[len-1]))
563	    --len;
564	ln[len] = '\0'; /* XXX */
565	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
566	if ((p = _http_match("Location", ln)) != NULL) {
567	    struct url *url;
568
569	    for (q = p; *q && !isspace(*q); q++)
570		/* VOID */ ;
571	    *q = 0;
572	    if ((url = fetchParseURL(p)) == NULL)
573		goto fouch;
574	    url->offset = URL->offset;
575	    url->length = URL->length;
576	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
577	    cf = fetchGetHTTP(url, flags);
578	    fetchFreeURL(url);
579	    fclose(f);
580	    return cf;
581	} else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
582	    for (q = p; *q && !isspace(*q); q++)
583		/* VOID */ ;
584	    *q = 0;
585	    if (strcasecmp(p, "chunked") == 0)
586		enc = ENC_CHUNKED;
587	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
588	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
589	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
590		    c->content_type[i] = *p;
591	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
592	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
593			  c->content_type));
594	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
595	    if (strncasecmp(p, "bytes ", 6) != 0)
596		goto fouch;
597	    p += 6;
598	    while (*p && isdigit(*p))
599		pos = pos * 10 + (*p++ - '0');
600	    /* XXX wouldn't hurt to be slightly more paranoid here */
601	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
602	    if (pos > URL->offset)
603		goto fouch;
604	}
605    }
606
607    /* only body remains */
608    c->encoding = enc;
609    cf = funopen(c,
610		 (int (*)(void *, char *, int))_http_readfn,
611		 (int (*)(void *, const char *, int))_http_writefn,
612		 (fpos_t (*)(void *, fpos_t, int))NULL,
613		 (int (*)(void *))_http_closefn);
614    if (cf == NULL)
615	goto fouch;
616
617    while (pos < URL->offset)
618	if (fgetc(cf) == EOF)
619	    goto cfouch;
620
621    return cf;
622
623fouch:
624    fclose(f);
625    free(c);
626    _http_seterr(999); /* XXX do this properly RSN */
627    return NULL;
628cfouch:
629    fclose(cf);
630    _http_seterr(999); /* XXX do this properly RSN */
631    return NULL;
632}
633
634FILE *
635fetchPutHTTP(struct url *URL, char *flags)
636{
637    warnx("fetchPutHTTP(): not implemented");
638    return NULL;
639}
640
641/*
642 * Get an HTTP document's metadata
643 */
644int
645fetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
646{
647    int e, noredirect;
648    size_t len;
649    char *ln, *p, *q;
650    FILE *f;
651
652    noredirect = (flags && strchr(flags, 'A'));
653
654    us->size = -1;
655    us->atime = us->mtime = 0;
656
657    /* connect */
658    if ((f = _http_connect(URL, flags)) == NULL)
659	return -1;
660
661    e = _http_request(f, "HEAD", URL, flags);
662    if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) {
663	_http_seterr(e);
664	fclose(f);
665	return -1;
666    }
667
668    while (1) {
669	if ((ln = fgetln(f, &len)) == NULL)
670	    goto fouch;
671	if ((ln[0] == '\r') || (ln[0] == '\n'))
672	    break;
673	while (isspace(ln[len-1]))
674	    --len;
675	ln[len] = '\0'; /* XXX */
676	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
677	if ((p = _http_match("Location", ln)) != NULL) {
678	    struct url *url;
679
680	    for (q = p; *q && !isspace(*q); q++)
681		/* VOID */ ;
682	    *q = 0;
683	    if ((url = fetchParseURL(p)) == NULL)
684		goto ouch;
685	    url->offset = URL->offset;
686	    url->length = URL->length;
687	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
688	    e = fetchStatHTTP(url, us, flags);
689	    fetchFreeURL(url);
690	    fclose(f);
691	    return e;
692	} else if ((p = _http_match("Last-Modified", ln)) != NULL) {
693	    struct tm tm;
694	    char locale[64];
695
696	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
697	    setlocale(LC_TIME, "C");
698	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
699	    /* XXX should add support for date-2 and date-3 */
700	    setlocale(LC_TIME, locale);
701	    us->atime = us->mtime = timegm(&tm);
702	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
703			  "%02d:%02d:%02d\033[m]\n",
704			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
705			  tm.tm_hour, tm.tm_min, tm.tm_sec));
706	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
707	    us->size = 0;
708	    while (*p && isdigit(*p))
709		us->size = us->size * 10 + (*p++ - '0');
710	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
711	}
712    }
713
714    fclose(f);
715    return 0;
716 ouch:
717    _http_seterr(999); /* XXX do this properly RSN */
718 fouch:
719    fclose(f);
720    return -1;
721}
722
723/*
724 * List a directory
725 */
726struct url_ent *
727fetchListHTTP(struct url *url, char *flags)
728{
729    warnx("fetchListHTTP(): not implemented");
730    return NULL;
731}
732