]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - lib/libfetch/http.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / lib / libfetch / http.c
1 /*-
2  * Copyright (c) 2000-2004 Dag-Erling Coïdan Smørgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 /*
33  * The following copyright applies to the base64 code:
34  *
35  *-
36  * Copyright 1997 Massachusetts Institute of Technology
37  *
38  * Permission to use, copy, modify, and distribute this software and
39  * its documentation for any purpose and without fee is hereby
40  * granted, provided that both the above copyright notice and this
41  * permission notice appear in all copies, that both the above
42  * copyright notice and this permission notice appear in all
43  * supporting documentation, and that the name of M.I.T. not be used
44  * in advertising or publicity pertaining to distribution of the
45  * software without specific, written prior permission.  M.I.T. makes
46  * no representations about the suitability of this software for any
47  * purpose.  It is provided "as is" without express or implied
48  * warranty.
49  *
50  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
51  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
52  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
54  * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
55  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
56  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
57  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
58  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
60  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61  * SUCH DAMAGE.
62  */
63
64 #include <sys/param.h>
65 #include <sys/socket.h>
66 #include <sys/time.h>
67
68 #include <ctype.h>
69 #include <err.h>
70 #include <errno.h>
71 #include <locale.h>
72 #include <netdb.h>
73 #include <stdarg.h>
74 #include <stdio.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <time.h>
78 #include <unistd.h>
79 #include <md5.h>
80
81 #include <netinet/in.h>
82 #include <netinet/tcp.h>
83
84 #include "fetch.h"
85 #include "common.h"
86 #include "httperr.h"
87
88 /* Maximum number of redirects to follow */
89 #define MAX_REDIRECT 5
90
91 /* Symbolic names for reply codes we care about */
92 #define HTTP_OK                 200
93 #define HTTP_PARTIAL            206
94 #define HTTP_MOVED_PERM         301
95 #define HTTP_MOVED_TEMP         302
96 #define HTTP_SEE_OTHER          303
97 #define HTTP_NOT_MODIFIED       304
98 #define HTTP_TEMP_REDIRECT      307
99 #define HTTP_NEED_AUTH          401
100 #define HTTP_NEED_PROXY_AUTH    407
101 #define HTTP_BAD_RANGE          416
102 #define HTTP_PROTOCOL_ERROR     999
103
104 #define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
105                             || (xyz) == HTTP_MOVED_TEMP \
106                             || (xyz) == HTTP_TEMP_REDIRECT \
107                             || (xyz) == HTTP_SEE_OTHER)
108
109 #define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
110
111
112 /*****************************************************************************
113  * I/O functions for decoding chunked streams
114  */
115
116 struct httpio
117 {
118         conn_t          *conn;          /* connection */
119         int              chunked;       /* chunked mode */
120         char            *buf;           /* chunk buffer */
121         size_t           bufsize;       /* size of chunk buffer */
122         ssize_t          buflen;        /* amount of data currently in buffer */
123         int              bufpos;        /* current read offset in buffer */
124         int              eof;           /* end-of-file flag */
125         int              error;         /* error flag */
126         size_t           chunksize;     /* remaining size of current chunk */
127 #ifndef NDEBUG
128         size_t           total;
129 #endif
130 };
131
132 /*
133  * Get next chunk header
134  */
135 static int
136 http_new_chunk(struct httpio *io)
137 {
138         char *p;
139
140         if (fetch_getln(io->conn) == -1)
141                 return (-1);
142
143         if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
144                 return (-1);
145
146         for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
147                 if (*p == ';')
148                         break;
149                 if (!isxdigit((unsigned char)*p))
150                         return (-1);
151                 if (isdigit((unsigned char)*p)) {
152                         io->chunksize = io->chunksize * 16 +
153                             *p - '0';
154                 } else {
155                         io->chunksize = io->chunksize * 16 +
156                             10 + tolower((unsigned char)*p) - 'a';
157                 }
158         }
159
160 #ifndef NDEBUG
161         if (fetchDebug) {
162                 io->total += io->chunksize;
163                 if (io->chunksize == 0)
164                         fprintf(stderr, "%s(): end of last chunk\n", __func__);
165                 else
166                         fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
167                             __func__, (unsigned long)io->chunksize,
168                             (unsigned long)io->total);
169         }
170 #endif
171
172         return (io->chunksize);
173 }
174
175 /*
176  * Grow the input buffer to at least len bytes
177  */
178 static inline int
179 http_growbuf(struct httpio *io, size_t len)
180 {
181         char *tmp;
182
183         if (io->bufsize >= len)
184                 return (0);
185
186         if ((tmp = realloc(io->buf, len)) == NULL)
187                 return (-1);
188         io->buf = tmp;
189         io->bufsize = len;
190         return (0);
191 }
192
193 /*
194  * Fill the input buffer, do chunk decoding on the fly
195  */
196 static int
197 http_fillbuf(struct httpio *io, size_t len)
198 {
199         if (io->error)
200                 return (-1);
201         if (io->eof)
202                 return (0);
203
204         if (io->chunked == 0) {
205                 if (http_growbuf(io, len) == -1)
206                         return (-1);
207                 if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
208                         io->error = 1;
209                         return (-1);
210                 }
211                 io->bufpos = 0;
212                 return (io->buflen);
213         }
214
215         if (io->chunksize == 0) {
216                 switch (http_new_chunk(io)) {
217                 case -1:
218                         io->error = 1;
219                         return (-1);
220                 case 0:
221                         io->eof = 1;
222                         return (0);
223                 }
224         }
225
226         if (len > io->chunksize)
227                 len = io->chunksize;
228         if (http_growbuf(io, len) == -1)
229                 return (-1);
230         if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
231                 io->error = 1;
232                 return (-1);
233         }
234         io->chunksize -= io->buflen;
235
236         if (io->chunksize == 0) {
237                 char endl[2];
238
239                 if (fetch_read(io->conn, endl, 2) != 2 ||
240                     endl[0] != '\r' || endl[1] != '\n')
241                         return (-1);
242         }
243
244         io->bufpos = 0;
245
246         return (io->buflen);
247 }
248
249 /*
250  * Read function
251  */
252 static int
253 http_readfn(void *v, char *buf, int len)
254 {
255         struct httpio *io = (struct httpio *)v;
256         int l, pos;
257
258         if (io->error)
259                 return (-1);
260         if (io->eof)
261                 return (0);
262
263         for (pos = 0; len > 0; pos += l, len -= l) {
264                 /* empty buffer */
265                 if (!io->buf || io->bufpos == io->buflen)
266                         if (http_fillbuf(io, len) < 1)
267                                 break;
268                 l = io->buflen - io->bufpos;
269                 if (len < l)
270                         l = len;
271                 memcpy(buf + pos, io->buf + io->bufpos, l);
272                 io->bufpos += l;
273         }
274
275         if (!pos && io->error)
276                 return (-1);
277         return (pos);
278 }
279
280 /*
281  * Write function
282  */
283 static int
284 http_writefn(void *v, const char *buf, int len)
285 {
286         struct httpio *io = (struct httpio *)v;
287
288         return (fetch_write(io->conn, buf, len));
289 }
290
291 /*
292  * Close function
293  */
294 static int
295 http_closefn(void *v)
296 {
297         struct httpio *io = (struct httpio *)v;
298         int r;
299
300         r = fetch_close(io->conn);
301         if (io->buf)
302                 free(io->buf);
303         free(io);
304         return (r);
305 }
306
307 /*
308  * Wrap a file descriptor up
309  */
310 static FILE *
311 http_funopen(conn_t *conn, int chunked)
312 {
313         struct httpio *io;
314         FILE *f;
315
316         if ((io = calloc(1, sizeof(*io))) == NULL) {
317                 fetch_syserr();
318                 return (NULL);
319         }
320         io->conn = conn;
321         io->chunked = chunked;
322         f = funopen(io, http_readfn, http_writefn, NULL, http_closefn);
323         if (f == NULL) {
324                 fetch_syserr();
325                 free(io);
326                 return (NULL);
327         }
328         return (f);
329 }
330
331
332 /*****************************************************************************
333  * Helper functions for talking to the server and parsing its replies
334  */
335
336 /* Header types */
337 typedef enum {
338         hdr_syserror = -2,
339         hdr_error = -1,
340         hdr_end = 0,
341         hdr_unknown = 1,
342         hdr_content_length,
343         hdr_content_range,
344         hdr_last_modified,
345         hdr_location,
346         hdr_transfer_encoding,
347         hdr_www_authenticate,
348         hdr_proxy_authenticate,
349 } hdr_t;
350
351 /* Names of interesting headers */
352 static struct {
353         hdr_t            num;
354         const char      *name;
355 } hdr_names[] = {
356         { hdr_content_length,           "Content-Length" },
357         { hdr_content_range,            "Content-Range" },
358         { hdr_last_modified,            "Last-Modified" },
359         { hdr_location,                 "Location" },
360         { hdr_transfer_encoding,        "Transfer-Encoding" },
361         { hdr_www_authenticate,         "WWW-Authenticate" },
362         { hdr_proxy_authenticate,       "Proxy-Authenticate" },
363         { hdr_unknown,                  NULL },
364 };
365
366 /*
367  * Send a formatted line; optionally echo to terminal
368  */
369 static int
370 http_cmd(conn_t *conn, const char *fmt, ...)
371 {
372         va_list ap;
373         size_t len;
374         char *msg;
375         int r;
376
377         va_start(ap, fmt);
378         len = vasprintf(&msg, fmt, ap);
379         va_end(ap);
380
381         if (msg == NULL) {
382                 errno = ENOMEM;
383                 fetch_syserr();
384                 return (-1);
385         }
386
387         r = fetch_putln(conn, msg, len);
388         free(msg);
389
390         if (r == -1) {
391                 fetch_syserr();
392                 return (-1);
393         }
394
395         return (0);
396 }
397
398 /*
399  * Get and parse status line
400  */
401 static int
402 http_get_reply(conn_t *conn)
403 {
404         char *p;
405
406         if (fetch_getln(conn) == -1)
407                 return (-1);
408         /*
409          * A valid status line looks like "HTTP/m.n xyz reason" where m
410          * and n are the major and minor protocol version numbers and xyz
411          * is the reply code.
412          * Unfortunately, there are servers out there (NCSA 1.5.1, to name
413          * just one) that do not send a version number, so we can't rely
414          * on finding one, but if we do, insist on it being 1.0 or 1.1.
415          * We don't care about the reason phrase.
416          */
417         if (strncmp(conn->buf, "HTTP", 4) != 0)
418                 return (HTTP_PROTOCOL_ERROR);
419         p = conn->buf + 4;
420         if (*p == '/') {
421                 if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
422                         return (HTTP_PROTOCOL_ERROR);
423                 p += 4;
424         }
425         if (*p != ' ' ||
426             !isdigit((unsigned char)p[1]) ||
427             !isdigit((unsigned char)p[2]) ||
428             !isdigit((unsigned char)p[3]))
429                 return (HTTP_PROTOCOL_ERROR);
430
431         conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
432         return (conn->err);
433 }
434
435 /*
436  * Check a header; if the type matches the given string, return a pointer
437  * to the beginning of the value.
438  */
439 static const char *
440 http_match(const char *str, const char *hdr)
441 {
442         while (*str && *hdr &&
443             tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
444                 /* nothing */;
445         if (*str || *hdr != ':')
446                 return (NULL);
447         while (*hdr && isspace((unsigned char)*++hdr))
448                 /* nothing */;
449         return (hdr);
450 }
451
452
453 /*
454  * Get the next header and return the appropriate symbolic code.  We
455  * need to read one line ahead for checking for a continuation line
456  * belonging to the current header (continuation lines start with
457  * white space). 
458  *
459  * We get called with a fresh line already in the conn buffer, either
460  * from the previous http_next_header() invocation, or, the first
461  * time, from a fetch_getln() performed by our caller.
462  *
463  * This stops when we encounter an empty line (we dont read beyond the header
464  * area).
465  * 
466  * Note that the "headerbuf" is just a place to return the result. Its
467  * contents are not used for the next call. This means that no cleanup
468  * is needed when ie doing another connection, just call the cleanup when
469  * fully done to deallocate memory.
470  */
471
472 /* Limit the max number of continuation lines to some reasonable value */
473 #define HTTP_MAX_CONT_LINES 10
474
475 /* Place into which to build a header from one or several lines */
476 typedef struct {
477         char    *buf;           /* buffer */
478         size_t   bufsize;       /* buffer size */
479         size_t   buflen;        /* length of buffer contents */
480 } http_headerbuf_t;
481
482 static void
483 init_http_headerbuf(http_headerbuf_t *buf)
484 {
485         buf->buf = NULL;
486         buf->bufsize = 0;
487         buf->buflen = 0;
488 }
489
490 static void 
491 clean_http_headerbuf(http_headerbuf_t *buf)
492 {
493         if (buf->buf)
494                 free(buf->buf);
495         init_http_headerbuf(buf);
496 }
497
498 /* Remove whitespace at the end of the buffer */
499 static void 
500 http_conn_trimright(conn_t *conn)
501 {
502         while (conn->buflen && 
503                isspace((unsigned char)conn->buf[conn->buflen - 1]))
504                 conn->buflen--;
505         conn->buf[conn->buflen] = '\0';
506 }
507
508 static hdr_t
509 http_next_header(conn_t *conn, http_headerbuf_t *hbuf, const char **p)
510 {
511         int i, len;
512
513         /* 
514          * Have to do the stripping here because of the first line. So
515          * it's done twice for the subsequent lines. No big deal 
516          */
517         http_conn_trimright(conn);
518         if (conn->buflen == 0)
519                 return (hdr_end);
520
521         /* Copy the line to the headerbuf */
522         if (hbuf->bufsize < conn->buflen + 1) {
523                 if ((hbuf->buf = realloc(hbuf->buf, conn->buflen + 1)) == NULL)
524                         return (hdr_syserror);
525                 hbuf->bufsize = conn->buflen + 1;
526         }
527         strcpy(hbuf->buf, conn->buf);
528         hbuf->buflen = conn->buflen;
529
530         /* 
531          * Fetch possible continuation lines. Stop at 1st non-continuation
532          * and leave it in the conn buffer 
533          */
534         for (i = 0; i < HTTP_MAX_CONT_LINES; i++) {
535                 if (fetch_getln(conn) == -1)
536                         return (hdr_syserror);
537
538                 /* 
539                  * Note: we carry on the idea from the previous version
540                  * that a pure whitespace line is equivalent to an empty
541                  * one (so it's not continuation and will be handled when
542                  * we are called next) 
543                  */
544                 http_conn_trimright(conn);
545                 if (conn->buf[0] != ' ' && conn->buf[0] != "\t"[0])
546                         break;
547
548                 /* Got a continuation line. Concatenate to previous */
549                 len = hbuf->buflen + conn->buflen;
550                 if (hbuf->bufsize < len + 1) {
551                         len *= 2;
552                         if ((hbuf->buf = realloc(hbuf->buf, len + 1)) == NULL)
553                                 return (hdr_syserror);
554                         hbuf->bufsize = len + 1;
555                 }
556                 strcpy(hbuf->buf + hbuf->buflen, conn->buf);
557                 hbuf->buflen += conn->buflen;
558         } 
559
560         /*
561          * We could check for malformed headers but we don't really care.
562          * A valid header starts with a token immediately followed by a
563          * colon; a token is any sequence of non-control, non-whitespace
564          * characters except "()<>@,;:\\\"{}".
565          */
566         for (i = 0; hdr_names[i].num != hdr_unknown; i++)
567                 if ((*p = http_match(hdr_names[i].name, hbuf->buf)) != NULL)
568                         return (hdr_names[i].num);
569
570         return (hdr_unknown);
571 }
572
573 /**************************
574  * [Proxy-]Authenticate header parsing
575  */
576
577 /* 
578  * Read doublequote-delimited string into output buffer obuf (allocated 
579  * by caller, whose responsibility it is to ensure that it's big enough)
580  * cp points to the first char after the initial '"'
581  * Handles \ quoting 
582  * Returns pointer to the first char after the terminating double quote, or 
583  * NULL for error.
584  */
585 static const char *
586 http_parse_headerstring(const char *cp, char *obuf)
587 {
588         for (;;) {
589                 switch (*cp) {
590                 case 0: /* Unterminated string */
591                         *obuf = 0;
592                         return (NULL);
593                 case '"': /* Ending quote */
594                         *obuf = 0;
595                         return (++cp);
596                 case '\\':
597                         if (*++cp == 0) {
598                                 *obuf = 0;
599                                 return (NULL);
600                         }
601                         /* FALLTHROUGH */
602                 default:
603                         *obuf++ = *cp++;
604                 }
605         }
606 }
607
608 /* Http auth challenge schemes */
609 typedef enum {HTTPAS_UNKNOWN, HTTPAS_BASIC,HTTPAS_DIGEST} http_auth_schemes_t;
610
611 /* Data holder for a Basic or Digest challenge. */
612 typedef struct {
613         http_auth_schemes_t scheme;
614         char    *realm;
615         char    *qop;
616         char    *nonce;
617         char    *opaque;
618         char    *algo;
619         int      stale;
620         int      nc; /* Nonce count */
621 } http_auth_challenge_t;
622
623 static void 
624 init_http_auth_challenge(http_auth_challenge_t *b)
625 {
626         b->scheme = HTTPAS_UNKNOWN;
627         b->realm = b->qop = b->nonce = b->opaque = b->algo = NULL;
628         b->stale = b->nc = 0;
629 }
630
631 static void 
632 clean_http_auth_challenge(http_auth_challenge_t *b)
633 {
634         if (b->realm) 
635                 free(b->realm);
636         if (b->qop) 
637                 free(b->qop);
638         if (b->nonce) 
639                 free(b->nonce);
640         if (b->opaque) 
641                 free(b->opaque);
642         if (b->algo) 
643                 free(b->algo);
644         init_http_auth_challenge(b);
645 }
646
647 /* Data holder for an array of challenges offered in an http response. */
648 #define MAX_CHALLENGES 10
649 typedef struct {
650         http_auth_challenge_t *challenges[MAX_CHALLENGES];
651         int     count; /* Number of parsed challenges in the array */
652         int     valid; /* We did parse an authenticate header */
653 } http_auth_challenges_t;
654
655 static void 
656 init_http_auth_challenges(http_auth_challenges_t *cs)
657 {
658         int i;
659         for (i = 0; i < MAX_CHALLENGES; i++)
660                 cs->challenges[i] = NULL;
661         cs->count = cs->valid = 0;
662 }
663
664 static void 
665 clean_http_auth_challenges(http_auth_challenges_t *cs)
666 {
667         int i;
668         /* We rely on non-zero pointers being allocated, not on the count */
669         for (i = 0; i < MAX_CHALLENGES; i++) {
670                 if (cs->challenges[i] != NULL) {
671                         clean_http_auth_challenge(cs->challenges[i]);
672                         free(cs->challenges[i]);
673                 }
674         }
675         init_http_auth_challenges(cs);
676 }
677
678 /* 
679  * Enumeration for lexical elements. Separators will be returned as their own
680  * ascii value
681  */
682 typedef enum {HTTPHL_WORD=256, HTTPHL_STRING=257, HTTPHL_END=258,
683               HTTPHL_ERROR = 259} http_header_lex_t;
684
685 /* 
686  * Determine what kind of token comes next and return possible value
687  * in buf, which is supposed to have been allocated big enough by
688  * caller. Advance input pointer and return element type. 
689  */
690 static int 
691 http_header_lex(const char **cpp, char *buf)
692 {
693         size_t l;
694         /* Eat initial whitespace */
695         *cpp += strspn(*cpp, " \t");
696         if (**cpp == 0)
697                 return (HTTPHL_END);
698
699         /* Separator ? */
700         if (**cpp == ',' || **cpp == '=')
701                 return (*((*cpp)++));
702
703         /* String ? */
704         if (**cpp == '"') {
705                 *cpp = http_parse_headerstring(++*cpp, buf);
706                 if (*cpp == NULL)
707                         return (HTTPHL_ERROR);
708                 return (HTTPHL_STRING);
709         }
710
711         /* Read other token, until separator or whitespace */
712         l = strcspn(*cpp, " \t,=");
713         memcpy(buf, *cpp, l);
714         buf[l] = 0;
715         *cpp += l;
716         return (HTTPHL_WORD);
717 }
718
719 /* 
720  * Read challenges from http xxx-authenticate header and accumulate them
721  * in the challenges list structure.
722  *
723  * Headers with multiple challenges are specified by rfc2617, but
724  * servers (ie: squid) often send them in separate headers instead,
725  * which in turn is forbidden by the http spec (multiple headers with
726  * the same name are only allowed for pure comma-separated lists, see
727  * rfc2616 sec 4.2).
728  *
729  * We support both approaches anyway
730  */
731 static int 
732 http_parse_authenticate(const char *cp, http_auth_challenges_t *cs)
733 {
734         int ret = -1;
735         http_header_lex_t lex;
736         char *key = malloc(strlen(cp) + 1);
737         char *value = malloc(strlen(cp) + 1);
738         char *buf = malloc(strlen(cp) + 1);
739
740         if (key == NULL || value == NULL || buf == NULL) {
741                 fetch_syserr();
742                 goto out;
743         }
744
745         /* In any case we've seen the header and we set the valid bit */
746         cs->valid = 1;
747
748         /* Need word first */
749         lex = http_header_lex(&cp, key);
750         if (lex != HTTPHL_WORD)
751                 goto out;
752
753         /* Loop on challenges */
754         for (; cs->count < MAX_CHALLENGES; cs->count++) {
755                 cs->challenges[cs->count] = 
756                         malloc(sizeof(http_auth_challenge_t));
757                 if (cs->challenges[cs->count] == NULL) {
758                         fetch_syserr();
759                         goto out;
760                 }
761                 init_http_auth_challenge(cs->challenges[cs->count]);
762                 if (!strcasecmp(key, "basic")) {
763                         cs->challenges[cs->count]->scheme = HTTPAS_BASIC;
764                 } else if (!strcasecmp(key, "digest")) {
765                         cs->challenges[cs->count]->scheme = HTTPAS_DIGEST;
766                 } else {
767                         cs->challenges[cs->count]->scheme = HTTPAS_UNKNOWN;
768                         /* 
769                          * Continue parsing as basic or digest may
770                          * follow, and the syntax is the same for
771                          * all. We'll just ignore this one when
772                          * looking at the list
773                          */
774                 }
775         
776                 /* Loop on attributes */
777                 for (;;) {
778                         /* Key */
779                         lex = http_header_lex(&cp, key);
780                         if (lex != HTTPHL_WORD)
781                                 goto out;
782
783                         /* Equal sign */
784                         lex = http_header_lex(&cp, buf);
785                         if (lex != '=')
786                                 goto out;
787
788                         /* Value */
789                         lex = http_header_lex(&cp, value);
790                         if (lex != HTTPHL_WORD && lex != HTTPHL_STRING)
791                                 goto out;
792
793                         if (!strcasecmp(key, "realm"))
794                                 cs->challenges[cs->count]->realm = 
795                                         strdup(value);
796                         else if (!strcasecmp(key, "qop"))
797                                 cs->challenges[cs->count]->qop = 
798                                         strdup(value);
799                         else if (!strcasecmp(key, "nonce"))
800                                 cs->challenges[cs->count]->nonce = 
801                                         strdup(value);
802                         else if (!strcasecmp(key, "opaque"))
803                                 cs->challenges[cs->count]->opaque = 
804                                         strdup(value);
805                         else if (!strcasecmp(key, "algorithm"))
806                                 cs->challenges[cs->count]->algo = 
807                                         strdup(value);
808                         else if (!strcasecmp(key, "stale"))
809                                 cs->challenges[cs->count]->stale = 
810                                         strcasecmp(value, "no");
811                         /* Else ignore unknown attributes */
812
813                         /* Comma or Next challenge or End */
814                         lex = http_header_lex(&cp, key);
815                         /* 
816                          * If we get a word here, this is the beginning of the
817                          * next challenge. Break the attributes loop 
818                          */
819                         if (lex == HTTPHL_WORD)
820                                 break;
821
822                         if (lex == HTTPHL_END) {
823                                 /* End while looking for ',' is normal exit */
824                                 cs->count++;
825                                 ret = 0;
826                                 goto out;
827                         }
828                         /* Anything else is an error */
829                         if (lex != ',')
830                                 goto out;
831
832                 } /* End attributes loop */
833         } /* End challenge loop */
834
835         /* 
836          * Challenges max count exceeded. This really can't happen
837          * with normal data, something's fishy -> error 
838          */ 
839
840 out:
841         if (key)
842                 free(key);
843         if (value)
844                 free(value);
845         if (buf)
846                 free(buf);
847         return (ret);
848 }
849
850
851 /*
852  * Parse a last-modified header
853  */
854 static int
855 http_parse_mtime(const char *p, time_t *mtime)
856 {
857         char locale[64], *r;
858         struct tm tm;
859
860         strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
861         setlocale(LC_TIME, "C");
862         r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
863         /* XXX should add support for date-2 and date-3 */
864         setlocale(LC_TIME, locale);
865         if (r == NULL)
866                 return (-1);
867         DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
868                   "%02d:%02d:%02d]\n",
869                   tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
870                   tm.tm_hour, tm.tm_min, tm.tm_sec));
871         *mtime = timegm(&tm);
872         return (0);
873 }
874
875 /*
876  * Parse a content-length header
877  */
878 static int
879 http_parse_length(const char *p, off_t *length)
880 {
881         off_t len;
882
883         for (len = 0; *p && isdigit((unsigned char)*p); ++p)
884                 len = len * 10 + (*p - '0');
885         if (*p)
886                 return (-1);
887         DEBUG(fprintf(stderr, "content length: [%lld]\n",
888             (long long)len));
889         *length = len;
890         return (0);
891 }
892
893 /*
894  * Parse a content-range header
895  */
896 static int
897 http_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
898 {
899         off_t first, last, len;
900
901         if (strncasecmp(p, "bytes ", 6) != 0)
902                 return (-1);
903         p += 6;
904         if (*p == '*') {
905                 first = last = -1;
906                 ++p;
907         } else {
908                 for (first = 0; *p && isdigit((unsigned char)*p); ++p)
909                         first = first * 10 + *p - '0';
910                 if (*p != '-')
911                         return (-1);
912                 for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
913                         last = last * 10 + *p - '0';
914         }
915         if (first > last || *p != '/')
916                 return (-1);
917         for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
918                 len = len * 10 + *p - '0';
919         if (*p || len < last - first + 1)
920                 return (-1);
921         if (first == -1) {
922                 DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
923                     (long long)len));
924                 *length = 0;
925         } else {
926                 DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
927                     (long long)first, (long long)last, (long long)len));
928                 *length = last - first + 1;
929         }
930         *offset = first;
931         *size = len;
932         return (0);
933 }
934
935
936 /*****************************************************************************
937  * Helper functions for authorization
938  */
939
940 /*
941  * Base64 encoding
942  */
943 static char *
944 http_base64(const char *src)
945 {
946         static const char base64[] =
947             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
948             "abcdefghijklmnopqrstuvwxyz"
949             "0123456789+/";
950         char *str, *dst;
951         size_t l;
952         int t, r;
953
954         l = strlen(src);
955         if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
956                 return (NULL);
957         dst = str;
958         r = 0;
959
960         while (l >= 3) {
961                 t = (src[0] << 16) | (src[1] << 8) | src[2];
962                 dst[0] = base64[(t >> 18) & 0x3f];
963                 dst[1] = base64[(t >> 12) & 0x3f];
964                 dst[2] = base64[(t >> 6) & 0x3f];
965                 dst[3] = base64[(t >> 0) & 0x3f];
966                 src += 3; l -= 3;
967                 dst += 4; r += 4;
968         }
969
970         switch (l) {
971         case 2:
972                 t = (src[0] << 16) | (src[1] << 8);
973                 dst[0] = base64[(t >> 18) & 0x3f];
974                 dst[1] = base64[(t >> 12) & 0x3f];
975                 dst[2] = base64[(t >> 6) & 0x3f];
976                 dst[3] = '=';
977                 dst += 4;
978                 r += 4;
979                 break;
980         case 1:
981                 t = src[0] << 16;
982                 dst[0] = base64[(t >> 18) & 0x3f];
983                 dst[1] = base64[(t >> 12) & 0x3f];
984                 dst[2] = dst[3] = '=';
985                 dst += 4;
986                 r += 4;
987                 break;
988         case 0:
989                 break;
990         }
991
992         *dst = 0;
993         return (str);
994 }
995
996
997 /*
998  * Extract authorization parameters from environment value.
999  * The value is like scheme:realm:user:pass
1000  */
1001 typedef struct {
1002         char    *scheme;
1003         char    *realm;
1004         char    *user;
1005         char    *password;
1006 } http_auth_params_t;
1007
1008 static void
1009 init_http_auth_params(http_auth_params_t *s)
1010 {
1011         s->scheme = s->realm = s->user = s->password = 0;
1012 }
1013
1014 static void 
1015 clean_http_auth_params(http_auth_params_t *s)
1016 {
1017         if (s->scheme) 
1018                 free(s->scheme);
1019         if (s->realm) 
1020                 free(s->realm);
1021         if (s->user) 
1022                 free(s->user);
1023         if (s->password) 
1024                 free(s->password);
1025         init_http_auth_params(s);
1026 }
1027
1028 static int
1029 http_authfromenv(const char *p, http_auth_params_t *parms)
1030 {
1031         int ret = -1;
1032         char *v, *ve;
1033         char *str = strdup(p);
1034
1035         if (str == NULL) {
1036                 fetch_syserr();
1037                 return (-1);
1038         }
1039         v = str;
1040
1041         if ((ve = strchr(v, ':')) == NULL)
1042                 goto out;
1043
1044         *ve = 0;
1045         if ((parms->scheme = strdup(v)) == NULL) {
1046                 fetch_syserr();
1047                 goto out;
1048         }
1049         v = ve + 1;
1050
1051         if ((ve = strchr(v, ':')) == NULL)
1052                 goto out;
1053
1054         *ve = 0;
1055         if ((parms->realm = strdup(v)) == NULL) {
1056                 fetch_syserr();
1057                 goto out;
1058         }
1059         v = ve + 1;
1060
1061         if ((ve = strchr(v, ':')) == NULL)
1062                 goto out;
1063
1064         *ve = 0;
1065         if ((parms->user = strdup(v)) == NULL) {
1066                 fetch_syserr();
1067                 goto out;
1068         }
1069         v = ve + 1;
1070
1071
1072         if ((parms->password = strdup(v)) == NULL) {
1073                 fetch_syserr();
1074                 goto out;
1075         }
1076         ret = 0;
1077 out:
1078         if (ret == -1) 
1079                 clean_http_auth_params(parms);
1080         if (str)
1081                 free(str);
1082         return (ret);
1083 }
1084
1085
1086 /* 
1087  * Digest response: the code to compute the digest is taken from the
1088  * sample implementation in RFC2616 
1089  */
1090 #define IN
1091 #define OUT
1092
1093 #define HASHLEN 16
1094 typedef char HASH[HASHLEN];
1095 #define HASHHEXLEN 32
1096 typedef char HASHHEX[HASHHEXLEN+1];
1097
1098 static const char *hexchars = "0123456789abcdef";
1099 static void 
1100 CvtHex(IN HASH Bin, OUT HASHHEX Hex)
1101 {
1102         unsigned short i;
1103         unsigned char j;
1104
1105         for (i = 0; i < HASHLEN; i++) {
1106                 j = (Bin[i] >> 4) & 0xf;
1107                 Hex[i*2] = hexchars[j];
1108                 j = Bin[i] & 0xf;
1109                 Hex[i*2+1] = hexchars[j];
1110         };
1111         Hex[HASHHEXLEN] = '\0';
1112 };
1113
1114 /* calculate H(A1) as per spec */
1115 static void 
1116 DigestCalcHA1(
1117         IN char * pszAlg,
1118         IN char * pszUserName,
1119         IN char * pszRealm,
1120         IN char * pszPassword,
1121         IN char * pszNonce,
1122         IN char * pszCNonce,
1123         OUT HASHHEX SessionKey
1124         )
1125 {
1126         MD5_CTX Md5Ctx;
1127         HASH HA1;
1128
1129         MD5Init(&Md5Ctx);
1130         MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
1131         MD5Update(&Md5Ctx, ":", 1);
1132         MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
1133         MD5Update(&Md5Ctx, ":", 1);
1134         MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
1135         MD5Final(HA1, &Md5Ctx);
1136         if (strcasecmp(pszAlg, "md5-sess") == 0) {
1137
1138                 MD5Init(&Md5Ctx);
1139                 MD5Update(&Md5Ctx, HA1, HASHLEN);
1140                 MD5Update(&Md5Ctx, ":", 1);
1141                 MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1142                 MD5Update(&Md5Ctx, ":", 1);
1143                 MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
1144                 MD5Final(HA1, &Md5Ctx);
1145         };
1146         CvtHex(HA1, SessionKey);
1147 }
1148
1149 /* calculate request-digest/response-digest as per HTTP Digest spec */
1150 static void 
1151 DigestCalcResponse(
1152         IN HASHHEX HA1,           /* H(A1) */
1153         IN char * pszNonce,       /* nonce from server */
1154         IN char * pszNonceCount,  /* 8 hex digits */
1155         IN char * pszCNonce,      /* client nonce */
1156         IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
1157         IN char * pszMethod,      /* method from the request */
1158         IN char * pszDigestUri,   /* requested URL */
1159         IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
1160         OUT HASHHEX Response      /* request-digest or response-digest */
1161         )
1162 {
1163 /*      DEBUG(fprintf(stderr, 
1164                       "Calc: HA1[%s] Nonce[%s] qop[%s] method[%s] URI[%s]\n",
1165                       HA1, pszNonce, pszQop, pszMethod, pszDigestUri));*/
1166         MD5_CTX Md5Ctx;
1167         HASH HA2;
1168         HASH RespHash;
1169         HASHHEX HA2Hex;
1170
1171         // calculate H(A2)
1172         MD5Init(&Md5Ctx);
1173         MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
1174         MD5Update(&Md5Ctx, ":", 1);
1175         MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
1176         if (strcasecmp(pszQop, "auth-int") == 0) {
1177                 MD5Update(&Md5Ctx, ":", 1);
1178                 MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
1179         };
1180         MD5Final(HA2, &Md5Ctx);
1181         CvtHex(HA2, HA2Hex);
1182
1183         // calculate response
1184         MD5Init(&Md5Ctx);
1185         MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
1186         MD5Update(&Md5Ctx, ":", 1);
1187         MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1188         MD5Update(&Md5Ctx, ":", 1);
1189         if (*pszQop) {
1190                 MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
1191                 MD5Update(&Md5Ctx, ":", 1);
1192                 MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
1193                 MD5Update(&Md5Ctx, ":", 1);
1194                 MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
1195                 MD5Update(&Md5Ctx, ":", 1);
1196         };
1197         MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
1198         MD5Final(RespHash, &Md5Ctx);
1199         CvtHex(RespHash, Response);
1200 }
1201
1202 /* 
1203  * Generate/Send a Digest authorization header 
1204  * This looks like: [Proxy-]Authorization: credentials
1205  *
1206  *  credentials      = "Digest" digest-response
1207  *  digest-response  = 1#( username | realm | nonce | digest-uri
1208  *                      | response | [ algorithm ] | [cnonce] |
1209  *                      [opaque] | [message-qop] |
1210  *                          [nonce-count]  | [auth-param] )
1211  *  username         = "username" "=" username-value
1212  *  username-value   = quoted-string
1213  *  digest-uri       = "uri" "=" digest-uri-value
1214  *  digest-uri-value = request-uri   ; As specified by HTTP/1.1
1215  *  message-qop      = "qop" "=" qop-value
1216  *  cnonce           = "cnonce" "=" cnonce-value
1217  *  cnonce-value     = nonce-value
1218  *  nonce-count      = "nc" "=" nc-value
1219  *  nc-value         = 8LHEX
1220  *  response         = "response" "=" request-digest
1221  *  request-digest = <"> 32LHEX <">
1222  */
1223 static int
1224 http_digest_auth(conn_t *conn, const char *hdr, http_auth_challenge_t *c,
1225                  http_auth_params_t *parms, struct url *url)
1226 {
1227         int r;
1228         char noncecount[10];
1229         char cnonce[40];
1230         char *options = 0;
1231
1232         if (!c->realm || !c->nonce) {
1233                 DEBUG(fprintf(stderr, "realm/nonce not set in challenge\n"));
1234                 return(-1);
1235         }
1236         if (!c->algo) 
1237                 c->algo = strdup("");
1238
1239         if (asprintf(&options, "%s%s%s%s", 
1240                      *c->algo? ",algorithm=" : "", c->algo,
1241                      c->opaque? ",opaque=" : "", c->opaque?c->opaque:"")== -1)
1242                 return (-1);
1243
1244         if (!c->qop) {
1245                 c->qop = strdup("");
1246                 *noncecount = 0;
1247                 *cnonce = 0;
1248         } else {
1249                 c->nc++;
1250                 sprintf(noncecount, "%08x", c->nc);
1251                 /* We don't try very hard with the cnonce ... */
1252                 sprintf(cnonce, "%x%lx", getpid(), (unsigned long)time(0));
1253         }
1254
1255         HASHHEX HA1;
1256         DigestCalcHA1(c->algo, parms->user, c->realm,
1257                       parms->password, c->nonce, cnonce, HA1);
1258         DEBUG(fprintf(stderr, "HA1: [%s]\n", HA1));
1259         HASHHEX digest;
1260         DigestCalcResponse(HA1, c->nonce, noncecount, cnonce, c->qop,
1261                            "GET", url->doc, "", digest);
1262
1263         if (c->qop[0]) {
1264                 r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1265                              "nonce=\"%s\",uri=\"%s\",response=\"%s\","
1266                              "qop=\"auth\", cnonce=\"%s\", nc=%s%s",
1267                              hdr, parms->user, c->realm, 
1268                              c->nonce, url->doc, digest,
1269                              cnonce, noncecount, options);
1270         } else {
1271                 r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1272                              "nonce=\"%s\",uri=\"%s\",response=\"%s\"%s",
1273                              hdr, parms->user, c->realm, 
1274                              c->nonce, url->doc, digest, options);
1275         }
1276         if (options)
1277                 free(options);
1278         return (r);
1279 }
1280
1281 /*
1282  * Encode username and password
1283  */
1284 static int
1285 http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
1286 {
1287         char *upw, *auth;
1288         int r;
1289
1290         DEBUG(fprintf(stderr, "basic: usr: [%s]\n", usr));
1291         DEBUG(fprintf(stderr, "basic: pwd: [%s]\n", pwd));
1292         if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
1293                 return (-1);
1294         auth = http_base64(upw);
1295         free(upw);
1296         if (auth == NULL)
1297                 return (-1);
1298         r = http_cmd(conn, "%s: Basic %s", hdr, auth);
1299         free(auth);
1300         return (r);
1301 }
1302
1303 /*
1304  * Chose the challenge to answer and call the appropriate routine to 
1305  * produce the header.
1306  */
1307 static int
1308 http_authorize(conn_t *conn, const char *hdr, http_auth_challenges_t *cs,
1309                http_auth_params_t *parms, struct url *url)
1310 {
1311         http_auth_challenge_t *basic = NULL;
1312         http_auth_challenge_t *digest = NULL;
1313         int i;
1314
1315         /* If user or pass are null we're not happy */
1316         if (!parms->user || !parms->password) {
1317                 DEBUG(fprintf(stderr, "NULL usr or pass\n"));
1318                 return (-1);
1319         }
1320
1321         /* Look for a Digest and a Basic challenge */
1322         for (i = 0; i < cs->count; i++) {
1323                 if (cs->challenges[i]->scheme == HTTPAS_BASIC)
1324                         basic = cs->challenges[i];
1325                 if (cs->challenges[i]->scheme == HTTPAS_DIGEST)
1326                         digest = cs->challenges[i];
1327         }
1328
1329         /* Error if "Digest" was specified and there is no Digest challenge */
1330         if (!digest && (parms->scheme && 
1331                         !strcasecmp(parms->scheme, "digest"))) {
1332                 DEBUG(fprintf(stderr, 
1333                               "Digest auth in env, not supported by peer\n"));
1334                 return (-1);
1335         }
1336         /* 
1337          * If "basic" was specified in the environment, or there is no Digest
1338          * challenge, do the basic thing. Don't need a challenge for this,
1339          * so no need to check basic!=NULL 
1340          */
1341         if (!digest || (parms->scheme && !strcasecmp(parms->scheme,"basic")))
1342                 return (http_basic_auth(conn,hdr,parms->user,parms->password));
1343
1344         /* Else, prefer digest. We just checked that it's not NULL */
1345         return (http_digest_auth(conn, hdr, digest, parms, url));
1346 }
1347
1348 /*****************************************************************************
1349  * Helper functions for connecting to a server or proxy
1350  */
1351
1352 /*
1353  * Connect to the correct HTTP server or proxy.
1354  */
1355 static conn_t *
1356 http_connect(struct url *URL, struct url *purl, const char *flags)
1357 {
1358         conn_t *conn;
1359         int verbose;
1360         int af, val;
1361
1362 #ifdef INET6
1363         af = AF_UNSPEC;
1364 #else
1365         af = AF_INET;
1366 #endif
1367
1368         verbose = CHECK_FLAG('v');
1369         if (CHECK_FLAG('4'))
1370                 af = AF_INET;
1371 #ifdef INET6
1372         else if (CHECK_FLAG('6'))
1373                 af = AF_INET6;
1374 #endif
1375
1376         if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
1377                 URL = purl;
1378         } else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
1379                 /* can't talk http to an ftp server */
1380                 /* XXX should set an error code */
1381                 return (NULL);
1382         }
1383
1384         if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
1385                 /* fetch_connect() has already set an error code */
1386                 return (NULL);
1387         if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
1388             fetch_ssl(conn, verbose) == -1) {
1389                 fetch_close(conn);
1390                 /* grrr */
1391                 errno = EAUTH;
1392                 fetch_syserr();
1393                 return (NULL);
1394         }
1395
1396         val = 1;
1397         setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
1398
1399         return (conn);
1400 }
1401
1402 static struct url *
1403 http_get_proxy(struct url * url, const char *flags)
1404 {
1405         struct url *purl;
1406         char *p;
1407
1408         if (flags != NULL && strchr(flags, 'd') != NULL)
1409                 return (NULL);
1410         if (fetch_no_proxy_match(url->host))
1411                 return (NULL);
1412         if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
1413             *p && (purl = fetchParseURL(p))) {
1414                 if (!*purl->scheme)
1415                         strcpy(purl->scheme, SCHEME_HTTP);
1416                 if (!purl->port)
1417                         purl->port = fetch_default_proxy_port(purl->scheme);
1418                 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1419                         return (purl);
1420                 fetchFreeURL(purl);
1421         }
1422         return (NULL);
1423 }
1424
1425 static void
1426 http_print_html(FILE *out, FILE *in)
1427 {
1428         size_t len;
1429         char *line, *p, *q;
1430         int comment, tag;
1431
1432         comment = tag = 0;
1433         while ((line = fgetln(in, &len)) != NULL) {
1434                 while (len && isspace((unsigned char)line[len - 1]))
1435                         --len;
1436                 for (p = q = line; q < line + len; ++q) {
1437                         if (comment && *q == '-') {
1438                                 if (q + 2 < line + len &&
1439                                     strcmp(q, "-->") == 0) {
1440                                         tag = comment = 0;
1441                                         q += 2;
1442                                 }
1443                         } else if (tag && !comment && *q == '>') {
1444                                 p = q + 1;
1445                                 tag = 0;
1446                         } else if (!tag && *q == '<') {
1447                                 if (q > p)
1448                                         fwrite(p, q - p, 1, out);
1449                                 tag = 1;
1450                                 if (q + 3 < line + len &&
1451                                     strcmp(q, "<!--") == 0) {
1452                                         comment = 1;
1453                                         q += 3;
1454                                 }
1455                         }
1456                 }
1457                 if (!tag && q > p)
1458                         fwrite(p, q - p, 1, out);
1459                 fputc('\n', out);
1460         }
1461 }
1462
1463
1464 /*****************************************************************************
1465  * Core
1466  */
1467
1468 /*
1469  * Send a request and process the reply
1470  *
1471  * XXX This function is way too long, the do..while loop should be split
1472  * XXX off into a separate function.
1473  */
1474 FILE *
1475 http_request(struct url *URL, const char *op, struct url_stat *us,
1476         struct url *purl, const char *flags)
1477 {
1478         char timebuf[80];
1479         char hbuf[MAXHOSTNAMELEN + 7], *host;
1480         conn_t *conn;
1481         struct url *url, *new;
1482         int chunked, direct, ims, noredirect, verbose;
1483         int e, i, n, val;
1484         off_t offset, clength, length, size;
1485         time_t mtime;
1486         const char *p;
1487         FILE *f;
1488         hdr_t h;
1489         struct tm *timestruct;
1490         http_headerbuf_t headerbuf;
1491         http_auth_challenges_t server_challenges;
1492         http_auth_challenges_t proxy_challenges;
1493
1494         /* The following calls don't allocate anything */
1495         init_http_headerbuf(&headerbuf); 
1496         init_http_auth_challenges(&server_challenges);
1497         init_http_auth_challenges(&proxy_challenges);
1498
1499         direct = CHECK_FLAG('d');
1500         noredirect = CHECK_FLAG('A');
1501         verbose = CHECK_FLAG('v');
1502         ims = CHECK_FLAG('i');
1503
1504         if (direct && purl) {
1505                 fetchFreeURL(purl);
1506                 purl = NULL;
1507         }
1508
1509         /* try the provided URL first */
1510         url = URL;
1511
1512         /* if the A flag is set, we only get one try */
1513         n = noredirect ? 1 : MAX_REDIRECT;
1514         i = 0;
1515
1516         e = HTTP_PROTOCOL_ERROR;
1517         do {
1518                 new = NULL;
1519                 chunked = 0;
1520                 offset = 0;
1521                 clength = -1;
1522                 length = -1;
1523                 size = -1;
1524                 mtime = 0;
1525
1526                 /* check port */
1527                 if (!url->port)
1528                         url->port = fetch_default_port(url->scheme);
1529
1530                 /* were we redirected to an FTP URL? */
1531                 if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
1532                         if (strcmp(op, "GET") == 0)
1533                                 return (ftp_request(url, "RETR", us, purl, flags));
1534                         else if (strcmp(op, "HEAD") == 0)
1535                                 return (ftp_request(url, "STAT", us, purl, flags));
1536                 }
1537
1538                 /* connect to server or proxy */
1539                 if ((conn = http_connect(url, purl, flags)) == NULL)
1540                         goto ouch;
1541
1542                 host = url->host;
1543 #ifdef INET6
1544                 if (strchr(url->host, ':')) {
1545                         snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
1546                         host = hbuf;
1547                 }
1548 #endif
1549                 if (url->port != fetch_default_port(url->scheme)) {
1550                         if (host != hbuf) {
1551                                 strcpy(hbuf, host);
1552                                 host = hbuf;
1553                         }
1554                         snprintf(hbuf + strlen(hbuf),
1555                             sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
1556                 }
1557
1558                 /* send request */
1559                 if (verbose)
1560                         fetch_info("requesting %s://%s%s",
1561                             url->scheme, host, url->doc);
1562                 if (purl) {
1563                         http_cmd(conn, "%s %s://%s%s HTTP/1.1",
1564                             op, url->scheme, host, url->doc);
1565                 } else {
1566                         http_cmd(conn, "%s %s HTTP/1.1",
1567                             op, url->doc);
1568                 }
1569
1570                 if (ims && url->ims_time) {
1571                         timestruct = gmtime((time_t *)&url->ims_time);
1572                         (void)strftime(timebuf, 80, "%a, %d %b %Y %T GMT",
1573                             timestruct);
1574                         if (verbose)
1575                                 fetch_info("If-Modified-Since: %s", timebuf);
1576                         http_cmd(conn, "If-Modified-Since: %s", timebuf);
1577                 }
1578                 /* virtual host */
1579                 http_cmd(conn, "Host: %s", host);
1580
1581                 /* 
1582                  * Proxy authorization: we only send auth after we received
1583                  * a 407 error. We do not first try basic anyway (changed 
1584                  * when support was added for digest-auth)
1585                  */
1586                 if (purl && proxy_challenges.valid) {
1587                         http_auth_params_t aparams;
1588                         init_http_auth_params(&aparams);
1589                         if (*purl->user || *purl->pwd) {
1590                                 aparams.user = purl->user ? 
1591                                         strdup(purl->user) : strdup("");
1592                                 aparams.password = purl->pwd?
1593                                         strdup(purl->pwd) : strdup("");
1594                         } else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && 
1595                                    *p != '\0') {
1596                                 if (http_authfromenv(p, &aparams) < 0) {
1597                                         http_seterr(HTTP_NEED_PROXY_AUTH);
1598                                         goto ouch;
1599                                 }
1600                         }
1601                         http_authorize(conn, "Proxy-Authorization", 
1602                                        &proxy_challenges, &aparams, url);
1603                         clean_http_auth_params(&aparams);
1604                 }
1605
1606                 /* 
1607                  * Server authorization: we never send "a priori"
1608                  * Basic auth, which used to be done if user/pass were
1609                  * set in the url. This would be weird because we'd send the
1610                  * password in the clear even if Digest is finally to be 
1611                  * used (it would have made more sense for the
1612                  * pre-digest version to do this when Basic was specified 
1613                  * in the environment) 
1614                  */
1615                 if (server_challenges.valid) {
1616                         http_auth_params_t aparams;
1617                         init_http_auth_params(&aparams);
1618                         if (*url->user || *url->pwd) {
1619                                 aparams.user = url->user ? 
1620                                         strdup(url->user) : strdup("");
1621                                 aparams.password = url->pwd ? 
1622                                         strdup(url->pwd) : strdup("");
1623                         } else if ((p = getenv("HTTP_AUTH")) != NULL && 
1624                                    *p != '\0') {
1625                                 if (http_authfromenv(p, &aparams) < 0) {
1626                                         http_seterr(HTTP_NEED_AUTH);
1627                                         goto ouch;
1628                                 }
1629                         } else if (fetchAuthMethod && 
1630                                    fetchAuthMethod(url) == 0) {
1631                                 aparams.user = url->user ? 
1632                                         strdup(url->user) : strdup("");
1633                                 aparams.password = url->pwd ? 
1634                                         strdup(url->pwd) : strdup("");
1635                         } else {
1636                                 http_seterr(HTTP_NEED_AUTH);
1637                                 goto ouch;
1638                         }
1639                         http_authorize(conn, "Authorization", 
1640                                        &server_challenges, &aparams, url);
1641                         clean_http_auth_params(&aparams);
1642                 }
1643
1644                 /* other headers */
1645                 if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
1646                         if (strcasecmp(p, "auto") == 0)
1647                                 http_cmd(conn, "Referer: %s://%s%s",
1648                                     url->scheme, host, url->doc);
1649                         else
1650                                 http_cmd(conn, "Referer: %s", p);
1651                 }
1652                 if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
1653                         http_cmd(conn, "User-Agent: %s", p);
1654                 else
1655                         http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
1656                 if (url->offset > 0)
1657                         http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
1658                 http_cmd(conn, "Connection: close");
1659                 http_cmd(conn, "");
1660
1661                 /*
1662                  * Force the queued request to be dispatched.  Normally, one
1663                  * would do this with shutdown(2) but squid proxies can be
1664                  * configured to disallow such half-closed connections.  To
1665                  * be compatible with such configurations, fiddle with socket
1666                  * options to force the pending data to be written.
1667                  */
1668                 val = 0;
1669                 setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
1670                            sizeof(val));
1671                 val = 1;
1672                 setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
1673                            sizeof(val));
1674
1675                 /* get reply */
1676                 switch (http_get_reply(conn)) {
1677                 case HTTP_OK:
1678                 case HTTP_PARTIAL:
1679                 case HTTP_NOT_MODIFIED:
1680                         /* fine */
1681                         break;
1682                 case HTTP_MOVED_PERM:
1683                 case HTTP_MOVED_TEMP:
1684                 case HTTP_SEE_OTHER:
1685                         /*
1686                          * Not so fine, but we still have to read the
1687                          * headers to get the new location.
1688                          */
1689                         break;
1690                 case HTTP_NEED_AUTH:
1691                         if (server_challenges.valid) {
1692                                 /*
1693                                  * We already sent out authorization code,
1694                                  * so there's nothing more we can do.
1695                                  */
1696                                 http_seterr(conn->err);
1697                                 goto ouch;
1698                         }
1699                         /* try again, but send the password this time */
1700                         if (verbose)
1701                                 fetch_info("server requires authorization");
1702                         break;
1703                 case HTTP_NEED_PROXY_AUTH:
1704                         if (proxy_challenges.valid) {
1705                                 /*
1706                                  * We already sent our proxy
1707                                  * authorization code, so there's
1708                                  * nothing more we can do. */
1709                                 http_seterr(conn->err);
1710                                 goto ouch;
1711                         }
1712                         /* try again, but send the password this time */
1713                         if (verbose)
1714                                 fetch_info("proxy requires authorization");
1715                         break;
1716                 case HTTP_BAD_RANGE:
1717                         /*
1718                          * This can happen if we ask for 0 bytes because
1719                          * we already have the whole file.  Consider this
1720                          * a success for now, and check sizes later.
1721                          */
1722                         break;
1723                 case HTTP_PROTOCOL_ERROR:
1724                         /* fall through */
1725                 case -1:
1726                         fetch_syserr();
1727                         goto ouch;
1728                 default:
1729                         http_seterr(conn->err);
1730                         if (!verbose)
1731                                 goto ouch;
1732                         /* fall through so we can get the full error message */
1733                 }
1734
1735                 /* get headers. http_next_header expects one line readahead */
1736                 if (fetch_getln(conn) == -1) {
1737                     fetch_syserr();
1738                     goto ouch;
1739                 }
1740                 do {
1741                     switch ((h = http_next_header(conn, &headerbuf, &p))) {
1742                         case hdr_syserror:
1743                                 fetch_syserr();
1744                                 goto ouch;
1745                         case hdr_error:
1746                                 http_seterr(HTTP_PROTOCOL_ERROR);
1747                                 goto ouch;
1748                         case hdr_content_length:
1749                                 http_parse_length(p, &clength);
1750                                 break;
1751                         case hdr_content_range:
1752                                 http_parse_range(p, &offset, &length, &size);
1753                                 break;
1754                         case hdr_last_modified:
1755                                 http_parse_mtime(p, &mtime);
1756                                 break;
1757                         case hdr_location:
1758                                 if (!HTTP_REDIRECT(conn->err))
1759                                         break;
1760                                 if (new)
1761                                         free(new);
1762                                 if (verbose)
1763                                         fetch_info("%d redirect to %s", conn->err, p);
1764                                 if (*p == '/')
1765                                         /* absolute path */
1766                                         new = fetchMakeURL(url->scheme, url->host, url->port, p,
1767                                             url->user, url->pwd);
1768                                 else
1769                                         new = fetchParseURL(p);
1770                                 if (new == NULL) {
1771                                         /* XXX should set an error code */
1772                                         DEBUG(fprintf(stderr, "failed to parse new URL\n"));
1773                                         goto ouch;
1774                                 }
1775                                 if (!*new->user && !*new->pwd) {
1776                                         strcpy(new->user, url->user);
1777                                         strcpy(new->pwd, url->pwd);
1778                                 }
1779                                 new->offset = url->offset;
1780                                 new->length = url->length;
1781                                 break;
1782                         case hdr_transfer_encoding:
1783                                 /* XXX weak test*/
1784                                 chunked = (strcasecmp(p, "chunked") == 0);
1785                                 break;
1786                         case hdr_www_authenticate:
1787                                 if (conn->err != HTTP_NEED_AUTH)
1788                                         break;
1789                                 http_parse_authenticate(p, &server_challenges);
1790                                 break;
1791                         case hdr_proxy_authenticate:
1792                                 if (conn->err != HTTP_NEED_PROXY_AUTH)
1793                                         break;
1794                                 http_parse_authenticate(p, &proxy_challenges);
1795                                 break;
1796                         case hdr_end:
1797                                 /* fall through */
1798                         case hdr_unknown:
1799                                 /* ignore */
1800                                 break;
1801                         }
1802                 } while (h > hdr_end);
1803
1804                 /* we need to provide authentication */
1805                 if (conn->err == HTTP_NEED_AUTH || 
1806                     conn->err == HTTP_NEED_PROXY_AUTH) {
1807                         e = conn->err;
1808                         if ((conn->err == HTTP_NEED_AUTH && 
1809                              !server_challenges.valid) || 
1810                             (conn->err == HTTP_NEED_PROXY_AUTH && 
1811                              !proxy_challenges.valid)) {
1812                                 /* 401/7 but no www/proxy-authenticate ?? */
1813                                 DEBUG(fprintf(stderr, "401/7 and no auth header\n"));
1814                                 goto ouch;
1815                         }
1816                         fetch_close(conn);
1817                         conn = NULL;
1818                         continue;
1819                 }
1820
1821                 /* requested range not satisfiable */
1822                 if (conn->err == HTTP_BAD_RANGE) {
1823                         if (url->offset == size && url->length == 0) {
1824                                 /* asked for 0 bytes; fake it */
1825                                 offset = url->offset;
1826                                 clength = -1;
1827                                 conn->err = HTTP_OK;
1828                                 break;
1829                         } else {
1830                                 http_seterr(conn->err);
1831                                 goto ouch;
1832                         }
1833                 }
1834
1835                 /* we have a hit or an error */
1836                 if (conn->err == HTTP_OK
1837                     || conn->err == HTTP_NOT_MODIFIED
1838                     || conn->err == HTTP_PARTIAL
1839                     || HTTP_ERROR(conn->err))
1840                         break;
1841
1842                 /* all other cases: we got a redirect */
1843                 e = conn->err;
1844                 clean_http_auth_challenges(&server_challenges);
1845                 fetch_close(conn);
1846                 conn = NULL;
1847                 if (!new) {
1848                         DEBUG(fprintf(stderr, "redirect with no new location\n"));
1849                         break;
1850                 }
1851                 if (url != URL)
1852                         fetchFreeURL(url);
1853                 url = new;
1854         } while (++i < n);
1855
1856         /* we failed, or ran out of retries */
1857         if (conn == NULL) {
1858                 http_seterr(e);
1859                 goto ouch;
1860         }
1861
1862         DEBUG(fprintf(stderr, "offset %lld, length %lld,"
1863                   " size %lld, clength %lld\n",
1864                   (long long)offset, (long long)length,
1865                   (long long)size, (long long)clength));
1866
1867         if (conn->err == HTTP_NOT_MODIFIED) {
1868                 http_seterr(HTTP_NOT_MODIFIED);
1869                 return (NULL);
1870         }
1871
1872         /* check for inconsistencies */
1873         if (clength != -1 && length != -1 && clength != length) {
1874                 http_seterr(HTTP_PROTOCOL_ERROR);
1875                 goto ouch;
1876         }
1877         if (clength == -1)
1878                 clength = length;
1879         if (clength != -1)
1880                 length = offset + clength;
1881         if (length != -1 && size != -1 && length != size) {
1882                 http_seterr(HTTP_PROTOCOL_ERROR);
1883                 goto ouch;
1884         }
1885         if (size == -1)
1886                 size = length;
1887
1888         /* fill in stats */
1889         if (us) {
1890                 us->size = size;
1891                 us->atime = us->mtime = mtime;
1892         }
1893
1894         /* too far? */
1895         if (URL->offset > 0 && offset > URL->offset) {
1896                 http_seterr(HTTP_PROTOCOL_ERROR);
1897                 goto ouch;
1898         }
1899
1900         /* report back real offset and size */
1901         URL->offset = offset;
1902         URL->length = clength;
1903
1904         /* wrap it up in a FILE */
1905         if ((f = http_funopen(conn, chunked)) == NULL) {
1906                 fetch_syserr();
1907                 goto ouch;
1908         }
1909
1910         if (url != URL)
1911                 fetchFreeURL(url);
1912         if (purl)
1913                 fetchFreeURL(purl);
1914
1915         if (HTTP_ERROR(conn->err)) {
1916                 http_print_html(stderr, f);
1917                 fclose(f);
1918                 f = NULL;
1919         }
1920         clean_http_headerbuf(&headerbuf);
1921         clean_http_auth_challenges(&server_challenges);
1922         clean_http_auth_challenges(&proxy_challenges);
1923         return (f);
1924
1925 ouch:
1926         if (url != URL)
1927                 fetchFreeURL(url);
1928         if (purl)
1929                 fetchFreeURL(purl);
1930         if (conn != NULL)
1931                 fetch_close(conn);
1932         clean_http_headerbuf(&headerbuf);
1933         clean_http_auth_challenges(&server_challenges);
1934         clean_http_auth_challenges(&proxy_challenges);
1935         return (NULL);
1936 }
1937
1938
1939 /*****************************************************************************
1940  * Entry points
1941  */
1942
1943 /*
1944  * Retrieve and stat a file by HTTP
1945  */
1946 FILE *
1947 fetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
1948 {
1949         return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
1950 }
1951
1952 /*
1953  * Retrieve a file by HTTP
1954  */
1955 FILE *
1956 fetchGetHTTP(struct url *URL, const char *flags)
1957 {
1958         return (fetchXGetHTTP(URL, NULL, flags));
1959 }
1960
1961 /*
1962  * Store a file by HTTP
1963  */
1964 FILE *
1965 fetchPutHTTP(struct url *URL __unused, const char *flags __unused)
1966 {
1967         warnx("fetchPutHTTP(): not implemented");
1968         return (NULL);
1969 }
1970
1971 /*
1972  * Get an HTTP document's metadata
1973  */
1974 int
1975 fetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
1976 {
1977         FILE *f;
1978
1979         f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1980         if (f == NULL)
1981                 return (-1);
1982         fclose(f);
1983         return (0);
1984 }
1985
1986 /*
1987  * List a directory
1988  */
1989 struct url_ent *
1990 fetchListHTTP(struct url *url __unused, const char *flags __unused)
1991 {
1992         warnx("fetchListHTTP(): not implemented");
1993         return (NULL);
1994 }