http.c revision 60954
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 60954 2000-05-26 15:34:42Z 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);
11237608Sdes#ifndef NDEBUG
11337608Sdes    fprintf(stderr, "\033[1m>>> ");
11437608Sdes    vfprintf(stderr, fmt, ap);
11537608Sdes    fprintf(stderr, "\033[m");
11637608Sdes#endif
11737608Sdes    va_end(ap);
11837608Sdes
11937608Sdes    return 0; /* XXX */
12037608Sdes}
12137608Sdes
12237608Sdes/*
12337608Sdes * Fill the input buffer, do chunk decoding on the fly
12437608Sdes */
12537535Sdesstatic char *
12637535Sdes_http_fillbuf(struct cookie *c)
12737535Sdes{
12837535Sdes    char *ln;
12937535Sdes    unsigned int len;
13037535Sdes
13137535Sdes    if (c->eof)
13237535Sdes	return NULL;
13337535Sdes
13437535Sdes    if (c->encoding == ENC_NONE) {
13537535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
13637535Sdes	c->b_cur = 0;
13737535Sdes    } else if (c->encoding == ENC_CHUNKED) {
13837535Sdes	if (c->chunksize == 0) {
13937535Sdes	    ln = fgetln(c->real_f, &len);
14060707Sdes	    if (len <= 2)
14160707Sdes		return NULL;
14237535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: "
14337535Sdes			  "%*.*s\033[m\n", (int)len-2, (int)len-2, ln));
14437535Sdes	    sscanf(ln, "%x", &(c->chunksize));
14537535Sdes	    if (!c->chunksize) {
14637535Sdes		DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
14737535Sdes			      "end of last chunk\033[m\n"));
14837535Sdes		c->eof = 1;
14937535Sdes		return NULL;
15037535Sdes	    }
15137535Sdes	    DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): "
15237535Sdes			  "new chunk: %X\033[m\n", c->chunksize));
15337535Sdes	}
15437535Sdes	c->buf = fgetln(c->real_f, &(c->b_len));
15537535Sdes	if (c->b_len > c->chunksize)
15637535Sdes	    c->b_len = c->chunksize;
15737535Sdes	c->chunksize -= c->b_len;
15837535Sdes	c->b_cur = 0;
15937535Sdes    }
16037535Sdes    else return NULL; /* unknown encoding */
16137535Sdes    return c->buf;
16237535Sdes}
16337535Sdes
16437608Sdes/*
16537608Sdes * Read function
16637608Sdes */
16737535Sdesstatic int
16837535Sdes_http_readfn(struct cookie *c, char *buf, int len)
16937535Sdes{
17037535Sdes    int l, pos = 0;
17137535Sdes    while (len) {
17237535Sdes	/* empty buffer */
17337535Sdes	if (!c->buf || (c->b_cur == c->b_len))
17437535Sdes	    if (!_http_fillbuf(c))
17537535Sdes		break;
17637535Sdes
17737535Sdes	l = c->b_len - c->b_cur;
17837535Sdes	if (len < l) l = len;
17937535Sdes	memcpy(buf + pos, c->buf + c->b_cur, l);
18037535Sdes	c->b_cur += l;
18137535Sdes	pos += l;
18237535Sdes	len -= l;
18337535Sdes    }
18437535Sdes
18537535Sdes    if (ferror(c->real_f))
18637535Sdes	return -1;
18737535Sdes    else return pos;
18837535Sdes}
18937535Sdes
19037608Sdes/*
19137608Sdes * Write function
19237608Sdes */
19337535Sdesstatic int
19437535Sdes_http_writefn(struct cookie *c, const char *buf, int len)
19537535Sdes{
19637535Sdes    size_t r = fwrite(buf, 1, (size_t)len, c->real_f);
19737535Sdes    return r ? r : -1;
19837535Sdes}
19937535Sdes
20037608Sdes/*
20137608Sdes * Close function
20237608Sdes */
20337535Sdesstatic int
20437535Sdes_http_closefn(struct cookie *c)
20537535Sdes{
20637535Sdes    int r = fclose(c->real_f);
20737535Sdes    free(c);
20837535Sdes    return (r == EOF) ? -1 : 0;
20937535Sdes}
21037535Sdes
21137608Sdes/*
21237608Sdes * Extract content type from cookie
21337608Sdes */
21437535Sdeschar *
21537535SdesfetchContentType(FILE *f)
21637535Sdes{
21737535Sdes    /*
21837535Sdes     * We have no way of making sure this really *is* one of our cookies,
21937535Sdes     * so just check for a null pointer and hope for the best.
22037535Sdes     */
22137535Sdes    return f->_cookie ? (((struct cookie *)f->_cookie)->content_type) : NULL;
22237535Sdes}
22337535Sdes
22437608Sdes/*
22537608Sdes * Base64 encoding
22637608Sdes */
22737608Sdesint
22837608Sdes_http_base64(char *dst, char *src, int l)
22937608Sdes{
23037608Sdes    static const char base64[] =
23137608Sdes	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
23237608Sdes	"abcdefghijklmnopqrstuvwxyz"
23337608Sdes	"0123456789+/";
23437608Sdes    int t, r = 0;
23537608Sdes
23637608Sdes    while (l >= 3) {
23737608Sdes	t = (src[0] << 16) | (src[1] << 8) | src[2];
23837608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
23937608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
24037608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
24137608Sdes	dst[3] = base64[(t >> 0) & 0x3f];
24237608Sdes	src += 3; l -= 3;
24337608Sdes	dst += 4; r += 4;
24437608Sdes    }
24537608Sdes
24637608Sdes    switch (l) {
24737608Sdes    case 2:
24837608Sdes	t = (src[0] << 16) | (src[1] << 8);
24937608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
25037608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
25137608Sdes	dst[2] = base64[(t >> 6) & 0x3f];
25237608Sdes	dst[3] = '=';
25337608Sdes	dst += 4;
25437608Sdes	r += 4;
25537608Sdes	break;
25637608Sdes    case 1:
25737608Sdes	t = src[0] << 16;
25837608Sdes	dst[0] = base64[(t >> 18) & 0x3f];
25937608Sdes	dst[1] = base64[(t >> 12) & 0x3f];
26037608Sdes	dst[2] = dst[3] = '=';
26137608Sdes	dst += 4;
26237608Sdes	r += 4;
26337608Sdes	break;
26437608Sdes    case 0:
26537608Sdes	break;
26637608Sdes    }
26737608Sdes
26837608Sdes    *dst = 0;
26937608Sdes    return r;
27037608Sdes}
27137608Sdes
27237608Sdes/*
27337608Sdes * Encode username and password
27437608Sdes */
27537608Sdeschar *
27637608Sdes_http_auth(char *usr, char *pwd)
27737608Sdes{
27837608Sdes    int len, lu, lp;
27937608Sdes    char *str, *s;
28037608Sdes
28137608Sdes    lu = strlen(usr);
28237608Sdes    lp = strlen(pwd);
28337608Sdes
28437608Sdes    len = (lu * 4 + 2) / 3	/* user name, round up */
28537608Sdes	+ 1			/* colon */
28637608Sdes	+ (lp * 4 + 2) / 3	/* password, round up */
28737608Sdes	+ 1;			/* null */
28837608Sdes
28937608Sdes    if ((s = str = (char *)malloc(len)) == NULL)
29037608Sdes	return NULL;
29137608Sdes
29237608Sdes    s += _http_base64(s, usr, lu);
29337608Sdes    *s++ = ':';
29437608Sdes    s += _http_base64(s, pwd, lp);
29537608Sdes    *s = 0;
29637608Sdes
29737608Sdes    return str;
29837608Sdes}
29937608Sdes
30037608Sdes/*
30160376Sdes * Connect to server or proxy
30237608Sdes */
30337535SdesFILE *
30460376Sdes_http_connect(struct url *URL, char *flags)
30537535Sdes{
30660376Sdes    int direct, sd = -1, verbose;
30760737Sume#ifdef INET6
30860737Sume    int af = AF_UNSPEC;
30960737Sume#else
31060737Sume    int af = AF_INET;
31160737Sume#endif
31237535Sdes    size_t len;
31360376Sdes    char *px;
31460376Sdes    FILE *f;
31560376Sdes
31655544Sdes    direct = (flags && strchr(flags, 'd'));
31755544Sdes    verbose = (flags && strchr(flags, 'v'));
31860737Sume    if ((flags && strchr(flags, '4')))
31960737Sume	af = AF_INET;
32060737Sume    else if ((flags && strchr(flags, '6')))
32160737Sume	af = AF_INET6;
32241862Sdes
32337535Sdes    /* check port */
32460189Sdes    if (!URL->port) {
32560189Sdes	struct servent *se;
32660189Sdes
32760587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
32860587Sume	    if ((se = getservbyname("ftp", "tcp")) != NULL)
32960587Sume		URL->port = ntohs(se->s_port);
33060587Sume	    else
33160587Sume		URL->port = 21;
33260189Sdes	else
33360587Sume	    if ((se = getservbyname("http", "tcp")) != NULL)
33460587Sume		URL->port = ntohs(se->s_port);
33560587Sume	    else
33660587Sume		URL->port = 80;
33760189Sdes    }
33837535Sdes
33937535Sdes    /* attempt to connect to proxy server */
34055544Sdes    if (!direct && (px = getenv("HTTP_PROXY")) != NULL) {
34141863Sdes	char host[MAXHOSTNAMELEN];
34260189Sdes	int port = 0;
34337535Sdes
34437535Sdes	/* measure length */
34560737Sume#ifdef INET6
34660737Sume	if (px[0] != '[' ||
34760737Sume	    (len = strcspn(px, "]")) >= strlen(px) ||
34860737Sume	    (px[++len] != '\0' && px[len] != ':'))
34960737Sume#endif
35060737Sume	    len = strcspn(px, ":");
35137535Sdes
35255544Sdes	/* get port (XXX atoi is a little too tolerant perhaps?) */
35360189Sdes	if (px[len] == ':') {
35460189Sdes	    if (strspn(px+len+1, "0123456789") != strlen(px+len+1)
35560189Sdes		|| strlen(px+len+1) > 5) {
35660189Sdes		/* XXX we should emit some kind of warning */
35760189Sdes	    }
35837535Sdes	    port = atoi(px+len+1);
35960189Sdes	    if (port < 1 || port > 65535) {
36060189Sdes		/* XXX we should emit some kind of warning */
36160189Sdes	    }
36260189Sdes	}
36360189Sdes	if (!port) {
36460189Sdes#if 0
36560189Sdes	    /*
36660189Sdes	     * commented out, since there is currently no service name
36760189Sdes	     * for HTTP proxies
36860189Sdes	     */
36960189Sdes	    struct servent *se;
37060189Sdes
37160189Sdes	    if ((se = getservbyname("xxxx", "tcp")) != NULL)
37260189Sdes		port = ntohs(se->s_port);
37360189Sdes	    else
37460189Sdes#endif
37560189Sdes		port = 3128;
37660189Sdes	}
37737535Sdes
37837535Sdes	/* get host name */
37960737Sume#ifdef INET6
38060737Sume	if (len > 1 && px[0] == '[' && px[len - 1] == ']') {
38160737Sume	    px++;
38260737Sume	    len -= 2;
38360737Sume	}
38460737Sume#endif
38537535Sdes	if (len >= MAXHOSTNAMELEN)
38637535Sdes	    len = MAXHOSTNAMELEN - 1;
38737535Sdes	strncpy(host, px, len);
38837535Sdes	host[len] = 0;
38937535Sdes
39037535Sdes	/* connect */
39160737Sume	sd = _fetch_connect(host, port, af, verbose);
39237535Sdes    }
39337535Sdes
39437535Sdes    /* if no proxy is configured or could be contacted, try direct */
39538394Sdes    if (sd == -1) {
39660587Sume	if (strcasecmp(URL->scheme, "ftp") == 0)
39760587Sume	    goto ouch;
39860737Sume	if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
39937535Sdes	    goto ouch;
40037535Sdes    }
40137535Sdes
40237535Sdes    /* reopen as stream */
40337571Sdes    if ((f = fdopen(sd, "r+")) == NULL)
40437535Sdes	goto ouch;
40560376Sdes
40660376Sdes    return f;
40737535Sdes
40860376Sdesouch:
40960376Sdes    if (sd >= 0)
41060376Sdes	close(sd);
41160376Sdes    _http_seterr(999); /* XXX do this properly RSN */
41260376Sdes    return NULL;
41360376Sdes}
41460376Sdes
41560376Sdes/*
41660954Sdes * Check a header line
41760954Sdes */
41860954Sdeschar *
41960954Sdes_http_match(char *str, char *hdr)
42060954Sdes{
42160954Sdes    while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
42260954Sdes	/* nothing */;
42360954Sdes    if (*str || *hdr != ':')
42460954Sdes	return NULL;
42560954Sdes    while (*hdr && isspace(*++hdr))
42660954Sdes	/* nothing */;
42760954Sdes    return hdr;
42860954Sdes}
42960954Sdes
43060954Sdes/*
43160376Sdes * Send a HEAD or GET request
43260376Sdes */
43360376Sdesint
43460376Sdes_http_request(FILE *f, char *op, struct url *URL, char *flags)
43560376Sdes{
43660376Sdes    int e, verbose;
43760376Sdes    char *ln, *p;
43860376Sdes    size_t len;
43960737Sume    char *host;
44060737Sume#ifdef INET6
44160737Sume    char hbuf[MAXHOSTNAMELEN + 1];
44260737Sume#endif
44360376Sdes
44460376Sdes    verbose = (flags && strchr(flags, 'v'));
44560737Sume
44660737Sume    host = URL->host;
44760737Sume#ifdef INET6
44860737Sume    if (strchr(URL->host, ':')) {
44960737Sume	snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host);
45060737Sume	host = hbuf;
45160737Sume    }
45260737Sume#endif
45360376Sdes
45437535Sdes    /* send request (proxies require absolute form, so use that) */
45541862Sdes    if (verbose)
45660587Sume	_fetch_info("requesting %s://%s:%d%s",
45760737Sume		    URL->scheme, host, URL->port, URL->doc);
45860376Sdes    _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL,
45960737Sume	      op, URL->scheme, host, URL->port, URL->doc);
46037535Sdes
46137535Sdes    /* start sending headers away */
46237535Sdes    if (URL->user[0] || URL->pwd[0]) {
46337608Sdes	char *auth_str = _http_auth(URL->user, URL->pwd);
46437608Sdes	if (!auth_str)
46560376Sdes	    return 999; /* XXX wrong */
46637608Sdes	_http_cmd(f, "Authorization: Basic %s" ENDL, auth_str);
46737608Sdes	free(auth_str);
46837535Sdes    }
46960737Sume    _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port);
47037608Sdes    _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname);
47160196Sdes    if (URL->offset)
47260196Sdes	_http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset);
47337608Sdes    _http_cmd(f, "Connection: close" ENDL ENDL);
47437535Sdes
47537535Sdes    /* get response */
47637535Sdes    if ((ln = fgetln(f, &len)) == NULL)
47760376Sdes	return 999;
47837535Sdes    DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n",
47937535Sdes		  (int)len-2, (int)len-2, ln));
48037535Sdes
48137535Sdes    /* we can't use strchr() and friends since ln isn't NUL-terminated */
48237535Sdes    p = ln;
48337535Sdes    while ((p < ln + len) && !isspace(*p))
48437535Sdes	p++;
48537535Sdes    while ((p < ln + len) && !isdigit(*p))
48637535Sdes	p++;
48737535Sdes    if (!isdigit(*p))
48860376Sdes	return 999;
48960376Sdes
49041863Sdes    e = atoi(p);
49141863Sdes    DEBUG(fprintf(stderr, "code:     [\033[1m%d\033[m]\n", e));
49260376Sdes    return e;
49360376Sdes}
49460376Sdes
49560376Sdes/*
49660376Sdes * Retrieve a file by HTTP
49760376Sdes */
49860376SdesFILE *
49960376SdesfetchGetHTTP(struct url *URL, char *flags)
50060376Sdes{
50160954Sdes    int e, enc = ENC_NONE, i, noredirect;
50260376Sdes    struct cookie *c;
50360376Sdes    char *ln, *p, *q;
50460376Sdes    FILE *f, *cf;
50560376Sdes    size_t len;
50660376Sdes    off_t pos = 0;
50760376Sdes
50860954Sdes    noredirect = (flags && strchr(flags, 'A'));
50960954Sdes
51060376Sdes    /* allocate cookie */
51160376Sdes    if ((c = calloc(1, sizeof *c)) == NULL)
51260376Sdes	return NULL;
51360376Sdes
51460376Sdes    /* connect */
51560376Sdes    if ((f = _http_connect(URL, flags)) == NULL) {
51660376Sdes	free(c);
51760376Sdes	return NULL;
51860376Sdes    }
51960376Sdes    c->real_f = f;
52060376Sdes
52160376Sdes    e = _http_request(f, "GET", URL, flags);
52260954Sdes    if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK)
52360954Sdes	&& (e != HTTP_MOVED || noredirect)) {
52441863Sdes	_http_seterr(e);
52537535Sdes	goto fouch;
52637571Sdes    }
52737535Sdes
52837535Sdes    /* browse through header */
52937535Sdes    while (1) {
53037535Sdes	if ((ln = fgetln(f, &len)) == NULL)
53137535Sdes	    goto fouch;
53237535Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
53337535Sdes	    break;
53460376Sdes	while (isspace(ln[len-1]))
53560376Sdes	    --len;
53660376Sdes	ln[len] = '\0'; /* XXX */
53760376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
53860954Sdes	if ((p = _http_match("Location", ln)) != NULL) {
53960954Sdes	    struct url *url;
54060954Sdes
54160376Sdes	    for (q = p; *q && !isspace(*q); q++)
54237535Sdes		/* VOID */ ;
54337535Sdes	    *q = 0;
54460954Sdes	    if ((url = fetchParseURL(p)) == NULL)
54560954Sdes		goto fouch;
54660954Sdes	    url->offset = URL->offset;
54760954Sdes	    url->length = URL->length;
54860954Sdes	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
54960954Sdes	    cf = fetchGetHTTP(url, flags);
55060954Sdes	    fetchFreeURL(url);
55160954Sdes	    fclose(f);
55260954Sdes	    return cf;
55360954Sdes	} else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) {
55460954Sdes	    for (q = p; *q && !isspace(*q); q++)
55560954Sdes		/* VOID */ ;
55660954Sdes	    *q = 0;
55737535Sdes	    if (strcasecmp(p, "chunked") == 0)
55837535Sdes		enc = ENC_CHUNKED;
55960376Sdes	    DEBUG(fprintf(stderr, "transfer encoding:  [\033[1m%s\033[m]\n", p));
56060376Sdes	} else if ((p = _http_match("Content-Type", ln)) != NULL) {
56160376Sdes	    for (i = 0; *p && i < HTTPCTYPELEN; p++, i++)
56260376Sdes		    c->content_type[i] = *p;
56337535Sdes	    do c->content_type[i--] = 0; while (isspace(c->content_type[i]));
56460376Sdes	    DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n",
56537535Sdes			  c->content_type));
56660376Sdes	} else if ((p = _http_match("Content-Range", ln)) != NULL) {
56760376Sdes	    if (strncasecmp(p, "bytes ", 6) != 0)
56860196Sdes		goto fouch;
56960376Sdes	    p += 6;
57060376Sdes	    while (*p && isdigit(*p))
57160196Sdes		pos = pos * 10 + (*p++ - '0');
57260196Sdes	    /* XXX wouldn't hurt to be slightly more paranoid here */
57360376Sdes	    DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos));
57460196Sdes	    if (pos > URL->offset)
57560196Sdes		goto fouch;
57637535Sdes	}
57737535Sdes    }
57837535Sdes
57937535Sdes    /* only body remains */
58037535Sdes    c->encoding = enc;
58137535Sdes    cf = funopen(c,
58237535Sdes		 (int (*)(void *, char *, int))_http_readfn,
58337535Sdes		 (int (*)(void *, const char *, int))_http_writefn,
58437535Sdes		 (fpos_t (*)(void *, fpos_t, int))NULL,
58537535Sdes		 (int (*)(void *))_http_closefn);
58637535Sdes    if (cf == NULL)
58737535Sdes	goto fouch;
58860189Sdes
58960196Sdes    while (pos < URL->offset)
59060196Sdes	if (fgetc(cf) == EOF)
59160196Sdes	    goto cfouch;
59260196Sdes
59337535Sdes    return cf;
59437535Sdes
59537535Sdesfouch:
59637535Sdes    fclose(f);
59737535Sdes    free(c);
59841862Sdes    _http_seterr(999); /* XXX do this properly RSN */
59937535Sdes    return NULL;
60060196Sdescfouch:
60160196Sdes    fclose(cf);
60260196Sdes    _http_seterr(999); /* XXX do this properly RSN */
60360196Sdes    return NULL;
60437535Sdes}
60537535Sdes
60637535SdesFILE *
60740975SdesfetchPutHTTP(struct url *URL, char *flags)
60837535Sdes{
60937535Sdes    warnx("fetchPutHTTP(): not implemented");
61037535Sdes    return NULL;
61137535Sdes}
61240975Sdes
61340975Sdes/*
61440975Sdes * Get an HTTP document's metadata
61540975Sdes */
61640975Sdesint
61760376SdesfetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
61840975Sdes{
61960954Sdes    int e, noredirect;
62060376Sdes    size_t len;
62160954Sdes    char *ln, *p, *q;
62260376Sdes    FILE *f;
62360954Sdes
62460954Sdes    noredirect = (flags && strchr(flags, 'A'));
62560376Sdes
62660581Sdes    us->size = -1;
62760581Sdes    us->atime = us->mtime = 0;
62860376Sdes
62960376Sdes    /* connect */
63060376Sdes    if ((f = _http_connect(URL, flags)) == NULL)
63160376Sdes	return -1;
63260376Sdes
63360954Sdes    e = _http_request(f, "HEAD", URL, flags);
63460954Sdes    if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) {
63560376Sdes	_http_seterr(e);
63660376Sdes	goto ouch;
63760376Sdes    }
63860376Sdes
63960376Sdes    while (1) {
64060376Sdes	if ((ln = fgetln(f, &len)) == NULL)
64160376Sdes	    goto fouch;
64260376Sdes	if ((ln[0] == '\r') || (ln[0] == '\n'))
64360376Sdes	    break;
64460376Sdes	while (isspace(ln[len-1]))
64560376Sdes	    --len;
64660376Sdes	ln[len] = '\0'; /* XXX */
64760376Sdes	DEBUG(fprintf(stderr, "header:	 [\033[1m%s\033[m]\n", ln));
64860954Sdes	if ((p = _http_match("Location", ln)) != NULL) {
64960954Sdes	    struct url *url;
65060954Sdes
65160954Sdes	    for (q = p; *q && !isspace(*q); q++)
65260954Sdes		/* VOID */ ;
65360954Sdes	    *q = 0;
65460954Sdes	    if ((url = fetchParseURL(p)) == NULL)
65560954Sdes		goto ouch;
65660954Sdes	    url->offset = URL->offset;
65760954Sdes	    url->length = URL->length;
65860954Sdes	    DEBUG(fprintf(stderr, "location:  [\033[1m%s\033[m]\n", p));
65960954Sdes	    e = fetchStatHTTP(url, us, flags);
66060954Sdes	    fetchFreeURL(url);
66160954Sdes	    fclose(f);
66260954Sdes	    return e;
66360954Sdes	} else if ((p = _http_match("Last-Modified", ln)) != NULL) {
66460376Sdes	    struct tm tm;
66560376Sdes	    char locale[64];
66660376Sdes
66760376Sdes	    strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
66860376Sdes	    setlocale(LC_TIME, "C");
66960376Sdes	    strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
67060376Sdes	    /* XXX should add support for date-2 and date-3 */
67160376Sdes	    setlocale(LC_TIME, locale);
67260376Sdes	    us->atime = us->mtime = timegm(&tm);
67360376Sdes	    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
67460376Sdes			  "%02d:%02d:%02d\033[m]\n",
67560376Sdes			  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
67660376Sdes			  tm.tm_hour, tm.tm_min, tm.tm_sec));
67760376Sdes	} else if ((p = _http_match("Content-Length", ln)) != NULL) {
67860376Sdes	    us->size = 0;
67960376Sdes	    while (*p && isdigit(*p))
68060376Sdes		us->size = us->size * 10 + (*p++ - '0');
68160376Sdes	    DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size));
68260376Sdes	}
68360376Sdes    }
68460581Sdes
68560581Sdes    fclose(f);
68660376Sdes    return 0;
68760376Sdes ouch:
68860376Sdes    _http_seterr(999); /* XXX do this properly RSN */
68960376Sdes fouch:
69060376Sdes    fclose(f);
69160376Sdes    return -1;
69240975Sdes}
69341989Sdes
69441989Sdes/*
69541989Sdes * List a directory
69641989Sdes */
69741989Sdesstruct url_ent *
69841989SdesfetchListHTTP(struct url *url, char *flags)
69941989Sdes{
70041989Sdes    warnx("fetchListHTTP(): not implemented");
70141989Sdes    return NULL;
70241989Sdes}
703