http.c revision 62965
137535Sdes/*-
237535Sdes * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
337535Sdes * All rights reserved.
437535Sdes *
537535Sdes * Redistribution and use in source and binary forms, with or without
637535Sdes * modification, are permitted provided that the following conditions
737535Sdes * are met:
837535Sdes * 1. Redistributions of source code must retain the above copyright
937535Sdes *    notice, this list of conditions and the following disclaimer
1037535Sdes *    in this position and unchanged.
1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright
1237535Sdes *    notice, this list of conditions and the following disclaimer in the
1337535Sdes *    documentation and/or other materials provided with the distribution.
1437535Sdes * 3. The name of the author may not be used to endorse or promote products
1537535Sdes *    derived from this software without specific prior written permission
1637535Sdes *
1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2737535Sdes *
2850476Speter * $FreeBSD: head/lib/libfetch/http.c 62965 2000-07-11 18:12:41Z des $
2937535Sdes */
3037535Sdes
3137608Sdes/*
3237608Sdes * The base64 code in this file is based on code from MIT fetch, which
3337608Sdes * has the following copyright and license:
3437608Sdes *
3537608Sdes *-
3637608Sdes * Copyright 1997 Massachusetts Institute of Technology
3737608Sdes *
3837608Sdes * Permission to use, copy, modify, and distribute this software and
3937608Sdes * its documentation for any purpose and without fee is hereby
4037608Sdes * granted, provided that both the above copyright notice and this
4137608Sdes * permission notice appear in all copies, that both the above
4237608Sdes * copyright notice and this permission notice appear in all
4337608Sdes * supporting documentation, and that the name of M.I.T. not be used
4437608Sdes * in advertising or publicity pertaining to distribution of the
4560189Sdes * software without specific, written prior permission.	 M.I.T. makes
4637608Sdes * no representations about the suitability of this software for any
4737608Sdes * purpose.  It is provided "as is" without express or implied
4837608Sdes * warranty.
4937608Sdes *
5037608Sdes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
5137608Sdes * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
5237608Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5337608Sdes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
5437608Sdes * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5537608Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
5637608Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
5737608Sdes * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
5837608Sdes * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
5937608Sdes * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6037608Sdes * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6137608Sdes * SUCH DAMAGE. */
6237608Sdes
6337535Sdes#include <sys/param.h>
6460737Sume#include <sys/socket.h>
6537535Sdes
6637535Sdes#include <err.h>
6737535Sdes#include <ctype.h>
6860376Sdes#include <locale.h>
6960189Sdes#include <netdb.h>
7037608Sdes#include <stdarg.h>
7137535Sdes#include <stdio.h>
7237535Sdes#include <stdlib.h>
7337535Sdes#include <string.h>
7460376Sdes#include <time.h>
7537535Sdes#include <unistd.h>
7637535Sdes
7737535Sdes#include "fetch.h"
7840939Sdes#include "common.h"
7941862Sdes#include "httperr.h"
8037535Sdes
8137535Sdesextern char *__progname;
8237535Sdes
8337535Sdes#define ENDL "\r\n"
8437535Sdes
8560196Sdes#define HTTP_OK		200
8660196Sdes#define HTTP_PARTIAL	206
8760954Sdes#define HTTP_MOVED	302
8860196Sdes
8937535Sdesstruct cookie
9037535Sdes{
9137535Sdes    FILE *real_f;
9237535Sdes#define ENC_NONE 0
9337535Sdes#define ENC_CHUNKED 1
9437535Sdes    int encoding;			/* 1 = chunked, 0 = none */
9537535Sdes#define HTTPCTYPELEN 59
9637535Sdes    char content_type[HTTPCTYPELEN+1];
9737535Sdes    char *buf;
9837535Sdes    int b_cur, eof;
9937535Sdes    unsigned b_len, chunksize;
10037535Sdes};
10137535Sdes
10237608Sdes/*
10337608Sdes * Send a formatted line; optionally echo to terminal
10437608Sdes */
10537608Sdesstatic int
10637608Sdes_http_cmd(FILE *f, char *fmt, ...)
10737608Sdes{
10837608Sdes    va_list ap;
10937608Sdes
11037608Sdes    va_start(ap, fmt);
11137608Sdes    vfprintf(f, fmt, ap);
11262965Sdes    DEBUG(fprintf(stderr, "\033[1m>>> "));
11362965Sdes    DEBUG(vfprintf(stderr, fmt, ap));
11462965Sdes    DEBUG(fprintf(stderr, "\033[m"));
11537608Sdes    va_end(ap);
11637608Sdes
11737608Sdes    return 0; /* XXX */
11837608Sdes}
11937608Sdes
12037608Sdes/*
12137608Sdes * Fill the input buffer, do chunk decoding on the fly
12237608Sdes */
12337535Sdesstatic char *
12437535Sdes_http_fillbuf(struct cookie *c)
12537535Sdes{
12637535Sdes    char *ln;
12737535Sdes    unsigned int len;
12837535Sdes
12937535Sdes    if (c->eof)
13037535Sdes	return NULL;
13137535Sdes
13237535Sdes    if (c->encoding == ENC_NONE) {
13337535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
13437535Sdes	c->b_cur = 0;
13537535Sdes    } else if (c->encoding == ENC_CHUNKED) {
13637535Sdes	if (c->chunksize == 0) {
13737535Sdes	    ln = fgetln(c->real_f, &len);
13860707Sdes	    if (len <= 2)
13960707Sdes		return NULL;
14037535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
14137535Sdes			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
14237535Sdes	    sscanf(ln, "%x", &(c->chunksize));
14337535Sdes	    if (!c->chunksize) {
14437535Sdes		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
14537535Sdes			      "end of last chunk\033[m\n"));
14637535Sdes		c->eof = 1;
14737535Sdes		return NULL;
14837535Sdes	    }
14937535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
15037535Sdes			  "new chunk: %X\033[m\n", c->chunksize));
15137535Sdes	}
15237535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
15337535Sdes	if (c->b_len > c->chunksize)
15437535Sdes	    c->b_len = c->chunksize;
15537535Sdes	c->chunksize -= c->b_len;
15637535Sdes	c->b_cur = 0;
15737535Sdes    }
15837535Sdes    else return NULL; /* unknown encoding */
15937535Sdes    return c->buf;
16037535Sdes}
16137535Sdes
16237608Sdes/*
16337608Sdes * Read function
16437608Sdes */
16537535Sdesstatic int
16637535Sdes_http_readfn(struct cookie *c, char *buf, int len)
16737535Sdes{
16837535Sdes    int l, pos = 0;
16937535Sdes    while (len) {
17037535Sdes	/* empty buffer */
17137535Sdes	if (!c->buf || (c->b_cur == c->b_len))
17237535Sdes	    if (!_http_fillbuf(c))
17337535Sdes		break;
17437535Sdes
17537535Sdes	l = c->b_len - c->b_cur;
17637535Sdes	if (len < l) l = len;
17737535Sdes	memcpy(buf + pos, c->buf + c->b_cur, l);
17837535Sdes	c->b_cur += l;
17937535Sdes	pos += l;
18037535Sdes	len -= l;
18137535Sdes    }
18237535Sdes
18337535Sdes    if (ferror(c->real_f))
18437535Sdes	return -1;
18537535Sdes    else return pos;
18637535Sdes}
18737535Sdes
18837608Sdes/*
18937608Sdes * Write function
19037608Sdes */
19137535Sdesstatic int
19237535Sdes_http_writefn(struct cookie *c, const char *buf, int len)
19337535Sdes{
19437535Sdes    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
19537535Sdes    return r ? r : -1;
19637535Sdes}
19737535Sdes
19837608Sdes/*
19937608Sdes * Close function
20037608Sdes */
20137535Sdesstatic int
20237535Sdes_http_closefn(struct cookie *c)
20337535Sdes{
20437535Sdes    int r = fclose(c->real_f);
20537535Sdes    free(c);
20637535Sdes    return (r == EOF) ? -1 : 0;
20737535Sdes}
20837535Sdes
20937608Sdes/*
21037608Sdes * Extract content type from cookie
21137608Sdes */
21237535Sdeschar *
21337535SdesfetchContentType(FILE *f)
21437535Sdes{
21537535Sdes    /*
21637535Sdes     * We have no way of making sure this really *is* one of our cookies,
21737535Sdes     * so just check for a null pointer and hope for the best.
21837535Sdes     */
21937535Sdes    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
22037535Sdes}
22137535Sdes
22237608Sdes/*
22337608Sdes * Base64 encoding
22437608Sdes */
22562965Sdesstatic char *
22662965Sdes_http_base64(char *src)
22737608Sdes{
22837608Sdes    static const char base64[] =
22937608Sdes	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
23037608Sdes	"abcdefghijklmnopqrstuvwxyz"
23137608Sdes	"0123456789+/";
23262965Sdes    char *str, *dst;
23362965Sdes    size_t l;
23462965Sdes    int t, r;
23562965Sdes
23662965Sdes    l = strlen(src);
23762965Sdes    if ((str = malloc(((l + 2) / 3) * 4)) == NULL)
23862965Sdes	return NULL;
23962965Sdes    dst = str;
24062965Sdes    r = 0;
24137608Sdes
24237608Sdes    while (l >= 3) {
24337608Sdes	t = (src[0] << 16) | (src[1] << 8) | src[2];
24437608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
24537608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
24637608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
24737608Sdes	dst[3] = base64[(t >> 0) & 0x3f];
24837608Sdes	src += 3; l -= 3;
24937608Sdes	dst += 4; r += 4;
25037608Sdes    }
25137608Sdes
25237608Sdes    switch (l) {
25337608Sdes    case 2:
25437608Sdes	t = (src[0] << 16) | (src[1] << 8);
25537608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
25637608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
25737608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
25837608Sdes	dst[3] = '=';
25937608Sdes	dst += 4;
26037608Sdes	r += 4;
26137608Sdes	break;
26237608Sdes    case 1:
26337608Sdes	t = src[0] << 16;
26437608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
26537608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
26637608Sdes	dst[2] = dst[3] = '=';
26737608Sdes	dst += 4;
26837608Sdes	r += 4;
26937608Sdes	break;
27037608Sdes    case 0:
27137608Sdes	break;
27237608Sdes    }
27337608Sdes
27437608Sdes    *dst = 0;
27562965Sdes    return str;
27637608Sdes}
27737608Sdes
27837608Sdes/*
27937608Sdes * Encode username and password
28037608Sdes */
28162965Sdesstatic int
28262965Sdes_http_basic_auth(FILE *f, char *hdr, char *usr, char *pwd)
28337608Sdes{
28462965Sdes    char *upw, *auth;
28562965Sdes    int r;
28637608Sdes
28762965Sdes    if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
28862965Sdes	return -1;
28962965Sdes    auth = _http_base64(upw);
29062965Sdes    free(upw);
29162965Sdes    if (auth == NULL)
29262965Sdes	return -1;
29362965Sdes    r = _http_cmd(f, "%s: Basic %s" ENDL, hdr, auth);
29462965Sdes    free(auth);
29562965Sdes    return r;
29662965Sdes}
29762965Sdes
29862965Sdes/*
29962965Sdes * Send an authorization header
30062965Sdes */
30162965Sdesstatic int
30262965Sdes_http_authorize(FILE *f, char *hdr, char *p)
30362965Sdes{
30462965Sdes    /* basic authorization */
30562965Sdes    if (strncasecmp(p, "basic:", 6) == 0) {
30662965Sdes	char *user, *pwd, *str;
30762965Sdes	int r;
30862965Sdes
30962965Sdes	/* skip realm */
31062965Sdes	for (p += 6; *p && *p != ':'; ++p)
31162965Sdes	    /* nothing */ ;
31262965Sdes	if (!*p || strchr(++p, ':') == NULL)
31362965Sdes	    return -1;
31462965Sdes	if ((str = strdup(p)) == NULL)
31562965Sdes	    return -1; /* XXX */
31662965Sdes	user = str;
31762965Sdes	pwd = strchr(str, ':');
31862965Sdes	*pwd++ = '\0';
31962965Sdes	r = _http_basic_auth(f, hdr, user, pwd);
32062965Sdes	free(str);
32162965Sdes	return r;
32262811Sdes    }
32362965Sdes    return -1;
32437608Sdes}
32537608Sdes
32637608Sdes/*
32760376Sdes * Connect to server or proxy
32837608Sdes */
32962965Sdesstatic FILE *
33062965Sdes_http_connect(struct url *URL, char *flags, int *proxy)
33137535Sdes{
33260376Sdes    int direct, sd = -1, verbose;
33360737Sume#ifdef INET6
33460737Sume    int af = AF_UNSPEC;
33560737Sume#else
33660737Sume    int af = AF_INET;
33760737Sume#endif
33837535Sdes    size_t len;
33960376Sdes    char *px;
34060376Sdes    FILE *f;
34160376Sdes
34255544Sdes    direct = (flags && strchr(flags, 'd'));
34355544Sdes    verbose = (flags && strchr(flags, 'v'));
34460737Sume    if ((flags && strchr(flags, '4')))
34560737Sume	af = AF_INET;
34660737Sume    else if ((flags && strchr(flags, '6')))
34760737Sume	af = AF_INET6;
34841862Sdes
34937535Sdes    /* check port */
35060189Sdes    if (!URL->port) {
35160189Sdes	struct servent *se;
35260189Sdes
35360587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
35460587Sume	    if ((se = getservbyname("ftp", "tcp")) != NULL)
35560587Sume		URL->port = ntohs(se->s_port);
35660587Sume	    else
35760587Sume		URL->port = 21;
35860189Sdes	else
35960587Sume	    if ((se = getservbyname("http", "tcp")) != NULL)
36060587Sume		URL->port = ntohs(se->s_port);
36160587Sume	    else
36260587Sume		URL->port = 80;
36360189Sdes    }
36437535Sdes
36537535Sdes    /* attempt to connect to proxy server */
36655544Sdes    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
36741863Sdes	char host[MAXHOSTNAMELEN];
36860189Sdes	int port = 0;
36937535Sdes
37037535Sdes	/* measure length */
37160737Sume#ifdef INET6
37260737Sume	if (px[0] != '[' ||
37360737Sume	    (len = strcspn(px, "]")) >= strlen(px) ||
37460737Sume	    (px[++len] != '\0' && px[len] != ':'))
37560737Sume#endif
37660737Sume	    len = strcspn(px, ":");
37737535Sdes
37855544Sdes	/* get port (XXX atoi is a little too tolerant perhaps?) */
37960189Sdes	if (px[len] == ':') {
38060189Sdes	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
38160189Sdes		|| strlen(px+len+1) > 5) {
38260189Sdes		/* XXX we should emit some kind of warning */
38360189Sdes	    }
38437535Sdes	    port = atoi(px+len+1);
38560189Sdes	    if (port < 1 || port > 65535) {
38660189Sdes		/* XXX we should emit some kind of warning */
38760189Sdes	    }
38860189Sdes	}
38960189Sdes	if (!port) {
39060189Sdes#if 0
39160189Sdes	    /*
39260189Sdes	     * commented out, since there is currently no service name
39360189Sdes	     * for HTTP proxies
39460189Sdes	     */
39560189Sdes	    struct servent *se;
39660189Sdes
39760189Sdes	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
39860189Sdes		port = ntohs(se->s_port);
39960189Sdes	    else
40060189Sdes#endif
40160189Sdes		port = 3128;
40260189Sdes	}
40337535Sdes
40437535Sdes	/* get host name */
40560737Sume#ifdef INET6
40660737Sume	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
40760737Sume	    px++;
40860737Sume	    len -= 2;
40960737Sume	}
41060737Sume#endif
41137535Sdes	if (len >= MAXHOSTNAMELEN)
41237535Sdes	    len = MAXHOSTNAMELEN - 1;
41337535Sdes	strncpy(host, px, len);
41437535Sdes	host[len] = 0;
41537535Sdes
41637535Sdes	/* connect */
41760737Sume	sd = _fetch_connect(host, port, af, verbose);
41837535Sdes    }
41937535Sdes
42037535Sdes    /* if no proxy is configured or could be contacted, try direct */
42162965Sdes    *proxy = (sd != -1);
42262965Sdes    if (!*proxy) {
42360587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
42460587Sume	    goto ouch;
42560737Sume	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
42637535Sdes	    goto ouch;
42737535Sdes    }
42837535Sdes
42937535Sdes    /* reopen as stream */
43037571Sdes    if ((f = fdopen(sd, "r+")) == NULL)
43137535Sdes	goto ouch;
43260376Sdes
43360376Sdes    return f;
43437535Sdes
43560376Sdesouch:
43660376Sdes    if (sd >= 0)
43760376Sdes	close(sd);
43860376Sdes    _http_seterr(999); /* XXX do this properly RSN */
43960376Sdes    return NULL;
44060376Sdes}
44160376Sdes
44260376Sdes/*
44360954Sdes * Check a header line
44460954Sdes */
44562965Sdesstatic char *
44660954Sdes_http_match(char *str, char *hdr)
44760954Sdes{
44860954Sdes    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
44960954Sdes	/* nothing */;
45060954Sdes    if (*str || *hdr != ':')
45160954Sdes	return NULL;
45260954Sdes    while (*hdr && isspace(*++hdr))
45360954Sdes	/* nothing */;
45460954Sdes    return hdr;
45560954Sdes}
45660954Sdes
45760954Sdes/*
45860376Sdes * Send a HEAD or GET request
45960376Sdes */
46062965Sdesstatic int
46162965Sdes_http_request(FILE *f, char *op, struct url *URL, char *flags, int proxy)
46260376Sdes{
46360376Sdes    int e, verbose;
46460376Sdes    char *ln, *p;
46560376Sdes    size_t len;
46660737Sume    char *host;
46760737Sume#ifdef INET6
46860737Sume    char hbuf[MAXHOSTNAMELEN + 1];
46960737Sume#endif
47060376Sdes
47160376Sdes    verbose = (flags && strchr(flags, 'v'));
47260737Sume
47360737Sume    host = URL->host;
47460737Sume#ifdef INET6
47560737Sume    if (strchr(URL->host, ':')) {
47660737Sume	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
47760737Sume	host = hbuf;
47860737Sume    }
47960737Sume#endif
48060376Sdes
48162965Sdes    /* send request (proxies require absolute form) */
48241862Sdes    if (verbose)
48360587Sume	_fetch_info("requesting %s://%s:%d%s",
48460737Sume		    URL->scheme, host, URL->port, URL->doc);
48562965Sdes    if (proxy)
48662965Sdes	_http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
48762965Sdes		  op, URL->scheme, host, URL->port, URL->doc);
48862965Sdes    else
48962965Sdes	_http_cmd(f, "%s %s HTTP/1.1" ENDL, op, URL->doc);
49037535Sdes
49137535Sdes    /* start sending headers away */
49262965Sdes    if (URL->user[0] || URL->pwd[0])
49362965Sdes	_http_basic_auth(f, "Authorization",
49462965Sdes			 URL->user ? URL->user : "",
49562965Sdes			 URL->pwd ? URL->pwd : "");
49662965Sdes    else if ((p = getenv("HTTP_AUTH")) != NULL)
49762965Sdes	_http_authorize(f, "Authorization", p);
49862965Sdes    if (proxy &&  (p = getenv("HTTP_PROXY_AUTH")) != NULL)
49962965Sdes	_http_authorize(f, "Proxy-Authorization", p);
50060737Sume    _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
50137608Sdes    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
50260196Sdes    if (URL->offset)
50360196Sdes	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
50437608Sdes    _http_cmd(f, "Connection: close" ENDL ENDL);
50537535Sdes
50637535Sdes    /* get response */
50737535Sdes    if ((ln = fgetln(f, &len)) == NULL)
50860376Sdes	return 999;
50937535Sdes    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
51037535Sdes		  (int)len-2, (int)len-2, ln));
51137535Sdes
51237535Sdes    /* we can't use strchr() and friends since ln isn't NUL-terminated */
51337535Sdes    p = ln;
51437535Sdes    while ((p < ln + len) && !isspace(*p))
51537535Sdes	p++;
51637535Sdes    while ((p < ln + len) && !isdigit(*p))
51737535Sdes	p++;
51837535Sdes    if (!isdigit(*p))
51960376Sdes	return 999;
52060376Sdes
52141863Sdes    e = atoi(p);
52241863Sdes    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
52360376Sdes    return e;
52460376Sdes}
52560376Sdes
52660376Sdes/*
52760376Sdes * Retrieve a file by HTTP
52860376Sdes */
52960376SdesFILE *
53060376SdesfetchGetHTTP(struct url *URL, char *flags)
53160376Sdes{
53262965Sdes    int e, enc = ENC_NONE, i, noredirect, proxy;
53360376Sdes    struct cookie *c;
53460376Sdes    char *ln, *p, *q;
53560376Sdes    FILE *f, *cf;
53660376Sdes    size_t len;
53760376Sdes    off_t pos = 0;
53860376Sdes
53960954Sdes    noredirect = (flags && strchr(flags, 'A'));
54060954Sdes
54160376Sdes    /* allocate cookie */
54260376Sdes    if ((c = calloc(1, sizeof *c)) == NULL)
54360376Sdes	return NULL;
54460376Sdes
54560376Sdes    /* connect */
54662965Sdes    if ((f = _http_connect(URL, flags, &proxy)) == NULL) {
54760376Sdes	free(c);
54860376Sdes	return NULL;
54960376Sdes    }
55060376Sdes    c->real_f = f;
55160376Sdes
55262965Sdes    e = _http_request(f, "GET", URL, flags, proxy);
55360954Sdes    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)
55460954Sdes	&& (e != HTTP_MOVED || noredirect)) {
55541863Sdes	_http_seterr(e);
55661896Sdes	free(c);
55761896Sdes	fclose(f);
55861896Sdes	return NULL;
55937571Sdes    }
56037535Sdes
56137535Sdes    /* browse through header */
56237535Sdes    while (1) {
56337535Sdes	if ((ln = fgetln(f, &len)) == NULL)
56437535Sdes	    goto fouch;
56537535Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
56637535Sdes	    break;
56760376Sdes	while (isspace(ln[len-1]))
56860376Sdes	    --len;
56960376Sdes	ln[len] = '\0'; /* XXX */
57060376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
57160954Sdes	if ((p = _http_match("Location", ln)) != NULL) {
57260954Sdes	    struct url *url;
57360954Sdes
57460376Sdes	    for (q = p; *q && !isspace(*q); q++)
57537535Sdes		/* VOID */ ;
57637535Sdes	    *q = 0;
57760954Sdes	    if ((url = fetchParseURL(p)) == NULL)
57860954Sdes		goto fouch;
57960954Sdes	    url->offset = URL->offset;
58060954Sdes	    url->length = URL->length;
58160954Sdes	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
58260954Sdes	    cf = fetchGetHTTP(url, flags);
58360954Sdes	    fetchFreeURL(url);
58460954Sdes	    fclose(f);
58560954Sdes	    return cf;
58660954Sdes	} else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
58760954Sdes	    for (q = p; *q && !isspace(*q); q++)
58860954Sdes		/* VOID */ ;
58960954Sdes	    *q = 0;
59037535Sdes	    if (strcasecmp(p, "chunked") == 0)
59137535Sdes		enc = ENC_CHUNKED;
59260376Sdes	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
59360376Sdes	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
59460376Sdes	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
59560376Sdes		    c->content_type[i] = *p;
59637535Sdes	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
59760376Sdes	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
59837535Sdes			  c->content_type));
59960376Sdes	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
60060376Sdes	    if (strncasecmp(p, "bytes ", 6) != 0)
60160196Sdes		goto fouch;
60260376Sdes	    p += 6;
60360376Sdes	    while (*p && isdigit(*p))
60460196Sdes		pos = pos * 10 + (*p++ - '0');
60560196Sdes	    /* XXX wouldn't hurt to be slightly more paranoid here */
60660376Sdes	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
60760196Sdes	    if (pos > URL->offset)
60860196Sdes		goto fouch;
60937535Sdes	}
61037535Sdes    }
61137535Sdes
61237535Sdes    /* only body remains */
61337535Sdes    c->encoding = enc;
61437535Sdes    cf = funopen(c,
61537535Sdes		 (int (*)(void *, char *, int))_http_readfn,
61637535Sdes		 (int (*)(void *, const char *, int))_http_writefn,
61737535Sdes		 (fpos_t (*)(void *, fpos_t, int))NULL,
61837535Sdes		 (int (*)(void *))_http_closefn);
61937535Sdes    if (cf == NULL)
62037535Sdes	goto fouch;
62160189Sdes
62260196Sdes    while (pos < URL->offset)
62360196Sdes	if (fgetc(cf) == EOF)
62460196Sdes	    goto cfouch;
62560196Sdes
62637535Sdes    return cf;
62737535Sdes
62837535Sdesfouch:
62937535Sdes    fclose(f);
63037535Sdes    free(c);
63141862Sdes    _http_seterr(999); /* XXX do this properly RSN */
63237535Sdes    return NULL;
63360196Sdescfouch:
63460196Sdes    fclose(cf);
63560196Sdes    _http_seterr(999); /* XXX do this properly RSN */
63660196Sdes    return NULL;
63737535Sdes}
63837535Sdes
63937535SdesFILE *
64040975SdesfetchPutHTTP(struct url *URL, char *flags)
64137535Sdes{
64237535Sdes    warnx("fetchPutHTTP(): not implemented");
64337535Sdes    return NULL;
64437535Sdes}
64540975Sdes
64640975Sdes/*
64740975Sdes * Get an HTTP document's metadata
64840975Sdes */
64940975Sdesint
65060376SdesfetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
65140975Sdes{
65262965Sdes    int e, noredirect, proxy;
65360376Sdes    size_t len;
65460954Sdes    char *ln, *p, *q;
65560376Sdes    FILE *f;
65660954Sdes
65760954Sdes    noredirect = (flags && strchr(flags, 'A'));
65860376Sdes
65960581Sdes    us->size = -1;
66060581Sdes    us->atime = us->mtime = 0;
66160376Sdes
66260376Sdes    /* connect */
66362965Sdes    if ((f = _http_connect(URL, flags, &proxy)) == NULL)
66460376Sdes	return -1;
66560376Sdes
66662965Sdes    e = _http_request(f, "HEAD", URL, flags, proxy);
66760954Sdes    if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) {
66860376Sdes	_http_seterr(e);
66961896Sdes	fclose(f);
67061896Sdes	return -1;
67160376Sdes    }
67260376Sdes
67360376Sdes    while (1) {
67460376Sdes	if ((ln = fgetln(f, &len)) == NULL)
67560376Sdes	    goto fouch;
67660376Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
67760376Sdes	    break;
67860376Sdes	while (isspace(ln[len-1]))
67960376Sdes	    --len;
68060376Sdes	ln[len] = '\0'; /* XXX */
68160376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
68260954Sdes	if ((p = _http_match("Location", ln)) != NULL) {
68360954Sdes	    struct url *url;
68460954Sdes
68560954Sdes	    for (q = p; *q && !isspace(*q); q++)
68660954Sdes		/* VOID */ ;
68760954Sdes	    *q = 0;
68860954Sdes	    if ((url = fetchParseURL(p)) == NULL)
68960954Sdes		goto ouch;
69060954Sdes	    url->offset = URL->offset;
69160954Sdes	    url->length = URL->length;
69260954Sdes	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
69360954Sdes	    e = fetchStatHTTP(url, us, flags);
69460954Sdes	    fetchFreeURL(url);
69560954Sdes	    fclose(f);
69660954Sdes	    return e;
69760954Sdes	} else if ((p = _http_match("Last-Modified", ln)) != NULL) {
69860376Sdes	    struct tm tm;
69960376Sdes	    char locale[64];
70060376Sdes
70160376Sdes	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
70260376Sdes	    setlocale(LC_TIME, "C");
70360376Sdes	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
70460376Sdes	    /* XXX should add support for date-2 and date-3 */
70560376Sdes	    setlocale(LC_TIME, locale);
70660376Sdes	    us->atime = us->mtime = timegm(&tm);
70760376Sdes	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
70860376Sdes			  "%02d:%02d:%02d\033[m]\n",
70960376Sdes			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
71060376Sdes			  tm.tm_hour, tm.tm_min, tm.tm_sec));
71160376Sdes	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
71260376Sdes	    us->size = 0;
71360376Sdes	    while (*p && isdigit(*p))
71460376Sdes		us->size = us->size * 10 + (*p++ - '0');
71560376Sdes	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
71660376Sdes	}
71760376Sdes    }
71860581Sdes
71960581Sdes    fclose(f);
72060376Sdes    return 0;
72160376Sdes ouch:
72260376Sdes    _http_seterr(999); /* XXX do this properly RSN */
72360376Sdes fouch:
72460376Sdes    fclose(f);
72560376Sdes    return -1;
72640975Sdes}
72741989Sdes
72841989Sdes/*
72941989Sdes * List a directory
73041989Sdes */
73141989Sdesstruct url_ent *
73241989SdesfetchListHTTP(struct url *url, char *flags)
73341989Sdes{
73441989Sdes    warnx("fetchListHTTP(): not implemented");
73541989Sdes    return NULL;
73641989Sdes}
737