]> sjero.net Git - wget/blobdiff - src/http.c
[svn] Fixes by Christian Biere:
[wget] / src / http.c
index f6498d38df041eb5671d5113e1ec7f5434084abf..b779e36ee6f71d82318c8e5ba7e9780bcbbbf5c5 100644 (file)
@@ -1,6 +1,5 @@
 /* HTTP support.
-   Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001, 2002
-   Free Software Foundation, Inc.
+   Copyright (C) 2003 Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
 
@@ -76,6 +75,9 @@ extern int errno;
 extern char *version_string;
 extern LARGE_INT total_downloaded_bytes;
 
+extern FILE *output_stream;
+extern int output_stream_regular;
+
 #ifndef MIN
 # define MIN(x, y) ((x) > (y) ? (y) : (x))
 #endif
@@ -86,13 +88,13 @@ struct cookie_jar *wget_cookie_jar;
 
 #define TEXTHTML_S "text/html"
 #define TEXTXHTML_S "application/xhtml+xml"
-#define HTTP_ACCEPT "*/*"
 
 /* Some status code validation macros: */
 #define H_20X(x)        (((x) >= 200) && ((x) < 300))
 #define H_PARTIAL(x)    ((x) == HTTP_STATUS_PARTIAL_CONTENTS)
-#define H_REDIRECTED(x) ((x) == HTTP_STATUS_MOVED_PERMANENTLY  \
-                         || (x) == HTTP_STATUS_MOVED_TEMPORARILY \
+#define H_REDIRECTED(x) ((x) == HTTP_STATUS_MOVED_PERMANENTLY          \
+                         || (x) == HTTP_STATUS_MOVED_TEMPORARILY       \
+                        || (x) == HTTP_STATUS_SEE_OTHER                \
                         || (x) == HTTP_STATUS_TEMPORARY_REDIRECT)
 
 /* HTTP/1.0 status codes from RFC1945, provided for reference.  */
@@ -107,21 +109,298 @@ struct cookie_jar *wget_cookie_jar;
 #define HTTP_STATUS_MULTIPLE_CHOICES   300
 #define HTTP_STATUS_MOVED_PERMANENTLY  301
 #define HTTP_STATUS_MOVED_TEMPORARILY  302
+#define HTTP_STATUS_SEE_OTHER           303 /* from HTTP/1.1 */
 #define HTTP_STATUS_NOT_MODIFIED       304
-#define HTTP_STATUS_TEMPORARY_REDIRECT  307
+#define HTTP_STATUS_TEMPORARY_REDIRECT  307 /* from HTTP/1.1 */
 
 /* Client error 4xx.  */
 #define HTTP_STATUS_BAD_REQUEST                400
 #define HTTP_STATUS_UNAUTHORIZED       401
 #define HTTP_STATUS_FORBIDDEN          403
 #define HTTP_STATUS_NOT_FOUND          404
+#define HTTP_STATUS_RANGE_NOT_SATISFIABLE 416
 
 /* Server errors 5xx.  */
 #define HTTP_STATUS_INTERNAL           500
 #define HTTP_STATUS_NOT_IMPLEMENTED    501
 #define HTTP_STATUS_BAD_GATEWAY                502
 #define HTTP_STATUS_UNAVAILABLE                503
+\f
+enum rp {
+  rel_none, rel_name, rel_value, rel_both
+};
+
+struct request {
+  const char *method;
+  char *arg;
+
+  struct request_header {
+    char *name, *value;
+    enum rp release_policy;
+  } *headers;
+  int hcount, hcapacity;
+};
+
+/* Create a new, empty request.  At least request_set_method must be
+   called before the request can be used.  */
+
+static struct request *
+request_new ()
+{
+  struct request *req = xnew0 (struct request);
+  req->hcapacity = 8;
+  req->headers = xnew_array (struct request_header, req->hcapacity);
+  return req;
+}
+
+/* Set the request's method and its arguments.  METH should be a
+   literal string (or it should outlive the request) because it will
+   not be freed.  ARG will be freed by request_free.  */
+
+static void
+request_set_method (struct request *req, const char *meth, char *arg)
+{
+  req->method = meth;
+  req->arg = arg;
+}
+
+/* Return the method string passed with the last call to
+   request_set_method.  */
+
+static const char *
+request_method (const struct request *req)
+{
+  return req->method;
+}
+
+/* Free one header according to the release policy specified with
+   request_set_header.  */
+
+static void
+release_header (struct request_header *hdr)
+{
+  switch (hdr->release_policy)
+    {
+    case rel_none:
+      break;
+    case rel_name:
+      xfree (hdr->name);
+      break;
+    case rel_value:
+      xfree (hdr->value);
+      break;
+    case rel_both:
+      xfree (hdr->name);
+      xfree (hdr->value);
+      break;
+    }
+}
+
+/* Set the request named NAME to VALUE.  Specifically, this means that
+   a "NAME: VALUE\r\n" header line will be used in the request.  If a
+   header with the same name previously existed in the request, its
+   value will be replaced by this one.
+
+   RELEASE_POLICY determines whether NAME and VALUE should be released
+   (freed) with request_free.  Allowed values are:
+
+    - rel_none     - don't free NAME or VALUE
+    - rel_name     - free NAME when done
+    - rel_value    - free VALUE when done
+    - rel_both     - free both NAME and VALUE when done
+
+   Setting release policy is useful when arguments come from different
+   sources.  For example:
+
+     // Don't free literal strings!
+     request_set_header (req, "Pragma", "no-cache", rel_none);
+
+     // Don't free a global variable, we'll need it later.
+     request_set_header (req, "Referer", opt.referer, rel_none);
+
+     // Value freshly allocated, free it when done.
+     request_set_header (req, "Range", aprintf ("bytes=%ld-", hs->restval),
+                        rel_value);
+   */
+
+static void
+request_set_header (struct request *req, char *name, char *value,
+                   enum rp release_policy)
+{
+  struct request_header *hdr;
+  int i;
+  if (!value)
+    return;
+  for (i = 0; i < req->hcount; i++)
+    {
+      hdr = &req->headers[i];
+      if (0 == strcasecmp (name, hdr->name))
+       {
+         /* Replace existing header. */
+         release_header (hdr);
+         hdr->name = name;
+         hdr->value = value;
+         hdr->release_policy = release_policy;
+         return;
+       }
+    }
+
+  /* Install new header. */
+
+  if (req->hcount >= req->hcount)
+    {
+      req->hcapacity <<= 1;
+      req->headers = xrealloc (req->headers,
+                              req->hcapacity * sizeof (struct request_header));
+    }
+  hdr = &req->headers[req->hcount++];
+  hdr->name = name;
+  hdr->value = value;
+  hdr->release_policy = release_policy;
+}
+
+/* Like request_set_header, but sets the whole header line, as
+   provided by the user using the `--header' option.  For example,
+   request_set_user_header (req, "Foo: bar") works just like
+   request_set_header (req, "Foo", "bar").  */
+
+static void
+request_set_user_header (struct request *req, const char *header)
+{
+  char *name;
+  const char *p = strchr (header, ':');
+  if (!p)
+    return;
+  BOUNDED_TO_ALLOCA (header, p, name);
+  ++p;
+  while (ISSPACE (*p))
+    ++p;
+  request_set_header (req, xstrdup (name), (char *) p, rel_name);
+}
+
+#define APPEND(p, str) do {                    \
+  int A_len = strlen (str);                    \
+  memcpy (p, str, A_len);                      \
+  p += A_len;                                  \
+} while (0)
+
+/* Construct the request and write it to FD using fd_write.  */
+
+static int
+request_send (const struct request *req, int fd)
+{
+  char *request_string, *p;
+  int i, size, write_error;
+
+  /* Count the request size. */
+  size = 0;
+
+  /* METHOD " " ARG " " "HTTP/1.0" "\r\n" */
+  size += strlen (req->method) + 1 + strlen (req->arg) + 1 + 8 + 2;
+
+  for (i = 0; i < req->hcount; i++)
+    {
+      struct request_header *hdr = &req->headers[i];
+      /* NAME ": " VALUE "\r\n" */
+      size += strlen (hdr->name) + 2 + strlen (hdr->value) + 2;
+    }
+
+  /* "\r\n\0" */
+  size += 3;
+
+  p = request_string = alloca_array (char, size);
+
+  /* Generate the request. */
+
+  APPEND (p, req->method); *p++ = ' ';
+  APPEND (p, req->arg);    *p++ = ' ';
+  memcpy (p, "HTTP/1.0\r\n", 10); p += 10;
+
+  for (i = 0; i < req->hcount; i++)
+    {
+      struct request_header *hdr = &req->headers[i];
+      APPEND (p, hdr->name);
+      *p++ = ':', *p++ = ' ';
+      APPEND (p, hdr->value);
+      *p++ = '\r', *p++ = '\n';
+    }
+
+  *p++ = '\r', *p++ = '\n', *p++ = '\0';
+  assert (p - request_string == size);
+
+#undef APPEND
+
+  DEBUGP (("\n---request begin---\n%s---request end---\n", request_string));
+
+  /* Send the request to the server. */
+
+  write_error = fd_write (fd, request_string, size - 1, -1);
+  if (write_error < 0)
+    logprintf (LOG_VERBOSE, _("Failed writing HTTP request: %s.\n"),
+              strerror (errno));
+  return write_error;
+}
+
+/* Release the resources used by REQ. */
+
+static void
+request_free (struct request *req)
+{
+  int i;
+  xfree_null (req->arg);
+  for (i = 0; i < req->hcount; i++)
+    release_header (&req->headers[i]);
+  xfree_null (req->headers);
+  xfree (req);
+}
+
+/* Send the contents of FILE_NAME to SOCK/SSL.  Make sure that exactly
+   PROMISED_SIZE bytes are sent over the wire -- if the file is
+   longer, read only that much; if the file is shorter, report an error.  */
+
+static int
+post_file (int sock, const char *file_name, long promised_size)
+{
+  static char chunk[8192];
+  long written = 0;
+  int write_error;
+  FILE *fp;
+
+  DEBUGP (("[writing POST file %s ... ", file_name));
+
+  fp = fopen (file_name, "rb");
+  if (!fp)
+    return -1;
+  while (!feof (fp) && written < promised_size)
+    {
+      int towrite;
+      int length = fread (chunk, 1, sizeof (chunk), fp);
+      if (length == 0)
+       break;
+      towrite = MIN (promised_size - written, length);
+      write_error = fd_write (sock, chunk, towrite, -1);
+      if (write_error < 0)
+       {
+         fclose (fp);
+         return -1;
+       }
+      written += towrite;
+    }
+  fclose (fp);
 
+  /* If we've written less than was promised, report a (probably
+     nonsensical) error rather than break the promise.  */
+  if (written < promised_size)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  assert (written == promised_size);
+  DEBUGP (("done]\n"));
+  return 0;
+}
+\f
 static const char *
 head_terminator (const char *hunk, int oldlen, int peeklen)
 {
@@ -171,14 +450,31 @@ struct response {
   const char *data;
 
   /* The array of pointers that indicate where each header starts.
-     For example, given three headers "foo", "bar", and "baz":
-       foo: value\r\nbar: value\r\nbaz: value\r\n\r\n
-       0             1             2             3
-     I.e. headers[0] points to the beginning of foo, headers[1] points
-     to the end of foo and the beginning of bar, etc.  */
+     For example, given this HTTP response:
+
+       HTTP/1.0 200 Ok
+       Description: some
+        text
+       Etag: x
+
+     The headers are located like this:
+
+     "HTTP/1.0 200 Ok\r\nDescription: some\r\n text\r\nEtag: x\r\n\r\n"
+     ^                   ^                             ^          ^
+     headers[0]          headers[1]                    headers[2] headers[3]
+
+     I.e. headers[0] points to the beginning of the request,
+     headers[1] points to the end of the first header and the
+     beginning of the second one, etc.  */
+
   const char **headers;
 };
 
+/* Create a new response object from the text of the HTTP response,
+   available in HEAD.  That text is automatically split into
+   constituent header lines for fast retrieval using
+   response_header_*.  */
+
 static struct response *
 response_new (const char *head)
 {
@@ -227,6 +523,13 @@ response_new (const char *head)
   return resp;
 }
 
+/* Locate the header named NAME in the request data.  If found, set
+   *BEGPTR to its starting, and *ENDPTR to its ending position, and
+   return 1.  Otherwise return 0.
+
+   This function is used as a building block for response_header_copy
+   and response_header_strdup.  */
+
 static int
 response_header_bounds (const struct response *resp, const char *name,
                        const char **begptr, const char **endptr)
@@ -261,6 +564,14 @@ response_header_bounds (const struct response *resp, const char *name,
   return 0;
 }
 
+/* Copy the response header named NAME to buffer BUF, no longer than
+   BUFSIZE (BUFSIZE includes the terminating 0).  If the header
+   exists, 1 is returned, otherwise 0.  If there should be no limit on
+   the size of the header, use response_header_strdup instead.
+
+   If BUFSIZE is 0, no data is copied, but the boolean indication of
+   whether the header is present is still returned.  */
+
 static int
 response_header_copy (const struct response *resp, const char *name,
                      char *buf, int bufsize)
@@ -270,13 +581,16 @@ response_header_copy (const struct response *resp, const char *name,
     return 0;
   if (bufsize)
     {
-      int len = MIN (e - b, bufsize);
-      strncpy (buf, b, len);
+      int len = MIN (e - b, bufsize - 1);
+      memcpy (buf, b, len);
       buf[len] = '\0';
     }
   return 1;
 }
 
+/* Return the value of header named NAME in RESP, allocated with
+   malloc.  If such a header does not exist in RESP, return NULL.  */
+
 static char *
 response_header_strdup (const struct response *resp, const char *name)
 {
@@ -302,9 +616,9 @@ response_status (const struct response *resp, char **message)
 
   if (!resp->headers)
     {
-      /* For a HTTP/0.9 response, always assume 200 response. */
+      /* For a HTTP/0.9 response, assume status 200. */
       if (message)
-       *message = xstrdup ("OK");
+       *message = xstrdup (_("No headers, assuming HTTP/0.9"));
       return 200;
     }
 
@@ -319,8 +633,8 @@ response_status (const struct response *resp, char **message)
     return -1;
   p += 4;
 
-  /* "/x.x" (optional because some Gnutella servers have been reported
-     as not sending the "/x.x" part.  */
+  /* Match the HTTP version.  This is optional because Gnutella
+     servers have been reported to not specify HTTP version.  */
   if (p < end && *p == '/')
     {
       ++p;
@@ -352,6 +666,8 @@ response_status (const struct response *resp, char **message)
   return status;
 }
 
+/* Release the resources used by RESP.  */
+
 static void
 response_free (struct response *resp)
 {
@@ -359,8 +675,10 @@ response_free (struct response *resp)
   xfree (resp);
 }
 
+/* Print [b, e) to the log, omitting the trailing CRLF.  */
+
 static void
-print_server_response_1 (const char *b, const char *e)
+print_server_response_1 (const char *prefix, const char *b, const char *e)
 {
   char *ln;
   if (b < e && e[-1] == '\n')
@@ -368,17 +686,20 @@ print_server_response_1 (const char *b, const char *e)
   if (b < e && e[-1] == '\r')
     --e;
   BOUNDED_TO_ALLOCA (b, e, ln);
-  logprintf (LOG_VERBOSE, "  %s\n", ln);
+  logprintf (LOG_VERBOSE, "%s%s\n", prefix, ln);
 }
 
+/* Print the server response, line by line, omitting the trailing CR
+   characters, prefixed with PREFIX.  */
+
 static void
-print_server_response (const struct response *resp)
+print_server_response (const struct response *resp, const char *prefix)
 {
   int i;
   if (!resp->headers)
     return;
   for (i = 0; resp->headers[i + 1]; i++)
-    print_server_response_1 (resp->headers[i], resp->headers[i + 1]);
+    print_server_response_1 (prefix, resp->headers[i], resp->headers[i + 1]);
 }
 
 /* Parse the `Content-Range' header and extract the information it
@@ -423,52 +744,32 @@ parse_content_range (const char *hdr, long *first_byte_ptr,
   *entity_length_ptr = num;
   return 1;
 }
-\f
-/* Send the contents of FILE_NAME to SOCK/SSL.  Make sure that exactly
-   PROMISED_SIZE bytes are sent over the wire -- if the file is
-   longer, read only that much; if the file is shorter, report an error.  */
-
-static int
-post_file (int sock, const char *file_name, long promised_size)
-{
-  static char chunk[8192];
-  long written = 0;
-  int write_error;
-  FILE *fp;
 
-  DEBUGP (("[writing POST file %s ... ", file_name));
+/* Read the body of the request, but don't store it anywhere and don't
+   display a progress gauge.  This is useful for reading the error
+   responses whose bodies don't need to be displayed or logged, but
+   which need to be read anyway.  */
 
-  fp = fopen (file_name, "rb");
-  if (!fp)
-    return -1;
-  while (!feof (fp) && written < promised_size)
-    {
-      int towrite;
-      int length = fread (chunk, 1, sizeof (chunk), fp);
-      if (length == 0)
-       break;
-      towrite = MIN (promised_size - written, length);
-      write_error = fd_write (sock, chunk, towrite, -1);
-      if (write_error < 0)
-       {
-         fclose (fp);
-         return -1;
-       }
-      written += towrite;
-    }
-  fclose (fp);
+static void
+skip_short_body (int fd, long contlen)
+{
+  /* Skipping the body doesn't make sense if the content length is
+     unknown because, in that case, persistent connections cannot be
+     used.  (#### This is not the case with HTTP/1.1 where they can
+     still be used with the magic of the "chunked" transfer!)  */
+  if (contlen == -1)
+    return;
+  DEBUGP (("Skipping %ld bytes of body data... ", contlen));
 
-  /* If we've written less than was promised, report a (probably
-     nonsensical) error rather than break the promise.  */
-  if (written < promised_size)
+  while (contlen > 0)
     {
-      errno = EINVAL;
-      return -1;
+      char dlbuf[512];
+      int ret = fd_read (fd, dlbuf, MIN (contlen, sizeof (dlbuf)), -1);
+      if (ret <= 0)
+       return;
+      contlen -= ret;
     }
-
-  assert (written == promised_size);
-  DEBUGP (("done]\n"));
-  return 0;
+  DEBUGP (("done.\n"));
 }
 \f
 /* Persistent connections.  Currently, we cache the most recently used
@@ -656,7 +957,10 @@ persistent_available_p (const char *host, int port, int ssl,
       if (pconn_active && (fd) == pconn.socket)        \
        invalidate_persistent ();               \
       else                                     \
-       fd_close (fd);                          \
+       {                                       \
+         fd_close (fd);                        \
+         fd = -1;                              \
+       }                                       \
     }                                          \
 } while (0)
 
@@ -665,6 +969,7 @@ persistent_available_p (const char *host, int port, int ssl,
     invalidate_persistent ();                  \
   else                                         \
     fd_close (fd);                             \
+  fd = -1;                                     \
 } while (0)
 \f
 struct http_stat
@@ -677,9 +982,8 @@ struct http_stat
   char *remote_time;           /* remote time-stamp string */
   char *error;                 /* textual HTTP error */
   int statcode;                        /* status code */
-  double dltime;               /* time of the download in msecs */
-  int no_truncate;             /* whether truncating the file is
-                                  forbidden. */
+  long rd_size;                        /* amount of data read from socket */
+  double dltime;               /* time it took to download the data */
   const char *referer;         /* value of the referer header. */
   char **local_file;           /* local file. */
 };
@@ -700,8 +1004,7 @@ free_hstat (struct http_stat *hs)
 static char *create_authorization_line PARAMS ((const char *, const char *,
                                                const char *, const char *,
                                                const char *));
-static char *basic_authentication_encode PARAMS ((const char *, const char *,
-                                                 const char *));
+static char *basic_authentication_encode PARAMS ((const char *, const char *));
 static int known_authentication_scheme_p PARAMS ((const char *));
 
 time_t http_atotm PARAMS ((const char *));
@@ -724,75 +1027,72 @@ time_t http_atotm PARAMS ((const char *));
 static uerr_t
 gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 {
-  char *request, *type, *command, *full_path;
+  struct request *req;
+
+  char *type;
   char *user, *passwd;
-  char *pragma_h, *referer, *useragent, *range, *wwwauth;
-  char *authenticate_h;
   char *proxyauth;
-  char *port_maybe;
-  char *request_keep_alive;
-  int sock, statcode;
+  int statcode;
   int write_error;
   long contlen, contrange;
   struct url *conn;
   FILE *fp;
-  int auth_tried_already;
+
+  int sock = -1;
+  int flags;
+
+  /* Whether authorization has been already tried. */
+  int auth_tried_already = 0;
+
+  /* Whether our connection to the remote host is through SSL.  */
   int using_ssl = 0;
-  char *cookies = NULL;
 
   char *head;
   struct response *resp;
   char hdrval[256];
   char *message;
-  char *set_cookie;
 
   /* Whether this connection will be kept alive after the HTTP request
      is done. */
   int keep_alive;
 
-  /* Flag that detects having received a keep-alive response.  */
-  int keep_alive_confirmed;
-
   /* Whether keep-alive should be inhibited. */
-  int inhibit_keep_alive;
-
-  /* Whether we need to print the host header with braces around host,
-     e.g. "Host: [3ffe:8100:200:2::2]:1234" instead of the usual
-     "Host: symbolic-name:1234". */
-  int squares_around_host = 0;
+  int inhibit_keep_alive = !opt.http_keep_alive;
 
   /* Headers sent when using POST. */
-  char *post_content_type, *post_content_length;
   long post_data_size = 0;
 
-  int host_lookup_failed;
+  int host_lookup_failed = 0;
 
 #ifdef HAVE_SSL
-  /* Initialize the SSL context.  After the first run, this is a
-     no-op.  */
-  switch (ssl_init ())
+  if (u->scheme == SCHEME_HTTPS)
     {
-    case SSLERRCTXCREATE:
-      /* this is fatal */
-      logprintf (LOG_NOTQUIET, _("Failed to set up an SSL context\n"));
-      return SSLERRCTXCREATE;
-    case SSLERRCERTFILE:
-      /* try without certfile */
-      logprintf (LOG_NOTQUIET,
-                _("Failed to load certificates from %s\n"),
-                opt.sslcertfile);
-      logprintf (LOG_NOTQUIET,
-                _("Trying without the specified certificate\n"));
-      break;
-    case SSLERRCERTKEY:
-      logprintf (LOG_NOTQUIET,
-                _("Failed to get certificate key from %s\n"),
-                opt.sslcertkey);
-      logprintf (LOG_NOTQUIET,
-                _("Trying without the specified certificate\n"));
-      break;
-    default:
-      break;
+      /* Initialize the SSL context.  After this has once been done,
+        it becomes a no-op.  */
+      switch (ssl_init ())
+       {
+       case SSLERRCTXCREATE:
+         /* this is fatal */
+         logprintf (LOG_NOTQUIET, _("Failed to set up an SSL context\n"));
+         return SSLERRCTXCREATE;
+       case SSLERRCERTFILE:
+         /* try without certfile */
+         logprintf (LOG_NOTQUIET,
+                    _("Failed to load certificates from %s\n"),
+                    opt.sslcertfile);
+         logprintf (LOG_NOTQUIET,
+                    _("Trying without the specified certificate\n"));
+         break;
+       case SSLERRCERTKEY:
+         logprintf (LOG_NOTQUIET,
+                    _("Failed to get certificate key from %s\n"),
+                    opt.sslcertkey);
+         logprintf (LOG_NOTQUIET,
+                    _("Trying without the specified certificate\n"));
+         break;
+       default:
+         break;
+       }
     }
 #endif /* HAVE_SSL */
 
@@ -801,22 +1101,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        know the local filename so we can save to it. */
     assert (*hs->local_file != NULL);
 
-  authenticate_h = NULL;
   auth_tried_already = 0;
 
-  inhibit_keep_alive = !opt.http_keep_alive;
-
- again:
-  /* We need to come back here when the initial attempt to retrieve
-     without authorization header fails.  (Expected to happen at least
-     for the Digest authorization scheme.)  */
-
-  keep_alive = 0;
-  keep_alive_confirmed = 0;
-
-  post_content_type = NULL;
-  post_content_length = NULL;
-
   /* Initialize certain elements of struct http_stat.  */
   hs->len = 0L;
   hs->contlen = -1;
@@ -849,18 +1135,143 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       /* #### This does not appear right.  Can't the proxy request,
         say, `Digest' authentication?  */
       if (proxy_user && proxy_passwd)
-       proxyauth = basic_authentication_encode (proxy_user, proxy_passwd,
-                                                "Proxy-Authorization");
+       proxyauth = basic_authentication_encode (proxy_user, proxy_passwd);
 
       /* If we're using a proxy, we will be connecting to the proxy
         server.  */
       conn = proxy;
     }
 
-  host_lookup_failed = 0;
-  sock = -1;
+  /* Prepare the request to send. */
+
+  req = request_new ();
+  {
+    const char *meth = "GET";
+    if (*dt & HEAD_ONLY)
+      meth = "HEAD";
+    else if (opt.post_file_name || opt.post_data)
+      meth = "POST";
+    /* Use the full path, i.e. one that includes the leading slash and
+       the query string.  E.g. if u->path is "foo/bar" and u->query is
+       "param=value", full_path will be "/foo/bar?param=value".  */
+    request_set_method (req, meth,
+                       proxy ? xstrdup (u->url) : url_full_path (u));
+  }
+
+  request_set_header (req, "Referer", (char *) hs->referer, rel_none);
+  if (*dt & SEND_NOCACHE)
+    request_set_header (req, "Pragma", "no-cache", rel_none);
+  if (hs->restval)
+    request_set_header (req, "Range",
+                       aprintf ("bytes=%ld-", hs->restval), rel_value);
+  if (opt.useragent)
+    request_set_header (req, "User-Agent", opt.useragent, rel_none);
+  else
+    request_set_header (req, "User-Agent",
+                       aprintf ("Wget/%s", version_string), rel_value);
+  request_set_header (req, "Accept", "*/*", rel_none);
+
+  /* Find the username and password for authentication. */
+  user = u->user;
+  passwd = u->passwd;
+  search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
+  user = user ? user : opt.http_user;
+  passwd = passwd ? passwd : opt.http_passwd;
+
+  if (user && passwd)
+    {
+      /* We have the username and the password, but haven't tried
+        any authorization yet.  Let's see if the "Basic" method
+        works.  If not, we'll come back here and construct a
+        proper authorization method with the right challenges.
+
+        If we didn't employ this kind of logic, every URL that
+        requires authorization would have to be processed twice,
+        which is very suboptimal and generates a bunch of false
+        "unauthorized" errors in the server log.
+
+        #### But this logic also has a serious problem when used
+        with stronger authentications: we *first* transmit the
+        username and the password in clear text, and *then* attempt a
+        stronger authentication scheme.  That cannot be right!  We
+        are only fortunate that almost everyone still uses the
+        `Basic' scheme anyway.
+
+        There should be an option to prevent this from happening, for
+        those who use strong authentication schemes and value their
+        passwords.  */
+      request_set_header (req, "Authorization",
+                         basic_authentication_encode (user, passwd),
+                         rel_value);
+    }
+
+  {
+    /* Whether we need to print the host header with braces around
+       host, e.g. "Host: [3ffe:8100:200:2::2]:1234" instead of the
+       usual "Host: symbolic-name:1234". */
+    int squares = strchr (u->host, ':') != NULL;
+    if (u->port == scheme_default_port (u->scheme))
+      request_set_header (req, "Host",
+                         aprintf (squares ? "[%s]" : "%s", u->host),
+                         rel_value);
+    else
+      request_set_header (req, "Host",
+                         aprintf (squares ? "[%s]:%d" : "%s:%d",
+                                  u->host, u->port),
+                         rel_value);
+  }
 
-  /* First: establish the connection.  */
+  if (!inhibit_keep_alive)
+    request_set_header (req, "Connection", "Keep-Alive", rel_none);
+
+  if (opt.cookies)
+    request_set_header (req, "Cookie",
+                       cookie_header (wget_cookie_jar,
+                                      u->host, u->port, u->path,
+#ifdef HAVE_SSL
+                                      u->scheme == SCHEME_HTTPS
+#else
+                                      0
+#endif
+                                      ),
+                       rel_value);
+
+  if (opt.post_data || opt.post_file_name)
+    {
+      request_set_header (req, "Content-Type",
+                         "application/x-www-form-urlencoded", rel_none);
+      if (opt.post_data)
+       post_data_size = strlen (opt.post_data);
+      else
+       {
+         post_data_size = file_size (opt.post_file_name);
+         if (post_data_size == -1)
+           {
+             logprintf (LOG_NOTQUIET, "POST data file missing: %s\n",
+                        opt.post_file_name);
+             post_data_size = 0;
+           }
+       }
+      request_set_header (req, "Content-Length",
+                         aprintf ("%ld", post_data_size), rel_value);
+    }
+
+  /* Add the user headers. */
+  if (opt.user_headers)
+    {
+      int i;
+      for (i = 0; opt.user_headers[i]; i++)
+       request_set_user_header (req, opt.user_headers[i]);
+    }
+
+ retry_with_auth:
+  /* We need to come back here when the initial attempt to retrieve
+     without authorization header fails.  (Expected to happen at least
+     for the Digest authorization scheme.)  */
+
+  keep_alive = 0;
+
+  /* Establish the connection.  */
 
   if (!inhibit_keep_alive)
     {
@@ -910,17 +1321,23 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        {
          /* When requesting SSL URLs through proxies, use the
             CONNECT method to request passthrough.  */
-         char *connect =
-           (char *) alloca (64
-                            + strlen (u->host)
-                            + (proxyauth ? strlen (proxyauth) : 0));
-         sprintf (connect, "CONNECT %s:%d HTTP/1.0\r\n%s\r\n",
-                  u->host, u->port, proxyauth ? proxyauth : "");
-         DEBUGP (("Writing to proxy: [%s]\n", connect));
-         write_error = fd_write (sock, connect, strlen (connect), -1);
+         struct request *connreq = request_new ();
+         request_set_method (connreq, "CONNECT",
+                             aprintf ("%s:%d", u->host, u->port));
+         if (proxyauth)
+           {
+             request_set_header (connreq, "Proxy-Authorization",
+                                 proxyauth, rel_value);
+             /* Now that PROXYAUTH is part of the CONNECT request,
+                zero it out so we don't send proxy authorization with
+                the regular request below.  */
+             proxyauth = NULL;
+           }
+
+         write_error = request_send (connreq, sock);
+         request_free (connreq);
          if (write_error < 0)
            {
-             xfree_null (proxyauth);
              logprintf (LOG_VERBOSE, _("Failed writing to proxy: %s.\n"),
                         strerror (errno));
              CLOSE_INVALIDATE (sock);
@@ -930,7 +1347,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          head = fd_read_http_head (sock);
          if (!head)
            {
-             xfree_null (proxyauth);
              logprintf (LOG_VERBOSE, _("Failed reading proxy response: %s\n"),
                         strerror (errno));
              CLOSE_INVALIDATE (sock);
@@ -950,7 +1366,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          if (statcode != 200)
            {
            failed_tunnel:
-             xfree_null (proxyauth);
              logprintf (LOG_NOTQUIET, _("Proxy tunneling failed: %s"),
                         message ? message : "?");
              xfree_null (message);
@@ -976,198 +1391,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 #endif /* HAVE_SSL */
     }
 
-  if (*dt & HEAD_ONLY)
-    command = "HEAD";
-  else if (opt.post_file_name || opt.post_data)
-    command = "POST";
-  else
-    command = "GET";
-
-  referer = NULL;
-  if (hs->referer)
-    {
-      referer = (char *)alloca (9 + strlen (hs->referer) + 3);
-      sprintf (referer, "Referer: %s\r\n", hs->referer);
-    }
-
-  if (*dt & SEND_NOCACHE)
-    pragma_h = "Pragma: no-cache\r\n";
-  else
-    pragma_h = "";
-
-  if (hs->restval)
-    {
-      range = (char *)alloca (13 + numdigit (hs->restval) + 4);
-      /* Gag me!  Some servers (e.g. WebSitePro) have been known to
-         respond to the following `Range' format by generating a
-         multipart/x-byte-ranges MIME document!  This MIME type was
-         present in an old draft of the byteranges specification.
-         HTTP/1.1 specifies a multipart/byte-ranges MIME type, but
-         only if multiple non-overlapping ranges are requested --
-         which Wget never does.  */
-      sprintf (range, "Range: bytes=%ld-\r\n", hs->restval);
-    }
-  else
-    range = NULL;
-  if (opt.useragent)
-    STRDUP_ALLOCA (useragent, opt.useragent);
-  else
-    {
-      useragent = (char *)alloca (10 + strlen (version_string));
-      sprintf (useragent, "Wget/%s", version_string);
-    }
-  /* Construct the authentication, if userid is present.  */
-  user = u->user;
-  passwd = u->passwd;
-  search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
-  user = user ? user : opt.http_user;
-  passwd = passwd ? passwd : opt.http_passwd;
-
-  wwwauth = NULL;
-  if (user && passwd)
-    {
-      if (!authenticate_h)
-       {
-         /* We have the username and the password, but haven't tried
-            any authorization yet.  Let's see if the "Basic" method
-            works.  If not, we'll come back here and construct a
-            proper authorization method with the right challenges.
-
-            If we didn't employ this kind of logic, every URL that
-            requires authorization would have to be processed twice,
-            which is very suboptimal and generates a bunch of false
-            "unauthorized" errors in the server log.
-
-            #### But this logic also has a serious problem when used
-            with stronger authentications: we *first* transmit the
-            username and the password in clear text, and *then*
-            attempt a stronger authentication scheme.  That cannot be
-            right!  We are only fortunate that almost everyone still
-            uses the `Basic' scheme anyway.
-
-            There should be an option to prevent this from happening,
-            for those who use strong authentication schemes and value
-            their passwords.  */
-         wwwauth = basic_authentication_encode (user, passwd, "Authorization");
-       }
-      else
-       {
-         /* Use the full path, i.e. one that includes the leading
-            slash and the query string, but is independent of proxy
-            setting.  */
-         char *pth = url_full_path (u);
-         wwwauth = create_authorization_line (authenticate_h, user, passwd,
-                                              command, pth);
-         xfree (pth);
-       }
-    }
-
-  /* String of the form :PORT.  Used only for non-standard ports. */
-  port_maybe = NULL;
-  if (u->port != scheme_default_port (u->scheme))
-    {
-      port_maybe = (char *)alloca (numdigit (u->port) + 2);
-      sprintf (port_maybe, ":%d", u->port);
-    }
-
-  if (!inhibit_keep_alive)
-    request_keep_alive = "Connection: Keep-Alive\r\n";
-  else
-    request_keep_alive = NULL;
-
-  if (opt.cookies)
-    cookies = cookie_header (wget_cookie_jar, u->host, u->port, u->path,
-#ifdef HAVE_SSL
-                            u->scheme == SCHEME_HTTPS
-#else
-                            0
-#endif
-                            );
-
-  if (opt.post_data || opt.post_file_name)
-    {
-      post_content_type = "Content-Type: application/x-www-form-urlencoded\r\n";
-      if (opt.post_data)
-       post_data_size = strlen (opt.post_data);
-      else
-       {
-         post_data_size = file_size (opt.post_file_name);
-         if (post_data_size == -1)
-           {
-             logprintf (LOG_NOTQUIET, "POST data file missing: %s\n",
-                        opt.post_file_name);
-             post_data_size = 0;
-           }
-       }
-      post_content_length = xmalloc (16 + numdigit (post_data_size) + 2 + 1);
-      sprintf (post_content_length,
-              "Content-Length: %ld\r\n", post_data_size);
-    }
-
-  if (proxy)
-    full_path = xstrdup (u->url);
-  else
-    /* Use the full path, i.e. one that includes the leading slash and
-       the query string.  E.g. if u->path is "foo/bar" and u->query is
-       "param=value", full_path will be "/foo/bar?param=value".  */
-    full_path = url_full_path (u);
-
-  if (strchr (u->host, ':'))
-    squares_around_host = 1;
-
-  /* Allocate the memory for the request.  */
-  request = (char *)alloca (strlen (command)
-                           + strlen (full_path)
-                           + strlen (useragent)
-                           + strlen (u->host)
-                           + (port_maybe ? strlen (port_maybe) : 0)
-                           + strlen (HTTP_ACCEPT)
-                           + (request_keep_alive
-                              ? strlen (request_keep_alive) : 0)
-                           + (referer ? strlen (referer) : 0)
-                           + (cookies ? strlen (cookies) : 0)
-                           + (wwwauth ? strlen (wwwauth) : 0)
-                           + (proxyauth ? strlen (proxyauth) : 0)
-                           + (range ? strlen (range) : 0)
-                           + strlen (pragma_h)
-                           + (post_content_type
-                              ? strlen (post_content_type) : 0)
-                           + (post_content_length
-                              ? strlen (post_content_length) : 0)
-                           + (opt.user_header ? strlen (opt.user_header) : 0)
-                           + 64);
-  /* Construct the request.  */
-  sprintf (request, "\
-%s %s HTTP/1.0\r\n\
-User-Agent: %s\r\n\
-Host: %s%s%s%s\r\n\
-Accept: %s\r\n\
-%s%s%s%s%s%s%s%s%s%s\r\n",
-          command, full_path,
-          useragent,
-          squares_around_host ? "[" : "", u->host, squares_around_host ? "]" : "",
-          port_maybe ? port_maybe : "",
-          HTTP_ACCEPT,
-          request_keep_alive ? request_keep_alive : "",
-          referer ? referer : "",
-          cookies ? cookies : "", 
-          wwwauth ? wwwauth : "", 
-          proxyauth ? proxyauth : "", 
-          range ? range : "",
-          pragma_h,
-          post_content_type ? post_content_type : "",
-          post_content_length ? post_content_length : "",
-          opt.user_header ? opt.user_header : "");
-  DEBUGP (("\n---request begin---\n%s", request));
-
-  /* Free the temporary memory.  */
-  xfree_null (wwwauth);
-  xfree_null (proxyauth);
-  xfree_null (cookies);
-  xfree (full_path);
-
   /* Send the request to server.  */
-  write_error = fd_write (sock, request, strlen (request), -1);
+  write_error = request_send (req, sock);
 
   if (write_error >= 0)
     {
@@ -1179,18 +1404,19 @@ Accept: %s\r\n\
       else if (opt.post_file_name && post_data_size != 0)
        write_error = post_file (sock, opt.post_file_name, post_data_size);
     }
-  DEBUGP (("---request end---\n"));
 
   if (write_error < 0)
     {
       logprintf (LOG_VERBOSE, _("Failed writing HTTP request: %s.\n"),
                 strerror (errno));
       CLOSE_INVALIDATE (sock);
+      request_free (req);
       return WRITEFAILED;
     }
   logprintf (LOG_VERBOSE, _("%s request sent, awaiting response... "),
             proxy ? "Proxy" : "HTTP");
-  contlen = contrange = -1;
+  contlen = -1;
+  contrange = 0;
   type = NULL;
   statcode = -1;
   *dt &= ~RETROKF;
@@ -1198,11 +1424,11 @@ Accept: %s\r\n\
   head = fd_read_http_head (sock);
   if (!head)
     {
-      logputs (LOG_VERBOSE, "\n");
       if (errno == 0)
        {
          logputs (LOG_NOTQUIET, _("No data received.\n"));
          CLOSE_INVALIDATE (sock);
+         request_free (req);
          return HEOF;
        }
       else
@@ -1210,6 +1436,7 @@ Accept: %s\r\n\
          logprintf (LOG_NOTQUIET, _("Read error (%s) in headers.\n"),
                     strerror (errno));
          CLOSE_INVALIDATE (sock);
+         request_free (req);
          return HERR;
        }
     }
@@ -1225,49 +1452,11 @@ Accept: %s\r\n\
   else
     {
       logprintf (LOG_VERBOSE, "\n");
-      print_server_response (resp);
+      print_server_response (resp, "  ");
     }
 
-  hs->statcode = statcode;
-  if (statcode == -1)
-    hs->error = xstrdup (_("Malformed status line"));
-  else if (!*message)
-    hs->error = xstrdup (_("(no description)"));
-  else
-    hs->error = xstrdup (message);
-
   if (response_header_copy (resp, "Content-Length", hdrval, sizeof (hdrval)))
     contlen = strtol (hdrval, NULL, 10);
-  type = response_header_strdup (resp, "Content-Type");
-  if (type)
-    {
-      char *tmp = strchr (type, ';');
-      if (tmp)
-       {
-         while (tmp > type && ISSPACE (tmp[-1]))
-           --tmp;
-         *tmp = '\0';
-       }
-    }
-  hs->newloc = response_header_strdup (resp, "Location");
-  hs->remote_time = response_header_strdup (resp, "Last-Modified");
-  set_cookie = response_header_strdup (resp, "Set-Cookie");
-  if (set_cookie)
-    {
-      /* The jar should have been created by now. */
-      assert (wget_cookie_jar != NULL);
-      cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port, u->path,
-                               set_cookie);
-      xfree (set_cookie);
-    }
-  authenticate_h = response_header_strdup (resp, "WWW-Authenticate");
-  if (response_header_copy (resp, "Content-Range", hdrval, sizeof (hdrval)))
-    {
-      long first_byte_pos, last_byte_pos, entity_length;
-      if (parse_content_range (hdrval, &first_byte_pos, &last_byte_pos,
-                              &entity_length))
-       contrange = first_byte_pos;
-    }
 
   /* Check for keep-alive related responses. */
   if (!inhibit_keep_alive && contlen != -1)
@@ -1281,56 +1470,97 @@ Accept: %s\r\n\
            keep_alive = 1;
        }
     }
-  response_free (resp);
-
   if (keep_alive)
     /* The server has promised that it will not close the connection
        when we're done.  This means that we can register it.  */
     register_persistent (conn->host, conn->port, sock, using_ssl);
 
-  if ((statcode == HTTP_STATUS_UNAUTHORIZED)
-      && authenticate_h)
+  if (statcode == HTTP_STATUS_UNAUTHORIZED)
     {
       /* Authorization is required.  */
-      xfree_null (type);
-      type = NULL;
-      free_hstat (hs);
-      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                  might be more bytes in the body. */
-      if (auth_tried_already)
+      skip_short_body (sock, contlen);
+      CLOSE_FINISH (sock);
+      if (auth_tried_already || !(user && passwd))
        {
          /* If we have tried it already, then there is not point
             retrying it.  */
-       failed:
          logputs (LOG_NOTQUIET, _("Authorization failed.\n"));
-         xfree (authenticate_h);
-         return AUTHFAILED;
        }
-      else if (!known_authentication_scheme_p (authenticate_h))
-       {
-         xfree (authenticate_h);
-         logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
-         return AUTHFAILED;
-       }
-      else if (BEGINS_WITH (authenticate_h, "Basic"))
+      else
        {
-         /* The authentication scheme is basic, the one we try by
-             default, and it failed.  There's no sense in trying
-             again.  */
-         goto failed;
+         char *www_authenticate = response_header_strdup (resp,
+                                                          "WWW-Authenticate");
+         /* If the authentication scheme is unknown or if it's the
+            "Basic" authentication (which we try by default), there's
+            no sense in retrying.  */
+         if (!www_authenticate
+             || !known_authentication_scheme_p (www_authenticate)
+             || BEGINS_WITH (www_authenticate, "Basic"))
+           {
+             xfree_null (www_authenticate);
+             logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
+           }
+         else
+           {
+             char *pth;
+             auth_tried_already = 1;
+             pth = url_full_path (u);
+             request_set_header (req, "Authorization",
+                                 create_authorization_line (www_authenticate,
+                                                            user, passwd,
+                                                            request_method (req),
+                                                            pth),
+                                 rel_value);
+             xfree (pth);
+             xfree (www_authenticate);
+             goto retry_with_auth;
+           }
        }
-      else
+      request_free (req);
+      return AUTHFAILED;
+    }
+  request_free (req);
+
+  hs->statcode = statcode;
+  if (statcode == -1)
+    hs->error = xstrdup (_("Malformed status line"));
+  else if (!*message)
+    hs->error = xstrdup (_("(no description)"));
+  else
+    hs->error = xstrdup (message);
+
+  type = response_header_strdup (resp, "Content-Type");
+  if (type)
+    {
+      char *tmp = strchr (type, ';');
+      if (tmp)
        {
-         auth_tried_already = 1;
-         goto again;
+         while (tmp > type && ISSPACE (tmp[-1]))
+           --tmp;
+         *tmp = '\0';
        }
     }
-  /* We do not need this anymore.  */
-  if (authenticate_h)
+  hs->newloc = response_header_strdup (resp, "Location");
+  hs->remote_time = response_header_strdup (resp, "Last-Modified");
+  {
+    char *set_cookie = response_header_strdup (resp, "Set-Cookie");
+    if (set_cookie)
+      {
+       /* The jar should have been created by now. */
+       assert (wget_cookie_jar != NULL);
+       cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port, u->path,
+                                 set_cookie);
+       xfree (set_cookie);
+      }
+  }
+  if (response_header_copy (resp, "Content-Range", hdrval, sizeof (hdrval)))
     {
-      xfree (authenticate_h);
-      authenticate_h = NULL;
+      long first_byte_pos, last_byte_pos, entity_length;
+      if (parse_content_range (hdrval, &first_byte_pos, &last_byte_pos,
+                              &entity_length))
+       contrange = first_byte_pos;
     }
+  response_free (resp);
 
   /* 20x responses are counted among successful by default.  */
   if (H_20X (statcode))
@@ -1352,8 +1582,9 @@ Accept: %s\r\n\
                     _("Location: %s%s\n"),
                     hs->newloc ? hs->newloc : _("unspecified"),
                     hs->newloc ? _(" [following]") : "");
-         CLOSE_INVALIDATE (sock);      /* would be CLOSE_FINISH, but there
-                                          might be more bytes in the body. */
+         if (keep_alive)
+           skip_short_body (sock, contlen);
+         CLOSE_FINISH (sock);
          xfree_null (type);
          return NEWLOCATION;
        }
@@ -1390,87 +1621,37 @@ Accept: %s\r\n\
        }
     }
 
-  if (contrange == -1)
+  if (statcode == HTTP_STATUS_RANGE_NOT_SATISFIABLE)
     {
-      /* We did not get a content-range header.  This means that the
-        server did not honor our `Range' request.  Normally, this
-        means we should reset hs->restval and continue normally.  */
-
-      /* However, if `-c' is used, we need to be a bit more careful:
-
-         1. If `-c' is specified and the file already existed when
-         Wget was started, it would be a bad idea for us to start
-         downloading it from scratch, effectively truncating it.  I
-         believe this cannot happen unless `-c' was specified.
-
-        2. If `-c' is used on a file that is already fully
-        downloaded, we're requesting bytes after the end of file,
-        which can result in server not honoring `Range'.  If this is
-        the case, `Content-Length' will be equal to the length of the
-        file.  */
-      if (opt.always_rest)
-       {
-         /* Check for condition #2. */
-         if (hs->restval > 0               /* restart was requested. */
-             && contlen != -1              /* we got content-length. */
-             && hs->restval >= contlen     /* file fully downloaded
-                                              or has shrunk.  */
-             )
-           {
-             logputs (LOG_VERBOSE, _("\
+      /* If `-c' is in use and the file has been fully downloaded (or
+        the remote file has shrunk), Wget effectively requests bytes
+        after the end of file and the server response with 416.  */
+      logputs (LOG_VERBOSE, _("\
 \n    The file is already fully retrieved; nothing to do.\n\n"));
-             /* In case the caller inspects. */
-             hs->len = contlen;
-             hs->res = 0;
-             /* Mark as successfully retrieved. */
-             *dt |= RETROKF;
-             xfree_null (type);
-             CLOSE_INVALIDATE (sock);  /* would be CLOSE_FINISH, but there
-                                          might be more bytes in the body. */
-             return RETRUNNEEDED;
-           }
-
-         /* Check for condition #1. */
-         if (hs->no_truncate)
-           {
-             logprintf (LOG_NOTQUIET,
-                        _("\
-\n\
-Continued download failed on this file, which conflicts with `-c'.\n\
-Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
-             xfree_null (type);
-             CLOSE_INVALIDATE (sock);
-             return CONTNOTSUPPORTED;
-           }
-
-         /* Fallthrough */
-       }
-
-      hs->restval = 0;
+      /* In case the caller inspects. */
+      hs->len = contlen;
+      hs->res = 0;
+      /* Mark as successfully retrieved. */
+      *dt |= RETROKF;
+      xfree_null (type);
+      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
+                                  might be more bytes in the body. */
+      return RETRUNNEEDED;
     }
-  else if (contrange != hs->restval ||
-          (H_PARTIAL (statcode) && contrange == -1))
+  if ((contrange != 0 && contrange != hs->restval)
+      || (H_PARTIAL (statcode) && !contrange))
     {
-      /* This means the whole request was somehow misunderstood by the
-        server.  Bail out.  */
+      /* The Range request was somehow misunderstood by the server.
+        Bail out.  */
       xfree_null (type);
       CLOSE_INVALIDATE (sock);
       return RANGEERR;
     }
-
-  if (hs->restval)
-    {
-      if (contlen != -1)
-       contlen += contrange;
-      else
-       contrange = -1;        /* If conent-length was not sent,
-                                 content-range will be ignored.  */
-    }
-  hs->contlen = contlen;
+  hs->contlen = contlen + contrange;
 
   if (opt.verbose)
     {
-      if ((*dt & RETROKF) && !opt.server_response)
+      if (*dt & RETROKF)
        {
          /* No need to print this output if the body won't be
             downloaded at all, or if the original server response is
@@ -1478,10 +1659,9 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
          logputs (LOG_VERBOSE, _("Length: "));
          if (contlen != -1)
            {
-             logputs (LOG_VERBOSE, legible (contlen));
-             if (contrange != -1)
-               logprintf (LOG_VERBOSE, _(" (%s to go)"),
-                          legible (contlen - contrange));
+             logputs (LOG_VERBOSE, legible (contlen + contrange));
+             if (contrange)
+               logprintf (LOG_VERBOSE, _(" (%s to go)"), legible (contlen));
            }
          else
            logputs (LOG_VERBOSE,
@@ -1502,13 +1682,16 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
       hs->len = 0L;
       hs->res = 0;
       xfree_null (type);
-      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                  might be more bytes in the body. */
+      /* Pre-1.10 Wget used CLOSE_INVALIDATE here.  Now we trust the
+        servers not to send body in response to a HEAD request.  If
+        you encounter such a server (more likely a broken CGI), use
+        `--no-http-keep-alive'.  */
+      CLOSE_FINISH (sock);
       return RETRFINISHED;
     }
 
   /* Open the local file.  */
-  if (!opt.dfp)
+  if (!output_stream)
     {
       mkalldirs (*hs->local_file);
       if (opt.backups)
@@ -1517,53 +1700,31 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
       if (!fp)
        {
          logprintf (LOG_NOTQUIET, "%s: %s\n", *hs->local_file, strerror (errno));
-         CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                     might be more bytes in the body. */
+         CLOSE_INVALIDATE (sock);
          return FOPENERR;
        }
     }
-  else                         /* opt.dfp */
-    {
-      extern int global_download_count;
-      fp = opt.dfp;
-      /* To ensure that repeated "from scratch" downloads work for -O
-        files, we rewind the file pointer, unless restval is
-        non-zero.  (This works only when -O is used on regular files,
-        but it's still a valuable feature.)
-
-        However, this loses when more than one URL is specified on
-        the command line the second rewinds eradicates the contents
-        of the first download.  Thus we disable the above trick for
-        all the downloads except the very first one.
-
-         #### A possible solution to this would be to remember the
-        file position in the output document and to seek to that
-        position, instead of rewinding.
-
-         We don't truncate stdout, since that breaks
-        "wget -O - [...] >> foo".
-      */
-      if (!hs->restval && global_download_count == 0 && opt.dfp != stdout)
-       {
-         /* This will silently fail for streams that don't correspond
-            to regular files, but that's OK.  */
-         rewind (fp);
-         /* ftruncate is needed because opt.dfp is opened in append
-            mode if opt.always_rest is set.  */
-         ftruncate (fileno (fp), 0);
-         clearerr (fp);
-       }
-    }
+  else
+    fp = output_stream;
 
-  /* #### This confuses the code that checks for file size.  There
-     should be some overhead information.  */
+  /* #### This confuses the timestamping code that checks for file
+     size.  Maybe we should save some additional information?  */
   if (opt.save_headers)
     fwrite (head, 1, strlen (head), fp);
 
-  /* Get the contents of the document.  */
-  hs->res = fd_read_body (sock, fp, &hs->len, hs->restval,
-                         (contlen != -1 ? contlen : 0),
-                         keep_alive, &hs->dltime);
+  /* Download the request body.  */
+  flags = 0;
+  if (keep_alive)
+    flags |= rb_read_exactly;
+  if (hs->restval > 0 && contrange == 0)
+    /* If the server ignored our range request, instruct fd_read_body
+       to skip the first RESTVAL bytes of body.  */
+    flags |= rb_skip_startpos;
+  hs->len = hs->restval;
+  hs->rd_size = 0;
+  hs->res = fd_read_body (sock, fp, contlen != -1 ? contlen : 0,
+                         hs->restval, &hs->rd_size, &hs->len, &hs->dltime,
+                         flags);
 
   if (hs->res >= 0)
     CLOSE_FINISH (sock);
@@ -1575,7 +1736,7 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
        error here.  Checking the result of fwrite() is not enough --
        errors could go unnoticed!  */
     int flush_res;
-    if (!opt.dfp)
+    if (!output_stream)
       flush_res = fclose (fp);
     else
       flush_res = fflush (fp);
@@ -1628,6 +1789,8 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   if (strchr (u->url, '*'))
     logputs (LOG_VERBOSE, _("Warning: wildcards not supported in HTTP.\n"));
 
+  xzero (hstat);
+
   /* Determine the local filename.  */
   if (local_file && *local_file)
     hstat.local_file = local_file;
@@ -1728,7 +1891,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
     }
   /* Reset the counter.  */
   count = 0;
-  *dt = 0 | ACCEPTRANGES;
+  *dt = 0;
   /* THE loop */
   do
     {
@@ -1741,7 +1904,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
       if (opt.verbose)
        {
          char *hurl = url_string (u, 1);
-         char tmp[15];
+         char tmp[256];
          strcpy (tmp, "        ");
          if (count > 1)
            sprintf (tmp, _("(try:%2d)"), count);
@@ -1760,21 +1923,15 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
        *dt |= HEAD_ONLY;
       else
        *dt &= ~HEAD_ONLY;
-      /* Assume no restarting.  */
-      hstat.restval = 0L;
+
       /* Decide whether or not to restart.  */
-      if (((count > 1 && (*dt & ACCEPTRANGES)) || opt.always_rest)
-         /* #### this calls access() and then stat(); could be optimized. */
-         && file_exists_p (locf))
-       if (stat (locf, &st) == 0 && S_ISREG (st.st_mode))
-         hstat.restval = st.st_size;
-
-      /* In `-c' is used and the file is existing and non-empty,
-        refuse to truncate it if the server doesn't support continued
-        downloads.  */
-      hstat.no_truncate = 0;
-      if (opt.always_rest && hstat.restval)
-       hstat.no_truncate = 1;
+      hstat.restval = 0;
+      if (count > 1)
+       hstat.restval = hstat.len; /* continue where we left off */
+      else if (opt.always_rest
+              && stat (locf, &st) == 0
+              && S_ISREG (st.st_mode))
+       hstat.restval = st.st_size;
 
       /* Decide whether to send the no-cache directive.  We send it in
         two cases:
@@ -1952,7 +2109,7 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          const char *fl = NULL;
          if (opt.output_document)
            {
-             if (opt.od_known_regular)
+             if (output_stream_regular)
                fl = opt.output_document;
            }
          else
@@ -1969,7 +2126,7 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          return RETROK;
        }
 
-      tmrate = retr_rate (hstat.len - hstat.restval, hstat.dltime, 0);
+      tmrate = retr_rate (hstat.rd_size, hstat.dltime, 0);
 
       if (hstat.len == hstat.contlen)
        {
@@ -2307,8 +2464,7 @@ base64_encode (const char *s, char *store, int length)
    This is done by encoding the string `USER:PASS' in base64 and
    prepending `HEADER: Basic ' to it.  */
 static char *
-basic_authentication_encode (const char *user, const char *passwd,
-                            const char *header)
+basic_authentication_encode (const char *user, const char *passwd)
 {
   char *t1, *t2, *res;
   int len1 = strlen (user) + 1 + strlen (passwd);
@@ -2316,10 +2472,12 @@ basic_authentication_encode (const char *user, const char *passwd,
 
   t1 = (char *)alloca (len1 + 1);
   sprintf (t1, "%s:%s", user, passwd);
-  t2 = (char *)alloca (1 + len2);
+
+  t2 = (char *)alloca (len2 + 1);
   base64_encode (t1, t2, len1);
-  res = (char *)xmalloc (len2 + 11 + strlen (header));
-  sprintf (res, "%s: Basic %s\r\n", header, t2);
+
+  res = (char *)xmalloc (6 + len2 + 1);
+  sprintf (res, "Basic %s", t2);
 
   return res;
 }
@@ -2503,7 +2661,7 @@ digest_authentication_encode (const char *au, const char *user,
                           + 2 * MD5_HASHLEN /*strlen (response_digest)*/
                           + (opaque ? strlen (opaque) : 0)
                           + 128);
-    sprintf (res, "Authorization: Digest \
+    sprintf (res, "Digest \
 username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
             user, realm, nonce, path, response_digest);
     if (opaque)
@@ -2513,7 +2671,6 @@ username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
        strcat (p, opaque);
        strcat (p, "\"");
       }
-    strcat (res, "\r\n");
   }
   return res;
 }
@@ -2545,17 +2702,13 @@ create_authorization_line (const char *au, const char *user,
                           const char *passwd, const char *method,
                           const char *path)
 {
-  char *wwwauth = NULL;
-
-  if (!strncasecmp (au, "Basic", 5))
-    wwwauth = basic_authentication_encode (user, passwd, "Authorization");
-  if (!strncasecmp (au, "NTLM", 4))
-    wwwauth = basic_authentication_encode (user, passwd, "Authorization");
+  if (0 == strncasecmp (au, "Basic", 5))
+    return basic_authentication_encode (user, passwd);
 #ifdef USE_DIGEST
-  else if (!strncasecmp (au, "Digest", 6))
-    wwwauth = digest_authentication_encode (au, user, passwd, method, path);
+  if (0 == strncasecmp (au, "Digest", 6))
+    return digest_authentication_encode (au, user, passwd, method, path);
 #endif /* USE_DIGEST */
-  return wwwauth;
+  return NULL;
 }
 \f
 void