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