]> sjero.net Git - wget/blobdiff - src/http.c
More progress bar aesthetic changes
[wget] / src / http.c
index a499c176b89e51d0b130dd032313e7138c92c43f..0583faed58b0e92eff8dcb9025f60c4724f6f639 100644 (file)
@@ -1,11 +1,13 @@
 /* HTTP support.
-   Copyright (C) 1996-2005 Free Software Foundation, Inc.
+   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+   2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation,
+   Inc.
 
 This file is part of GNU Wget.
 
 GNU Wget is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+the Free Software Foundation; either version 3 of the License, or
  (at your option) any later version.
 
 GNU Wget is distributed in the hope that it will be useful,
@@ -14,33 +16,31 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
-along with Wget; if not, write to the Free Software Foundation, Inc.,
-51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+along with Wget.  If not, see <http://www.gnu.org/licenses/>.
 
-In addition, as a special exception, the Free Software Foundation
-gives permission to link the code of its release of Wget with the
-OpenSSL project's "OpenSSL" library (or with modified versions of it
-that use the same license as the "OpenSSL" library), and distribute
-the linked executables.  You must obey the GNU General Public License
-in all respects for all of the code used other than "OpenSSL".  If you
-modify this file, you may extend this exception to your version of the
-file, but you are not obligated to do so.  If you do not wish to do
-so, delete this exception statement from your version.  */
+Additional permission under GNU GPL version 3 section 7
 
-#include <config.h>
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.  */
+
+#include "wget.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
+#include <unistd.h>
 #include <assert.h>
 #include <errno.h>
 #include <time.h>
 #include <locale.h>
 
-#include "wget.h"
+#include "hash.h"
 #include "http.h"
 #include "utils.h"
 #include "url.h"
@@ -55,13 +55,31 @@ so, delete this exception statement from your version.  */
 # include "http-ntlm.h"
 #endif
 #include "cookies.h"
-#ifdef ENABLE_DIGEST
-# include "gen-md5.h"
-#endif
+#include "md5.h"
 #include "convert.h"
+#include "spider.h"
+#include "warc.h"
+
+#ifdef TESTING
+#include "test.h"
+#endif
+
+#ifdef __VMS
+# include "vms.h"
+#endif /* def __VMS */
 
 extern char *version_string;
 
+/* Forward decls. */
+struct http_stat;
+static char *create_authorization_line (const char *, const char *,
+                                        const char *, const char *,
+                                        const char *, bool *, uerr_t *);
+static char *basic_authentication_encode (const char *, const char *);
+static bool known_authentication_scheme_p (const char *, const char *);
+static void ensure_extension (struct http_stat *, const char *, int *);
+static void load_cookies (void);
+
 #ifndef MIN
 # define MIN(x, y) ((x) > (y) ? (y) : (x))
 #endif
@@ -72,8 +90,10 @@ static struct cookie_jar *wget_cookie_jar;
 
 #define TEXTHTML_S "text/html"
 #define TEXTXHTML_S "application/xhtml+xml"
+#define TEXTCSS_S "text/css"
 
 /* Some status code validation macros: */
+#define H_10X(x)        (((x) >= 100) && ((x) < 200))
 #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          \
@@ -125,27 +145,22 @@ struct request {
   int hcount, hcapacity;
 };
 
-/* Create a new, empty request.  At least request_set_method must be
-   called before the request can be used.  */
+extern int numurls;
+
+/* Create a new, empty request. Set the request's method and its
+   arguments.  METHOD 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 struct request *
-request_new (void)
+request_new (const char *method, char *arg)
 {
   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->method = method;
   req->arg = arg;
+  return req;
 }
 
 /* Return the method string passed with the last call to
@@ -209,7 +224,7 @@ release_header (struct request_header *hdr)
    */
 
 static void
-request_set_header (struct request *req, char *name, char *value,
+request_set_header (struct request *req, const char *name, const char *value,
                     enum rp release_policy)
 {
   struct request_header *hdr;
@@ -220,7 +235,7 @@ request_set_header (struct request *req, char *name, char *value,
       /* A NULL value is a no-op; if freeing the name is requested,
          free it now to avoid leaks.  */
       if (release_policy == rel_name || release_policy == rel_both)
-        xfree (name);
+        xfree ((void *)name);
       return;
     }
 
@@ -231,8 +246,8 @@ request_set_header (struct request *req, char *name, char *value,
         {
           /* Replace existing header. */
           release_header (hdr);
-          hdr->name = name;
-          hdr->value = value;
+          hdr->name = (void *)name;
+          hdr->value = (void *)value;
           hdr->release_policy = release_policy;
           return;
         }
@@ -246,8 +261,8 @@ request_set_header (struct request *req, char *name, char *value,
       req->headers = xrealloc (req->headers, req->hcapacity * sizeof (*hdr));
     }
   hdr = &req->headers[req->hcount++];
-  hdr->name = name;
-  hdr->value = value;
+  hdr->name = (void *)name;
+  hdr->value = (void *)value;
   hdr->release_policy = release_policy;
 }
 
@@ -265,7 +280,7 @@ request_set_user_header (struct request *req, const char *header)
     return;
   BOUNDED_TO_ALLOCA (header, p, name);
   ++p;
-  while (ISSPACE (*p))
+  while (c_isspace (*p))
     ++p;
   request_set_header (req, xstrdup (name), (char *) p, rel_name);
 }
@@ -274,7 +289,7 @@ request_set_user_header (struct request *req, const char *header)
    the header was actually removed, false otherwise.  */
 
 static bool
-request_remove_header (struct request *req, char *name)
+request_remove_header (struct request *req, const char *name)
 {
   int i;
   for (i = 0; i < req->hcount; i++)
@@ -299,10 +314,12 @@ request_remove_header (struct request *req, char *name)
   p += A_len;                                   \
 } while (0)
 
-/* Construct the request and write it to FD using fd_write.  */
+/* Construct the request and write it to FD using fd_write.
+   If warc_tmp is set to a file pointer, the request string will
+   also be written to that file. */
 
 static int
-request_send (const struct request *req, int fd)
+request_send (const struct request *req, int fd, FILE *warc_tmp)
 {
   char *request_string, *p;
   int i, size, write_error;
@@ -329,7 +346,7 @@ request_send (const struct request *req, int fd)
 
   APPEND (p, req->method); *p++ = ' ';
   APPEND (p, req->arg);    *p++ = ' ';
-  memcpy (p, "HTTP/1.0\r\n", 10); p += 10;
+  memcpy (p, "HTTP/1.1\r\n", 10); p += 10;
 
   for (i = 0; i < req->hcount; i++)
     {
@@ -353,6 +370,13 @@ request_send (const struct request *req, int fd)
   if (write_error < 0)
     logprintf (LOG_VERBOSE, _("Failed writing HTTP request: %s.\n"),
                fd_errstr (fd));
+  else if (warc_tmp != NULL)
+    {
+      /* Write a copy of the data to the WARC record. */
+      int warc_tmp_written = fwrite (request_string, 1, size - 1, warc_tmp);
+      if (warc_tmp_written != size - 1)
+        return -2;
+    }
   return write_error;
 }
 
@@ -369,19 +393,73 @@ request_free (struct request *req)
   xfree (req);
 }
 
+static struct hash_table *basic_authed_hosts;
+
+/* Find out if this host has issued a Basic challenge yet; if so, give
+ * it the username, password. A temporary measure until we can get
+ * proper authentication in place. */
+
+static bool
+maybe_send_basic_creds (const char *hostname, const char *user,
+                        const char *passwd, struct request *req)
+{
+  bool do_challenge = false;
+
+  if (opt.auth_without_challenge)
+    {
+      DEBUGP (("Auth-without-challenge set, sending Basic credentials.\n"));
+      do_challenge = true;
+    }
+  else if (basic_authed_hosts
+      && hash_table_contains(basic_authed_hosts, hostname))
+    {
+      DEBUGP (("Found %s in basic_authed_hosts.\n", quote (hostname)));
+      do_challenge = true;
+    }
+  else
+    {
+      DEBUGP (("Host %s has not issued a general basic challenge.\n",
+              quote (hostname)));
+    }
+  if (do_challenge)
+    {
+      request_set_header (req, "Authorization",
+                          basic_authentication_encode (user, passwd),
+                          rel_value);
+    }
+  return do_challenge;
+}
+
+static void
+register_basic_auth_host (const char *hostname)
+{
+  if (!basic_authed_hosts)
+    {
+      basic_authed_hosts = make_nocase_string_hash_table (1);
+    }
+  if (!hash_table_contains(basic_authed_hosts, hostname))
+    {
+      hash_table_put (basic_authed_hosts, xstrdup(hostname), NULL);
+      DEBUGP (("Inserted %s into basic_authed_hosts\n", quote (hostname)));
+    }
+}
+
+
 /* Send the contents of FILE_NAME to SOCK.  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.  */
+   longer, read only that much; if the file is shorter, report an error.
+   If warc_tmp is set to a file pointer, the post data will
+   also be written to that file.  */
 
 static int
-post_file (int sock, const char *file_name, wgint promised_size)
+body_file_send (int sock, const char *file_name, wgint promised_size, FILE *warc_tmp)
 {
   static char chunk[8192];
   wgint written = 0;
   int write_error;
   FILE *fp;
 
-  DEBUGP (("[writing POST file %s ... ", file_name));
+  DEBUGP (("[writing BODY file %s ... ", file_name));
 
   fp = fopen (file_name, "rb");
   if (!fp)
@@ -399,6 +477,16 @@ post_file (int sock, const char *file_name, wgint promised_size)
           fclose (fp);
           return -1;
         }
+      if (warc_tmp != NULL)
+        {
+          /* Write a copy of the data to the WARC record. */
+          int warc_tmp_written = fwrite (chunk, 1, towrite, warc_tmp);
+          if (warc_tmp_written != towrite)
+            {
+              fclose (fp);
+              return -2;
+            }
+        }
       written += towrite;
     }
   fclose (fp);
@@ -595,9 +683,9 @@ resp_header_locate (const struct response *resp, const char *name, int start,
           && 0 == strncasecmp (b, name, name_len))
         {
           b += name_len + 1;
-          while (b < e && ISSPACE (*b))
+          while (b < e && c_isspace (*b))
             ++b;
-          while (b < e && ISSPACE (e[-1]))
+          while (b < e && c_isspace (e[-1]))
             --e;
           *begptr = b;
           *endptr = e;
@@ -696,17 +784,17 @@ resp_status (const struct response *resp, char **message)
   if (p < end && *p == '/')
     {
       ++p;
-      while (p < end && ISDIGIT (*p))
+      while (p < end && c_isdigit (*p))
         ++p;
       if (p < end && *p == '.')
-        ++p; 
-      while (p < end && ISDIGIT (*p))
+        ++p;
+      while (p < end && c_isdigit (*p))
         ++p;
     }
 
-  while (p < end && ISSPACE (*p))
+  while (p < end && c_isspace (*p))
     ++p;
-  if (end - p < 3 || !ISDIGIT (p[0]) || !ISDIGIT (p[1]) || !ISDIGIT (p[2]))
+  if (end - p < 3 || !c_isdigit (p[0]) || !c_isdigit (p[1]) || !c_isdigit (p[2]))
     return -1;
 
   status = 100 * (p[0] - '0') + 10 * (p[1] - '0') + (p[2] - '0');
@@ -714,9 +802,9 @@ resp_status (const struct response *resp, char **message)
 
   if (message)
     {
-      while (p < end && ISSPACE (*p))
+      while (p < end && c_isspace (*p))
         ++p;
-      while (p < end && ISSPACE (end[-1]))
+      while (p < end && c_isspace (end[-1]))
         --end;
       *message = strdupdelim (p, end);
     }
@@ -733,6 +821,21 @@ resp_free (struct response *resp)
   xfree (resp);
 }
 
+/* Print a single line of response, the characters [b, e).  We tried
+   getting away with
+      logprintf (LOG_VERBOSE, "%s%.*s\n", prefix, (int) (e - b), b);
+   but that failed to escape the non-printable characters and, in fact,
+   caused crashes in UTF-8 locales.  */
+
+static void
+print_response_line(const char *prefix, const char *b, const char *e)
+{
+  char *copy;
+  BOUNDED_TO_ALLOCA(b, e, copy);
+  logprintf (LOG_ALWAYS, "%s%s\n", prefix,
+             quotearg_style (escape_quoting_style, copy));
+}
+
 /* Print the server response, line by line, omitting the trailing CRLF
    from individual header lines, and prefixed with PREFIX.  */
 
@@ -751,9 +854,7 @@ print_server_response (const struct response *resp, const char *prefix)
         --e;
       if (b < e && e[-1] == '\r')
         --e;
-      /* This is safe even on printfs with broken handling of "%.<n>s"
-         because resp->headers ends with \0.  */
-      logprintf (LOG_VERBOSE, "%s%.*s\n", prefix, e - b, b);
+      print_response_line(prefix, b, e);
     }
 }
 
@@ -775,27 +876,30 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
          HTTP spec. */
       if (*hdr == ':')
         ++hdr;
-      while (ISSPACE (*hdr))
+      while (c_isspace (*hdr))
         ++hdr;
       if (!*hdr)
         return false;
     }
-  if (!ISDIGIT (*hdr))
+  if (!c_isdigit (*hdr))
     return false;
-  for (num = 0; ISDIGIT (*hdr); hdr++)
+  for (num = 0; c_isdigit (*hdr); hdr++)
     num = 10 * num + (*hdr - '0');
-  if (*hdr != '-' || !ISDIGIT (*(hdr + 1)))
+  if (*hdr != '-' || !c_isdigit (*(hdr + 1)))
     return false;
   *first_byte_ptr = num;
   ++hdr;
-  for (num = 0; ISDIGIT (*hdr); hdr++)
+  for (num = 0; c_isdigit (*hdr); hdr++)
     num = 10 * num + (*hdr - '0');
-  if (*hdr != '/' || !ISDIGIT (*(hdr + 1)))
+  if (*hdr != '/' || !c_isdigit (*(hdr + 1)))
     return false;
   *last_byte_ptr = num;
   ++hdr;
-  for (num = 0; ISDIGIT (*hdr); hdr++)
-    num = 10 * num + (*hdr - '0');
+  if (*hdr == '*')
+    num = -1;
+  else
+    for (num = 0; c_isdigit (*hdr); hdr++)
+      num = 10 * num + (*hdr - '0');
   *entity_length_ptr = num;
   return true;
 }
@@ -810,29 +914,50 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
    mode, the body is displayed for debugging purposes.  */
 
 static bool
-skip_short_body (int fd, wgint contlen)
+skip_short_body (int fd, wgint contlen, bool chunked)
 {
   enum {
     SKIP_SIZE = 512,                /* size of the download buffer */
     SKIP_THRESHOLD = 4096        /* the largest size we read */
   };
+  wgint remaining_chunk_size = 0;
   char dlbuf[SKIP_SIZE + 1];
   dlbuf[SKIP_SIZE] = '\0';        /* so DEBUGP can safely print it */
 
-  /* We shouldn't get here with unknown contlen.  (This will change
-     with HTTP/1.1, which supports "chunked" transfer.)  */
-  assert (contlen != -1);
-
   /* If the body is too large, it makes more sense to simply close the
      connection than to try to read the body.  */
   if (contlen > SKIP_THRESHOLD)
     return false;
 
-  DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));
-
-  while (contlen > 0)
+  while (contlen > 0 || chunked)
     {
-      int ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);
+      int ret;
+      if (chunked)
+        {
+          if (remaining_chunk_size == 0)
+            {
+              char *line = fd_read_line (fd);
+              char *endl;
+              if (line == NULL)
+                break;
+
+              remaining_chunk_size = strtol (line, &endl, 16);
+              xfree (line);
+
+              if (remaining_chunk_size == 0)
+                {
+                  line = fd_read_line (fd);
+                  xfree_null (line);
+                  break;
+                }
+            }
+
+          contlen = MIN (remaining_chunk_size, SKIP_SIZE);
+        }
+
+      DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));
+
+      ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);
       if (ret <= 0)
         {
           /* Don't normally report the error since this is an
@@ -842,6 +967,20 @@ skip_short_body (int fd, wgint contlen)
           return false;
         }
       contlen -= ret;
+
+      if (chunked)
+        {
+          remaining_chunk_size -= ret;
+          if (remaining_chunk_size == 0)
+            {
+              char *line = fd_read_line (fd);
+              if (line == NULL)
+                return false;
+              else
+                xfree (line);
+            }
+        }
+
       /* Safe even if %.*s bogusly expects terminating \0 because
          we've zero-terminated dlbuf above.  */
       DEBUGP (("%.*s", ret, dlbuf));
@@ -850,6 +989,241 @@ skip_short_body (int fd, wgint contlen)
   DEBUGP (("] done.\n"));
   return true;
 }
+
+#define NOT_RFC2231 0
+#define RFC2231_NOENCODING 1
+#define RFC2231_ENCODING 2
+
+/* extract_param extracts the parameter name into NAME.
+   However, if the parameter name is in RFC2231 format then
+   this function adjusts NAME by stripping of the trailing
+   characters that are not part of the name but are present to
+   indicate the presence of encoding information in the value
+   or a fragment of a long parameter value
+*/
+static int
+modify_param_name(param_token *name)
+{
+  const char *delim1 = memchr (name->b, '*', name->e - name->b);
+  const char *delim2 = memrchr (name->b, '*', name->e - name->b);
+
+  int result;
+
+  if(delim1 == NULL)
+    {
+      result = NOT_RFC2231;
+    }
+  else if(delim1 == delim2)
+    {
+      if ((name->e - 1) == delim1)
+       {
+         result = RFC2231_ENCODING;
+       }
+      else
+       {
+         result = RFC2231_NOENCODING;
+       }
+      name->e = delim1;
+    }
+  else
+    {
+      name->e = delim1;
+      result = RFC2231_ENCODING;
+    }
+  return result;
+}
+
+/* extract_param extract the paramater value into VALUE.
+   Like modify_param_name this function modifies VALUE by
+   stripping off the encoding information from the actual value
+*/
+static void
+modify_param_value (param_token *value, int encoding_type )
+{
+  if (encoding_type == RFC2231_ENCODING)
+    {
+      const char *delim = memrchr (value->b, '\'', value->e - value->b);
+      if ( delim != NULL )
+       {
+         value->b = (delim+1);
+       }
+    }
+}
+
+/* Extract a parameter from the string (typically an HTTP header) at
+   **SOURCE and advance SOURCE to the next parameter.  Return false
+   when there are no more parameters to extract.  The name of the
+   parameter is returned in NAME, and the value in VALUE.  If the
+   parameter has no value, the token's value is zeroed out.
+
+   For example, if *SOURCE points to the string "attachment;
+   filename=\"foo bar\"", the first call to this function will return
+   the token named "attachment" and no value, and the second call will
+   return the token named "filename" and value "foo bar".  The third
+   call will return false, indicating no more valid tokens.
+
+   is_url_encoded is an out parameter. If not NULL, a boolean value will be
+   stored into it, letting the caller know whether or not the extracted value is
+   URL-encoded. The caller can then decode it with url_unescape(), which however
+   performs decoding in-place. URL-encoding is used by RFC 2231 to support
+   non-US-ASCII characters in HTTP header values.  */
+
+bool
+extract_param (const char **source, param_token *name, param_token *value,
+               char separator, bool *is_url_encoded)
+{
+  const char *p = *source;
+  int param_type;
+  if (is_url_encoded)
+    *is_url_encoded = false;   /* initializing the out parameter */
+
+  while (c_isspace (*p)) ++p;
+  if (!*p)
+    {
+      *source = p;
+      return false;             /* no error; nothing more to extract */
+    }
+
+  /* Extract name. */
+  name->b = p;
+  while (*p && !c_isspace (*p) && *p != '=' && *p != separator) ++p;
+  name->e = p;
+  if (name->b == name->e)
+    return false;               /* empty name: error */
+  while (c_isspace (*p)) ++p;
+  if (*p == separator || !*p)           /* no value */
+    {
+      xzero (*value);
+      if (*p == separator) ++p;
+      *source = p;
+      return true;
+    }
+  if (*p != '=')
+    return false;               /* error */
+
+  /* *p is '=', extract value */
+  ++p;
+  while (c_isspace (*p)) ++p;
+  if (*p == '"')                /* quoted */
+    {
+      value->b = ++p;
+      while (*p && *p != '"') ++p;
+      if (!*p)
+        return false;
+      value->e = p++;
+      /* Currently at closing quote; find the end of param. */
+      while (c_isspace (*p)) ++p;
+      while (*p && *p != separator) ++p;
+      if (*p == separator)
+        ++p;
+      else if (*p)
+        /* garbage after closed quote, e.g. foo="bar"baz */
+        return false;
+    }
+  else                          /* unquoted */
+    {
+      value->b = p;
+      while (*p && *p != separator) ++p;
+      value->e = p;
+      while (value->e != value->b && c_isspace (value->e[-1]))
+        --value->e;
+      if (*p == separator) ++p;
+    }
+  *source = p;
+
+  param_type = modify_param_name(name);
+  if (param_type != NOT_RFC2231)
+    {
+      if (param_type == RFC2231_ENCODING && is_url_encoded)
+        *is_url_encoded = true;
+      modify_param_value(value, param_type);
+    }
+  return true;
+}
+
+#undef NOT_RFC2231
+#undef RFC2231_NOENCODING
+#undef RFC2231_ENCODING
+
+/* Appends the string represented by VALUE to FILENAME */
+
+static void
+append_value_to_filename (char **filename, param_token const * const value,
+                          bool is_url_encoded)
+{
+  int original_length = strlen(*filename);
+  int new_length = strlen(*filename) + (value->e - value->b);
+  *filename = xrealloc (*filename, new_length+1);
+  memcpy (*filename + original_length, value->b, (value->e - value->b)); 
+  (*filename)[new_length] = '\0';
+  if (is_url_encoded)
+    url_unescape (*filename + original_length);
+}
+
+#undef MAX
+#define MAX(p, q) ((p) > (q) ? (p) : (q))
+
+/* Parse the contents of the `Content-Disposition' header, extracting
+   the information useful to Wget.  Content-Disposition is a header
+   borrowed from MIME; when used in HTTP, it typically serves for
+   specifying the desired file name of the resource.  For example:
+
+       Content-Disposition: attachment; filename="flora.jpg"
+
+   Wget will skip the tokens it doesn't care about, such as
+   "attachment" in the previous example; it will also skip other
+   unrecognized params.  If the header is syntactically correct and
+   contains a file name, a copy of the file name is stored in
+   *filename and true is returned.  Otherwise, the function returns
+   false.
+
+   The file name is stripped of directory components and must not be
+   empty.
+
+   Historically, this function returned filename prefixed with opt.dir_prefix,
+   now that logic is handled by the caller, new code should pay attention,
+   changed by crq, Sep 2010.
+
+*/
+static bool
+parse_content_disposition (const char *hdr, char **filename)
+{
+  param_token name, value;
+  *filename = NULL;
+  bool is_url_encoded = false;
+  for ( ; extract_param (&hdr, &name, &value, ';', &is_url_encoded);
+        is_url_encoded = false)
+    {
+      int isFilename = BOUNDED_EQUAL_NO_CASE ( name.b, name.e, "filename" );
+      if ( isFilename && value.b != NULL)
+        {
+          /* Make the file name begin at the last slash or backslash. */
+          const char *last_slash = memrchr (value.b, '/', value.e - value.b);
+          const char *last_bs = memrchr (value.b, '\\', value.e - value.b);
+          if (last_slash && last_bs)
+            value.b = 1 + MAX (last_slash, last_bs);
+          else if (last_slash || last_bs)
+            value.b = 1 + (last_slash ? last_slash : last_bs);
+          if (value.b == value.e)
+            continue;
+
+          if (*filename)
+            append_value_to_filename (filename, &value, is_url_encoded);
+          else
+            {
+              *filename = strdupdelim (value.b, value.e);
+              if (is_url_encoded)
+                url_unescape (*filename);
+            }
+        }
+    }
+
+  if (*filename)
+    return true;
+  else
+    return false;
+}
+
 \f
 /* Persistent connections.  Currently, we cache the most recently used
    connection as persistent, provided that the HTTP server agrees to
@@ -1080,16 +1454,21 @@ struct http_stat
   char *remote_time;            /* remote time-stamp string */
   char *error;                  /* textual HTTP error */
   int statcode;                 /* status code */
+  char *message;                /* status message */
   wgint 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 name. */
-  bool timestamp_checked;       /* true if pre-download time-stamping checks 
+  bool existence_checked;       /* true if we already checked for a file's
+                                   existence after having begun to download
+                                   (needed in gethttp for when connection is
+                                   interrupted/restarted. */
+  bool timestamp_checked;       /* true if pre-download time-stamping checks
                                  * have already been performed */
   char *orig_file_name;         /* name of file to compare for time-stamping
                                  * (might be != local_file if -K is set) */
   wgint orig_file_size;         /* size of file to compare for time-stamping */
-  time_t orig_file_tstamp;      /* time-stamp of file to compare for 
+  time_t orig_file_tstamp;      /* time-stamp of file to compare for
                                  * time-stamping */
 };
 
@@ -1102,6 +1481,7 @@ free_hstat (struct http_stat *hs)
   xfree_null (hs->rderrmsg);
   xfree_null (hs->local_file);
   xfree_null (hs->orig_file_name);
+  xfree_null (hs->message);
 
   /* Guard against being called twice. */
   hs->newloc = NULL;
@@ -1109,31 +1489,175 @@ free_hstat (struct http_stat *hs)
   hs->error = NULL;
 }
 
-static char *create_authorization_line (const char *, const char *,
-                                        const char *, const char *,
-                                        const char *, bool *);
-static char *basic_authentication_encode (const char *, const char *);
-static bool known_authentication_scheme_p (const char *, const char *);
-static void load_cookies (void);
+static void
+get_file_flags (const char *filename, int *dt)
+{
+  logprintf (LOG_VERBOSE, _("\
+File %s already there; not retrieving.\n\n"), quote (filename));
+  /* If the file is there, we suppose it's retrieved OK.  */
+  *dt |= RETROKF;
+
+  /* #### Bogusness alert.  */
+  /* If its suffix is "html" or "htm" or similar, assume text/html.  */
+  if (has_html_suffix_p (filename))
+    *dt |= TEXTHTML;
+}
+
+/* Download the response body from the socket and writes it to
+   an output file.  The headers have already been read from the
+   socket.  If WARC is enabled, the response body will also be
+   written to a WARC response record.
+
+   hs, contlen, contrange, chunked_transfer_encoding and url are
+   parameters from the gethttp method.  fp is a pointer to the
+   output file.
+
+   url, warc_timestamp_str, warc_request_uuid, warc_ip, type
+   and statcode will be saved in the headers of the WARC record.
+   The head parameter contains the HTTP headers of the response.
+   If fp is NULL and WARC is enabled, the response body will be
+   written only to the WARC file.  If WARC is disabled and fp
+   is a file pointer, the data will be written to the file.
+   If fp is a file pointer and WARC is enabled, the body will
+   be written to both destinations.
+   
+   Returns the error code.   */
+static int
+read_response_body (struct http_stat *hs, int sock, FILE *fp, wgint contlen,
+                    wgint contrange, bool chunked_transfer_encoding,
+                    char *url, char *warc_timestamp_str, char *warc_request_uuid,
+                    ip_address *warc_ip, char *type, int statcode, char *head)
+{
+  int warc_payload_offset = 0;
+  FILE *warc_tmp = NULL;
+  int warcerr = 0;
+
+  if (opt.warc_filename != NULL)
+    {
+      /* Open a temporary file where we can write the response before we
+         add it to the WARC record.  */
+      warc_tmp = warc_tempfile ();
+      if (warc_tmp == NULL)
+        warcerr = WARC_TMP_FOPENERR;
+
+      if (warcerr == 0)
+        {
+          /* We should keep the response headers for the WARC record.  */
+          int head_len = strlen (head);
+          int warc_tmp_written = fwrite (head, 1, head_len, warc_tmp);
+          if (warc_tmp_written != head_len)
+            warcerr = WARC_TMP_FWRITEERR;
+          warc_payload_offset = head_len;
+        }
+
+      if (warcerr != 0)
+        {
+          if (warc_tmp != NULL)
+            fclose (warc_tmp);
+          return warcerr;
+        }
+    }
+
+  if (fp != NULL)
+    {
+      /* This confuses the timestamping code that checks for file size.
+         #### The timestamping code should be smarter about file size.  */
+      if (opt.save_headers && hs->restval == 0)
+        fwrite (head, 1, strlen (head), fp);
+    }
+
+  /* Read the response body.  */
+  int flags = 0;
+  if (contlen != -1)
+    /* If content-length is present, read that much; otherwise, read
+       until EOF.  The HTTP spec doesn't require the server to
+       actually close the connection when it's done sending data. */
+    flags |= rb_read_exactly;
+  if (fp != NULL && 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;
+  if (chunked_transfer_encoding)
+    flags |= rb_chunked_transfer_encoding;
+
+  hs->len = hs->restval;
+  hs->rd_size = 0;
+  /* Download the response body and write it to fp.
+     If we are working on a WARC file, we simultaneously write the
+     response body to warc_tmp.  */
+  hs->res = fd_read_body (hs->local_file, sock, fp, contlen != -1 ? contlen : 0,
+                          hs->restval, &hs->rd_size, &hs->len, &hs->dltime,
+                          flags, warc_tmp);
+  if (hs->res >= 0)
+    {
+      if (warc_tmp != NULL)
+        {
+          /* Create a response record and write it to the WARC file.
+             Note: per the WARC standard, the request and response should share
+             the same date header.  We re-use the timestamp of the request.
+             The response record should also refer to the uuid of the request.  */
+          bool r = warc_write_response_record (url, warc_timestamp_str,
+                                               warc_request_uuid, warc_ip,
+                                               warc_tmp, warc_payload_offset,
+                                               type, statcode, hs->newloc);
+
+          /* warc_write_response_record has closed warc_tmp. */
+
+          if (! r)
+            return WARC_ERR;
+        }
+
+      return RETRFINISHED;
+    }
+  
+  if (warc_tmp != NULL)
+    fclose (warc_tmp);
+
+  if (hs->res == -2)
+    {
+      /* Error while writing to fd. */
+      return FWRITEERR;
+    }
+  else if (hs->res == -3)
+    {
+      /* Error while writing to warc_tmp. */
+      return WARC_TMP_FWRITEERR;
+    }
+  else
+    {
+      /* A read error! */
+      hs->rderrmsg = xstrdup (fd_errstr (sock));
+      return RETRFINISHED;
+    }
+}
 
 #define BEGINS_WITH(line, string_constant)                               \
   (!strncasecmp (line, string_constant, sizeof (string_constant) - 1)    \
-   && (ISSPACE (line[sizeof (string_constant) - 1])                      \
+   && (c_isspace (line[sizeof (string_constant) - 1])                      \
        || !line[sizeof (string_constant) - 1]))
 
+#ifdef __VMS
 #define SET_USER_AGENT(req) do {                                         \
   if (!opt.useragent)                                                    \
     request_set_header (req, "User-Agent",                               \
-                        aprintf ("Wget/%s", version_string), rel_value); \
+                        aprintf ("Wget/%s (VMS %s %s)",                  \
+                        version_string, vms_arch(), vms_vers()),         \
+                        rel_value);                                      \
   else if (*opt.useragent)                                               \
     request_set_header (req, "User-Agent", opt.useragent, rel_none);     \
 } while (0)
-
-/* The flags that allow clobbering the file (opening with "wb").
-   Defined here to avoid repetition later.  #### This will require
-   rework.  */
-#define ALLOW_CLOBBER (opt.noclobber || opt.always_rest || opt.timestamping \
-                       || opt.dirstruct || opt.output_document)
+#else /* def __VMS */
+#define SET_USER_AGENT(req) do {                                         \
+  if (!opt.useragent)                                                    \
+    request_set_header (req, "User-Agent",                               \
+                        aprintf ("Wget/%s (%s)",                         \
+                        version_string, OS_TYPE),                        \
+                        rel_value);                                      \
+  else if (*opt.useragent)                                               \
+    request_set_header (req, "User-Agent", opt.useragent, rel_none);     \
+} while (0)
+#endif /* def __VMS [else] */
 
 /* Retrieve a document through HTTP protocol.  It recognizes status
    code, and correctly handles redirections.  It closes the network
@@ -1146,7 +1670,8 @@ static void load_cookies (void);
    If PROXY is non-NULL, the connection will be made to the proxy
    server, and u->url will be requested.  */
 static uerr_t
-gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
+gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy,
+         struct iri *iri, int count)
 {
   struct request *req;
 
@@ -1158,14 +1683,19 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   wgint contlen, contrange;
   struct url *conn;
   FILE *fp;
+  int err;
 
   int sock = -1;
-  int flags;
 
-  /* Set to 1 when the authorization has failed permanently and should
+  /* Set to 1 when the authorization has already been sent and should
      not be tried again. */
   bool auth_finished = false;
 
+  /* Set to 1 when just globally-set Basic authorization has been sent;
+   * should prevent further Basic negotiations, but not other
+   * mechanisms. */
+  bool basic_auth_finished = false;
+
   /* Whether NTLM authentication is used for this request. */
   bool ntlm_seen = false;
 
@@ -1178,25 +1708,30 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
   char *head;
   struct response *resp;
-  char hdrval[256];
+  char hdrval[512];
   char *message;
 
+  /* Declare WARC variables. */
+  bool warc_enabled = (opt.warc_filename != NULL);
+  FILE *warc_tmp = NULL;
+  char warc_timestamp_str [21];
+  char warc_request_uuid [48];
+  ip_address *warc_ip = NULL;
+  off_t warc_payload_offset = -1;
+
   /* Whether this connection will be kept alive after the HTTP request
      is done. */
   bool keep_alive;
 
-  /* Whether keep-alive should be inhibited.
+  /* Is the server using the chunked transfer encoding?  */
+  bool chunked_transfer_encoding = false;
 
-     RFC 2068 requests that 1.0 clients not send keep-alive requests
-     to proxies.  This is because many 1.0 proxies do not interpret
-     the Connection header and transfer it to the remote server,
-     causing it to not close the connection and leave both the proxy
-     and the client hanging.  */
+  /* Whether keep-alive should be inhibited.  */
   bool inhibit_keep_alive =
-    !opt.http_keep_alive || opt.ignore_length || proxy != NULL;
+    !opt.http_keep_alive || opt.ignore_length;
 
   /* Headers sent when using POST. */
-  wgint post_data_size = 0;
+  wgint body_data_size = 0;
 
   bool host_lookup_failed = false;
 
@@ -1223,19 +1758,18 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   hs->newloc = NULL;
   hs->remote_time = NULL;
   hs->error = NULL;
+  hs->message = NULL;
 
   conn = u;
 
   /* Prepare the request to send. */
-
-  req = request_new ();
   {
     char *meth_arg;
     const char *meth = "GET";
     if (head_only)
       meth = "HEAD";
-    else if (opt.post_file_name || opt.post_data)
-      meth = "POST";
+    else if (opt.method)
+      meth = opt.method;
     /* 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".  */
@@ -1250,12 +1784,18 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       meth_arg = xstrdup (u->url);
     else
       meth_arg = url_full_path (u);
-    request_set_method (req, meth, meth_arg);
+    req = request_new (meth, meth_arg);
   }
 
   request_set_header (req, "Referer", (char *) hs->referer, rel_none);
   if (*dt & SEND_NOCACHE)
-    request_set_header (req, "Pragma", "no-cache", rel_none);
+    {
+      /* Cache-Control MUST be obeyed by all HTTP/1.1 caching mechanisms...  */
+      request_set_header (req, "Cache-Control", "no-cache, must-revalidate", rel_none);
+
+      /* ... but some HTTP/1.0 caches doesn't implement Cache-Control.  */
+      request_set_header (req, "Pragma", "no-cache", rel_none);
+    }
   if (hs->restval)
     request_set_header (req, "Range",
                         aprintf ("bytes=%s-",
@@ -1271,37 +1811,105 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   user = user ? user : (opt.http_user ? opt.http_user : opt.user);
   passwd = passwd ? passwd : (opt.http_passwd ? opt.http_passwd : opt.passwd);
 
-  if (user && passwd)
+  /* We only do "site-wide" authentication with "global" user/password
+   * values unless --auth-no-challange has been requested; URL user/password
+   * info overrides. */
+  if (user && passwd && (!u->user || opt.auth_without_challenge))
     {
-      /* 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 this is a host for which we've already received a Basic
+       * challenge, we'll go ahead and send Basic authentication creds. */
+      basic_auth_finished = maybe_send_basic_creds(u->host, user, passwd, req);
+    }
 
-         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.
+  /* Generate the Host header, HOST:PORT.  Take into account that:
 
-         #### 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.
+     - Broken server-side software often doesn't recognize the PORT
+       argument, so we must generate "Host: www.server.com" instead of
+       "Host: www.server.com:80" (and likewise for https port).
 
-         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);
+     - IPv6 addresses contain ":", so "Host: 3ffe:8100:200:2::2:1234"
+       becomes ambiguous and needs to be rewritten as "Host:
+       [3ffe:8100:200:2::2]:1234".  */
+  {
+    /* Formats arranged for hfmt[add_port][add_squares].  */
+    static const char *hfmt[][2] = {
+      { "%s", "[%s]" }, { "%s:%d", "[%s]:%d" }
+    };
+    int add_port = u->port != scheme_default_port (u->scheme);
+    int add_squares = strchr (u->host, ':') != NULL;
+    request_set_header (req, "Host",
+                        aprintf (hfmt[add_port][add_squares], u->host, u->port),
+                        rel_value);
+  }
+
+  if (inhibit_keep_alive)
+    request_set_header (req, "Connection", "Close", rel_none);
+  else
+    {
+      if (proxy == NULL)
+        request_set_header (req, "Connection", "Keep-Alive", rel_none);
+      else
+        {
+          request_set_header (req, "Connection", "Close", rel_none);
+          request_set_header (req, "Proxy-Connection", "Keep-Alive", rel_none);
+        }
     }
 
-  proxyauth = NULL;
-  if (proxy)
+  if (opt.method)
     {
-      char *proxy_user, *proxy_passwd;
+
+      if (opt.body_data || opt.body_file)
+        {
+          request_set_header (req, "Content-Type",
+                              "application/x-www-form-urlencoded", rel_none);
+
+          if (opt.body_data)
+            body_data_size = strlen (opt.body_data);
+          else
+            {
+              body_data_size = file_size (opt.body_file);
+              if (body_data_size == -1)
+                {
+                  logprintf (LOG_NOTQUIET, _("BODY data file %s missing: %s\n"),
+                             quote (opt.body_file), strerror (errno));
+                  return FILEBADFILE;
+                }
+            }
+          request_set_header (req, "Content-Length",
+                              xstrdup (number_to_static_string (body_data_size)),
+                              rel_value);
+        }
+    }
+
+ 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.)  */
+
+  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);
+
+  /* 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]);
+    }
+
+  proxyauth = NULL;
+  if (proxy)
+    {
+      char *proxy_user, *proxy_passwd;
       /* For normal username and password, URL components override
          command-line/wgetrc parameters.  With proxy
          authentication, it's the reverse, because proxy URLs are
@@ -1333,81 +1941,13 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
         request_set_header (req, "Proxy-Authorization", proxyauth, rel_value);
     }
 
-  /* Generate the Host header, HOST:PORT.  Take into account that:
-
-     - Broken server-side software often doesn't recognize the PORT
-       argument, so we must generate "Host: www.server.com" instead of
-       "Host: www.server.com:80" (and likewise for https port).
-
-     - IPv6 addresses contain ":", so "Host: 3ffe:8100:200:2::2:1234"
-       becomes ambiguous and needs to be rewritten as "Host:
-       [3ffe:8100:200:2::2]:1234".  */
-  {
-    /* Formats arranged for hfmt[add_port][add_squares].  */
-    static const char *hfmt[][2] = {
-      { "%s", "[%s]" }, { "%s:%d", "[%s]:%d" }
-    };
-    int add_port = u->port != scheme_default_port (u->scheme);
-    int add_squares = strchr (u->host, ':') != NULL;
-    request_set_header (req, "Host",
-                        aprintf (hfmt[add_port][add_squares], u->host, u->port),
-                        rel_value);
-  }
-
-  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 `%s' missing: %s\n"),
-                         opt.post_file_name, strerror (errno));
-              post_data_size = 0;
-            }
-        }
-      request_set_header (req, "Content-Length",
-                          xstrdup (number_to_static_string (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 = false;
+  keep_alive = true;
 
   /* Establish the connection.  */
 
-  if (!inhibit_keep_alive)
+  if (inhibit_keep_alive)
+    keep_alive = false;
+  else
     {
       /* Look for a persistent connection to target host, unless a
          proxy is used.  The exception is when SSL is in use, in which
@@ -1427,10 +1967,19 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 #endif
                                   &host_lookup_failed))
         {
+          int family = socket_family (pconn.socket, ENDPOINT_PEER);
           sock = pconn.socket;
           using_ssl = pconn.ssl;
-          logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"),
-                     escnonprint (pconn.host), pconn.port);
+#if ENABLE_IPV6
+          if (family == AF_INET6)
+             logprintf (LOG_VERBOSE, _("Reusing existing connection to [%s]:%d.\n"),
+                        quotearg_style (escape_quoting_style, pconn.host),
+                         pconn.port);
+          else
+#endif
+             logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"),
+                        quotearg_style (escape_quoting_style, pconn.host),
+                        pconn.port);
           DEBUGP (("Reusing fd %d.\n", sock));
           if (pconn.authorized)
             /* If the connection is already authorized, the "Basic"
@@ -1438,19 +1987,22 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                only hurts us.  */
             request_remove_header (req, "Authorization");
         }
-    }
-
-  if (sock < 0)
-    {
-      /* In its current implementation, persistent_available_p will
-         look up conn->host in some cases.  If that lookup failed, we
-         don't need to bother with connect_to_host.  */
-      if (host_lookup_failed)
+      else if (host_lookup_failed)
         {
           request_free (req);
+          logprintf(LOG_NOTQUIET,
+                    _("%s: unable to resolve host address %s\n"),
+                    exec_name, quote (relevant->host));
           return HOSTERR;
         }
+      else if (sock != -1)
+        {
+          sock = -1;
+        }
+    }
 
+  if (sock < 0)
+    {
       sock = connect_to_host (conn->host, conn->port);
       if (sock == E_HOST)
         {
@@ -1469,8 +2021,7 @@ 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.  */
-          struct request *connreq = request_new ();
-          request_set_method (connreq, "CONNECT",
+          struct request *connreq = request_new ("CONNECT",
                               aprintf ("%s:%d", u->host, u->port));
           SET_USER_AGENT (connreq);
           if (proxyauth)
@@ -1482,16 +2033,16 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                  the regular request below.  */
               proxyauth = NULL;
             }
-          /* Examples in rfc2817 use the Host header in CONNECT
-             requests.  I don't see how that gains anything, given
-             that the contents of Host would be exactly the same as
-             the contents of CONNECT.  */
+          request_set_header (connreq, "Host",
+                              aprintf ("%s:%d", u->host, u->port),
+                              rel_value);
 
-          write_error = request_send (connreq, sock);
+          write_error = request_send (connreq, sock, 0);
           request_free (connreq);
           if (write_error < 0)
             {
               CLOSE_INVALIDATE (sock);
+              request_free (req);
               return WRITEFAILED;
             }
 
@@ -1501,6 +2052,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
               logprintf (LOG_VERBOSE, _("Failed reading proxy response: %s\n"),
                          fd_errstr (sock));
               CLOSE_INVALIDATE (sock);
+              request_free (req);
               return HERR;
             }
           message = NULL;
@@ -1513,14 +2065,27 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
           resp = resp_new (head);
           statcode = resp_status (resp, &message);
+          if (statcode < 0)
+            {
+              char *tms = datetime_str (time (NULL));
+              logprintf (LOG_VERBOSE, "%d\n", statcode);
+              logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"), tms, statcode,
+                         quotearg_style (escape_quoting_style,
+                                         _("Malformed status line")));
+              xfree (head);
+              request_free (req);
+              return HERR;
+            }
+          hs->message = xstrdup (message);
           resp_free (resp);
           xfree (head);
           if (statcode != 200)
             {
             failed_tunnel:
               logprintf (LOG_NOTQUIET, _("Proxy tunneling failed: %s"),
-                         message ? escnonprint (message) : "?");
+                         message ? quotearg_style (escape_quoting_style, message) : "?");
               xfree_null (message);
+              request_free (req);
               return CONSSLERR;
             }
           xfree_null (message);
@@ -1533,35 +2098,83 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
       if (conn->scheme == SCHEME_HTTPS)
         {
-          if (!ssl_connect (sock) || !ssl_check_certificate (sock, u->host))
+          if (!ssl_connect_wget (sock, u->host))
             {
               fd_close (sock);
+              request_free (req);
               return CONSSLERR;
             }
+          else if (!ssl_check_certificate (sock, u->host))
+            {
+              fd_close (sock);
+              request_free (req);
+              return VERIFCERTERR;
+            }
           using_ssl = true;
         }
 #endif /* HAVE_SSL */
     }
 
+  /* Open the temporary file where we will write the request. */
+  if (warc_enabled)
+    {
+      warc_tmp = warc_tempfile ();
+      if (warc_tmp == NULL)
+        {
+          CLOSE_INVALIDATE (sock);
+          request_free (req);
+          return WARC_TMP_FOPENERR;
+        }
+
+      if (! proxy)
+        {
+          warc_ip = (ip_address *) alloca (sizeof (ip_address));
+          socket_ip_address (sock, warc_ip, ENDPOINT_PEER);
+        }
+    }
+
   /* Send the request to server.  */
-  write_error = request_send (req, sock);
+  write_error = request_send (req, sock, warc_tmp);
 
   if (write_error >= 0)
     {
-      if (opt.post_data)
+      if (opt.body_data)
         {
-          DEBUGP (("[POST data: %s]\n", opt.post_data));
-          write_error = fd_write (sock, opt.post_data, post_data_size, -1);
+          DEBUGP (("[BODY data: %s]\n", opt.body_data));
+          write_error = fd_write (sock, opt.body_data, body_data_size, -1);
+          if (write_error >= 0 && warc_tmp != NULL)
+            {
+              /* Remember end of headers / start of payload. */
+              warc_payload_offset = ftello (warc_tmp);
+
+              /* Write a copy of the data to the WARC record. */
+              int warc_tmp_written = fwrite (opt.body_data, 1, body_data_size, warc_tmp);
+              if (warc_tmp_written != body_data_size)
+                write_error = -2;
+            }
+         }
+      else if (opt.body_file && body_data_size != 0)
+        {
+          if (warc_tmp != NULL)
+            /* Remember end of headers / start of payload */
+            warc_payload_offset = ftello (warc_tmp);
+
+          write_error = body_file_send (sock, opt.body_file, body_data_size, warc_tmp);
         }
-      else if (opt.post_file_name && post_data_size != 0)
-        write_error = post_file (sock, opt.post_file_name, post_data_size);
     }
 
   if (write_error < 0)
     {
       CLOSE_INVALIDATE (sock);
       request_free (req);
-      return WRITEFAILED;
+
+      if (warc_tmp != NULL)
+        fclose (warc_tmp);
+
+      if (write_error == -2)
+        return WARC_TMP_FWRITEERR;
+      else
+        return WRITEFAILED;
     }
   logprintf (LOG_VERBOSE, _("%s request sent, awaiting response... "),
              proxy ? "Proxy" : "HTTP");
@@ -1569,6 +2182,30 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   contrange = 0;
   *dt &= ~RETROKF;
 
+
+  if (warc_enabled)
+    {
+      bool warc_result;
+      /* Generate a timestamp and uuid for this request. */
+      warc_timestamp (warc_timestamp_str);
+      warc_uuid_str (warc_request_uuid);
+
+      /* Create a request record and store it in the WARC file. */
+      warc_result = warc_write_request_record (u->url, warc_timestamp_str,
+                                               warc_request_uuid, warc_ip,
+                                               warc_tmp, warc_payload_offset);
+      if (! warc_result)
+        {
+          CLOSE_INVALIDATE (sock);
+          request_free (req);
+          return WARC_ERR;
+        }
+
+      /* warc_write_request_record has also closed warc_tmp. */
+    }
+
+
+read_header:
   head = read_http_response_head (sock);
   if (!head)
     {
@@ -1595,55 +2232,285 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   /* Check for status line.  */
   message = NULL;
   statcode = resp_status (resp, &message);
+  if (statcode < 0)
+    {
+      char *tms = datetime_str (time (NULL));
+      logprintf (LOG_VERBOSE, "%d\n", statcode);
+      logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"), tms, statcode,
+                 quotearg_style (escape_quoting_style,
+                                 _("Malformed status line")));
+      CLOSE_INVALIDATE (sock);
+      resp_free (resp);
+      request_free (req);
+      xfree (head);
+      return HERR;
+    }
+
+  if (H_10X (statcode))
+    {
+      DEBUGP (("Ignoring response\n"));
+      resp_free (resp);
+      xfree (head);
+      goto read_header;
+    }
+
+  hs->message = xstrdup (message);
   if (!opt.server_response)
     logprintf (LOG_VERBOSE, "%2d %s\n", statcode,
-               message ? escnonprint (message) : "");
+               message ? quotearg_style (escape_quoting_style, message) : "");
   else
     {
       logprintf (LOG_VERBOSE, "\n");
       print_server_response (resp, "  ");
     }
 
-  /* Determine the local filename if needed. Notice that if -O is used 
-   * hstat.local_file is set by http_loop to the argument of -O. */
-  if (!hs->local_file)     
+  if (!opt.ignore_length
+      && resp_header_copy (resp, "Content-Length", hdrval, sizeof (hdrval)))
     {
-      if (resp_header_copy (resp, "Content-Disposition", hdrval, sizeof (hdrval)))
-        /* Honor Content-Disposition. */
+      wgint parsed;
+      errno = 0;
+      parsed = str_to_wgint (hdrval, NULL, 10);
+      if (parsed == WGINT_MAX && errno == ERANGE)
+        {
+          /* Out of range.
+             #### If Content-Length is out of range, it most likely
+             means that the file is larger than 2G and that we're
+             compiled without LFS.  In that case we should probably
+             refuse to even attempt to download the file.  */
+          contlen = -1;
+        }
+      else if (parsed < 0)
         {
-          hs->local_file = xstrdup (hdrval);
+          /* Negative Content-Length; nonsensical, so we can't
+             assume any information about the content to receive. */
+          contlen = -1;
         }
       else
-        /* Choose filename according to URL name. */
+        contlen = parsed;
+    }
+
+  /* Check for keep-alive related responses. */
+  if (!inhibit_keep_alive && contlen != -1)
+    {
+      if (resp_header_copy (resp, "Connection", hdrval, sizeof (hdrval)))
         {
-          hs->local_file = url_file_name (u);
+          if (0 == strcasecmp (hdrval, "Close"))
+            keep_alive = false;
         }
     }
-  
-  /* TODO: perform this check only once. */
-  if (opt.noclobber && file_exists_p (hs->local_file))
+
+  chunked_transfer_encoding = false;
+  if (resp_header_copy (resp, "Transfer-Encoding", hdrval, sizeof (hdrval))
+      && 0 == strcasecmp (hdrval, "chunked"))
+    chunked_transfer_encoding = true;
+
+  /* Handle (possibly multiple instances of) the Set-Cookie header. */
+  if (opt.cookies)
     {
-      /* If opt.noclobber is turned on and file already exists, do not
-         retrieve the file */
-      logprintf (LOG_VERBOSE, _("\
-File `%s' already there; not retrieving.\n\n"), hs->local_file);
-      /* If the file is there, we suppose it's retrieved OK.  */
-      *dt |= RETROKF;
+      int scpos;
+      const char *scbeg, *scend;
+      /* The jar should have been created by now. */
+      assert (wget_cookie_jar != NULL);
+      for (scpos = 0;
+           (scpos = resp_header_locate (resp, "Set-Cookie", scpos,
+                                        &scbeg, &scend)) != -1;
+           ++scpos)
+        {
+          char *set_cookie; BOUNDED_TO_ALLOCA (scbeg, scend, set_cookie);
+          cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port,
+                                    u->path, set_cookie);
+        }
+    }
+
+  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)
+    {
+      /* Authorization is required.  */
+
+      /* Normally we are not interested in the response body.
+         But if we are writing a WARC file we are: we like to keep everyting.  */
+      if (warc_enabled)
+        {
+          int _err;
+          type = resp_header_strdup (resp, "Content-Type");
+          _err = read_response_body (hs, sock, NULL, contlen, 0,
+                                    chunked_transfer_encoding,
+                                    u->url, warc_timestamp_str,
+                                    warc_request_uuid, warc_ip, type,
+                                    statcode, head);
+          xfree_null (type);
+
+          if (_err != RETRFINISHED || hs->res < 0)
+            {
+              CLOSE_INVALIDATE (sock);
+              request_free (req);
+              xfree_null (message);
+              resp_free (resp);
+              xfree (head);
+              return _err;
+            }
+          else
+            CLOSE_FINISH (sock);
+        }
+      else
+        {
+          /* Since WARC is disabled, we are not interested in the response body.  */
+          if (keep_alive && !head_only
+              && skip_short_body (sock, contlen, chunked_transfer_encoding))
+            CLOSE_FINISH (sock);
+          else
+            CLOSE_INVALIDATE (sock);
+        }
 
-      /* #### Bogusness alert.  */
-      /* If its suffix is "html" or "htm" or similar, assume text/html.  */
-      if (has_html_suffix_p (hs->local_file))
-        *dt |= TEXTHTML;
+      pconn.authorized = false;
+      uerr_t auth_err = RETROK;
+      if (!auth_finished && (user && passwd))
+        {
+          /* IIS sends multiple copies of WWW-Authenticate, one with
+             the value "negotiate", and other(s) with data.  Loop over
+             all the occurrences and pick the one we recognize.  */
+          int wapos;
+          const char *wabeg, *waend;
+          char *www_authenticate = NULL;
+          for (wapos = 0;
+               (wapos = resp_header_locate (resp, "WWW-Authenticate", wapos,
+                                            &wabeg, &waend)) != -1;
+               ++wapos)
+            if (known_authentication_scheme_p (wabeg, waend))
+              {
+                BOUNDED_TO_ALLOCA (wabeg, waend, www_authenticate);
+                break;
+              }
 
-      return RETROK;
+          if (!www_authenticate)
+            {
+              /* If the authentication header is missing or
+                 unrecognized, there's no sense in retrying.  */
+              logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
+            }
+          else if (!basic_auth_finished
+                   || !BEGINS_WITH (www_authenticate, "Basic"))
+            {
+              char *pth = url_full_path (u);
+              const char *value;
+              uerr_t *auth_stat;
+              auth_stat = xmalloc (sizeof (uerr_t));
+              *auth_stat = RETROK;
+
+              value =  create_authorization_line (www_authenticate,
+                                                  user, passwd,
+                                                  request_method (req),
+                                                  pth,
+                                                  &auth_finished,
+                                                  auth_stat);
+
+              auth_err = *auth_stat;
+              if (auth_err == RETROK)
+                {
+                  request_set_header (req, "Authorization", value, rel_value);
+
+                  if (BEGINS_WITH (www_authenticate, "NTLM"))
+                    ntlm_seen = true;
+                  else if (!u->user && BEGINS_WITH (www_authenticate, "Basic"))
+                    {
+                      /* Need to register this host as using basic auth,
+                       * so we automatically send creds next time. */
+                      register_basic_auth_host (u->host);
+                    }
+
+                  xfree (pth);
+                  xfree_null (message);
+                  resp_free (resp);
+                  xfree (head);
+                  xfree (auth_stat);
+                  goto retry_with_auth;
+                }
+              else
+                {
+                  /* Creating the Authorization header went wrong */
+                }
+            }
+          else
+            {
+              /* We already did Basic auth, and it failed. Gotta
+               * give up. */
+            }
+        }
+      request_free (req);
+      xfree_null (message);
+      resp_free (resp);
+      xfree (head);
+      if (auth_err == RETROK)
+        return AUTHFAILED;
+      else
+        return auth_err;
+    }
+  else /* statcode != HTTP_STATUS_UNAUTHORIZED */
+    {
+      /* Kludge: if NTLM is used, mark the TCP connection as authorized. */
+      if (ntlm_seen)
+        pconn.authorized = true;
     }
 
+  /* Determine the local filename if needed. Notice that if -O is used
+   * hstat.local_file is set by http_loop to the argument of -O. */
+  if (!hs->local_file)
+    {
+      char *local_file = NULL;
+
+      /* Honor Content-Disposition whether possible. */
+      if (!opt.content_disposition
+          || !resp_header_copy (resp, "Content-Disposition",
+                                hdrval, sizeof (hdrval))
+          || !parse_content_disposition (hdrval, &local_file))
+        {
+          /* The Content-Disposition header is missing or broken.
+           * Choose unique file name according to given URL. */
+          hs->local_file = url_file_name (u, NULL);
+        }
+      else
+        {
+          DEBUGP (("Parsed filename from Content-Disposition: %s\n",
+                  local_file));
+          hs->local_file = url_file_name (u, local_file);
+        }
+    }
+
+  /* TODO: perform this check only once. */
+  if (!hs->existence_checked && file_exists_p (hs->local_file))
+    {
+      if (opt.noclobber && !opt.output_document)
+        {
+          /* If opt.noclobber is turned on and file already exists, do not
+             retrieve the file. But if the output_document was given, then this
+             test was already done and the file didn't exist. Hence the !opt.output_document */
+          get_file_flags (hs->local_file, dt);
+          request_free (req);
+          resp_free (resp);
+          xfree (head);
+          xfree_null (message);
+          return RETRUNNEEDED;
+        }
+      else if (!ALLOW_CLOBBER)
+        {
+          char *unique = unique_name (hs->local_file, true);
+          if (unique != hs->local_file)
+            xfree (hs->local_file);
+          hs->local_file = unique;
+        }
+    }
+  hs->existence_checked = true;
+
   /* Support timestamping */
   /* TODO: move this code out of gethttp. */
   if (opt.timestamping && !hs->timestamp_checked)
     {
       size_t filename_len = strlen (hs->local_file);
-      char *filename_plus_orig_suffix = alloca (filename_len + sizeof (".orig"));
+      char *filename_plus_orig_suffix = alloca (filename_len + sizeof (ORIG_SFX));
       bool local_dot_orig_file_exists = false;
       char *local_filename = NULL;
       struct_stat st;
@@ -1668,15 +2535,15 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
              --hniksic */
           memcpy (filename_plus_orig_suffix, hs->local_file, filename_len);
           memcpy (filename_plus_orig_suffix + filename_len,
-                  ".orig", sizeof (".orig"));
+                  ORIG_SFX, sizeof (ORIG_SFX));
 
           /* Try to stat() the .orig file. */
           if (stat (filename_plus_orig_suffix, &st) == 0)
             {
-              local_dot_orig_file_exists = 1;
+              local_dot_orig_file_exists = true;
               local_filename = filename_plus_orig_suffix;
             }
-        }      
+        }
 
       if (!local_dot_orig_file_exists)
         /* Couldn't stat() <file>.orig, so try to stat() <file>. */
@@ -1699,102 +2566,6 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
         }
     }
 
-  if (!opt.ignore_length
-      && resp_header_copy (resp, "Content-Length", hdrval, sizeof (hdrval)))
-    {
-      wgint parsed;
-      errno = 0;
-      parsed = str_to_wgint (hdrval, NULL, 10);
-      if (parsed == WGINT_MAX && errno == ERANGE)
-        /* Out of range.
-           #### If Content-Length is out of range, it most likely
-           means that the file is larger than 2G and that we're
-           compiled without LFS.  In that case we should probably
-           refuse to even attempt to download the file.  */
-        contlen = -1;
-      else
-        contlen = parsed;
-    }
-
-  /* Check for keep-alive related responses. */
-  if (!inhibit_keep_alive && contlen != -1)
-    {
-      if (resp_header_copy (resp, "Keep-Alive", NULL, 0))
-        keep_alive = true;
-      else if (resp_header_copy (resp, "Connection", hdrval, sizeof (hdrval)))
-        {
-          if (0 == strcasecmp (hdrval, "Keep-Alive"))
-            keep_alive = true;
-        }
-    }
-  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)
-    {
-      /* Authorization is required.  */
-      if (keep_alive && !head_only && skip_short_body (sock, contlen))
-        CLOSE_FINISH (sock);
-      else
-        CLOSE_INVALIDATE (sock);
-      pconn.authorized = false;
-      if (!auth_finished && (user && passwd))
-        {
-          /* IIS sends multiple copies of WWW-Authenticate, one with
-             the value "negotiate", and other(s) with data.  Loop over
-             all the occurrences and pick the one we recognize.  */
-          int wapos;
-          const char *wabeg, *waend;
-          char *www_authenticate = NULL;
-          for (wapos = 0;
-               (wapos = resp_header_locate (resp, "WWW-Authenticate", wapos,
-                                            &wabeg, &waend)) != -1;
-               ++wapos)
-            if (known_authentication_scheme_p (wabeg, waend))
-              {
-                BOUNDED_TO_ALLOCA (wabeg, waend, www_authenticate);
-                break;
-              }
-
-          if (!www_authenticate)
-            /* If the authentication header is missing or
-               unrecognized, there's no sense in retrying.  */
-            logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
-          else if (BEGINS_WITH (www_authenticate, "Basic"))
-            /* If the authentication scheme is "Basic", which we send
-               by default, there's no sense in retrying either.  (This
-               should be changed when we stop sending "Basic" data by
-               default.)  */
-            ;
-          else
-            {
-              char *pth;
-              pth = url_full_path (u);
-              request_set_header (req, "Authorization",
-                                  create_authorization_line (www_authenticate,
-                                                             user, passwd,
-                                                             request_method (req),
-                                                             pth,
-                                                             &auth_finished),
-                                  rel_value);
-              if (BEGINS_WITH (www_authenticate, "NTLM"))
-                ntlm_seen = true;
-              xfree (pth);
-              goto retry_with_auth;
-            }
-        }
-      logputs (LOG_NOTQUIET, _("Authorization failed.\n"));
-      request_free (req);
-      return AUTHFAILED;
-    }
-  else /* statcode != HTTP_STATUS_UNAUTHORIZED */
-    {
-      /* Kludge: if NTLM is used, mark the TCP connection as authorized. */
-      if (ntlm_seen)
-        pconn.authorized = true;
-    }
   request_free (req);
 
   hs->statcode = statcode;
@@ -1812,38 +2583,35 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
       char *tmp = strchr (type, ';');
       if (tmp)
         {
-          while (tmp > type && ISSPACE (tmp[-1]))
+          /* sXXXav: only needed if IRI support is enabled */
+          char *tmp2 = tmp + 1;
+
+          while (tmp > type && c_isspace (tmp[-1]))
             --tmp;
           *tmp = '\0';
+
+          /* Try to get remote encoding if needed */
+          if (opt.enable_iri && !opt.encoding_remote)
+            {
+              tmp = parse_charset (tmp2);
+              if (tmp)
+                set_content_encoding (iri, tmp);
+              xfree_null(tmp);
+            }
         }
     }
   hs->newloc = resp_header_strdup (resp, "Location");
   hs->remote_time = resp_header_strdup (resp, "Last-Modified");
 
-  /* Handle (possibly multiple instances of) the Set-Cookie header. */
-  if (opt.cookies)
-    {
-      int scpos;
-      const char *scbeg, *scend;
-      /* The jar should have been created by now. */
-      assert (wget_cookie_jar != NULL);
-      for (scpos = 0;
-           (scpos = resp_header_locate (resp, "Set-Cookie", scpos,
-                                        &scbeg, &scend)) != -1;
-           ++scpos)
-        {
-          char *set_cookie; BOUNDED_TO_ALLOCA (scbeg, scend, set_cookie);
-          cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port,
-                                    u->path, set_cookie);
-        }
-    }
-
   if (resp_header_copy (resp, "Content-Range", hdrval, sizeof (hdrval)))
     {
       wgint 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;
+        {
+          contrange = first_byte_pos;
+          contlen = last_byte_pos - first_byte_pos + 1;
+        }
     }
   resp_free (resp);
 
@@ -1851,6 +2619,22 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
   if (H_20X (statcode))
     *dt |= RETROKF;
 
+  if (statcode == HTTP_STATUS_NO_CONTENT)
+    {
+      /* 204 response has no body (RFC 2616, 4.3) */
+
+      /* In case the caller cares to look...  */
+      hs->len = 0;
+      hs->res = 0;
+      hs->restval = 0;
+
+      CLOSE_FINISH (sock);
+      xfree_null (type);
+      xfree (head);
+
+      return RETRFINISHED;
+    }
+
   /* Return if redirected.  */
   if (H_REDIRECTED (statcode) || statcode == HTTP_STATUS_MULTIPLE_CHOICES)
     {
@@ -1867,11 +2651,74 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
                      _("Location: %s%s\n"),
                      hs->newloc ? escnonprint_uri (hs->newloc) : _("unspecified"),
                      hs->newloc ? _(" [following]") : "");
-          if (keep_alive && !head_only && skip_short_body (sock, contlen))
-            CLOSE_FINISH (sock);
+          /* In case the caller cares to look...  */
+          hs->len = 0;
+          hs->res = 0;
+          hs->restval = 0;
+
+          /* Normally we are not interested in the response body of a redirect.
+             But if we are writing a WARC file we are: we like to keep everyting.  */
+          if (warc_enabled)
+            {
+              int _err = read_response_body (hs, sock, NULL, contlen, 0,
+                                            chunked_transfer_encoding,
+                                            u->url, warc_timestamp_str,
+                                            warc_request_uuid, warc_ip, type,
+                                            statcode, head);
+
+              if (_err != RETRFINISHED || hs->res < 0)
+                {
+                  CLOSE_INVALIDATE (sock);
+                  xfree_null (type);
+                  xfree (head);
+                  return _err;
+                }
+              else
+                CLOSE_FINISH (sock);
+            }
           else
-            CLOSE_INVALIDATE (sock);
+            {
+              /* Since WARC is disabled, we are not interested in the response body.  */
+              if (keep_alive && !head_only
+                  && skip_short_body (sock, contlen, chunked_transfer_encoding))
+                CLOSE_FINISH (sock);
+              else
+                CLOSE_INVALIDATE (sock);
+            }
+
           xfree_null (type);
+          xfree (head);
+          /* From RFC2616: The status codes 303 and 307 have
+             been added for servers that wish to make unambiguously
+             clear which kind of reaction is expected of the client.
+
+             A 307 should be redirected using the same method,
+             in other words, a POST should be preserved and not
+             converted to a GET in that case.
+
+             With strict adherence to RFC2616, POST requests are not
+             converted to a GET request on 301 Permanent Redirect
+             or 302 Temporary Redirect.
+
+             A switch may be provided later based on the HTTPbis draft
+             that allows clients to convert POST requests to GET
+             requests on 301 and 302 response codes. */
+          switch (statcode)
+            {
+            case HTTP_STATUS_TEMPORARY_REDIRECT:
+              return NEWLOCATION_KEEP_POST;
+            case HTTP_STATUS_MOVED_PERMANENTLY:
+              if (opt.method && strcasecmp (opt.method, "post") != 0)
+                return NEWLOCATION_KEEP_POST;
+              break;
+            case HTTP_STATUS_MOVED_TEMPORARILY:
+              if (opt.method && strcasecmp (opt.method, "post") != 0)
+                return NEWLOCATION_KEEP_POST;
+              break;
+            default:
+              return NEWLOCATION;
+            }
           return NEWLOCATION;
         }
     }
@@ -1886,42 +2733,36 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
   else
     *dt &= ~TEXTHTML;
 
-  if (opt.html_extension && (*dt & TEXTHTML))
-    /* -E / --html-extension / html_extension = on was specified, and this is a
-       text/html file.  If some case-insensitive variation on ".htm[l]" isn't
-       already the file's suffix, tack on ".html". */
-    {
-      char *last_period_in_local_filename = strrchr (hs->local_file, '.');
-
-      if (last_period_in_local_filename == NULL
-          || !(0 == strcasecmp (last_period_in_local_filename, ".htm")
-               || 0 == strcasecmp (last_period_in_local_filename, ".html")))
-        {
-          int local_filename_len = strlen (hs->local_file);
-          /* Resize the local file, allowing for ".html" preceded by
-             optional ".NUMBER".  */
-          hs->local_file = xrealloc (hs->local_file,
-                                     local_filename_len + 24 + sizeof (".html"));
-          strcpy(hs->local_file + local_filename_len, ".html");
-          /* If clobbering is not allowed and the file, as named,
-             exists, tack on ".NUMBER.html" instead. */
-          if (!ALLOW_CLOBBER)
-            {
-              int ext_num = 1;
-              do
-                sprintf (hs->local_file + local_filename_len,
-                         ".%d.html", ext_num++);
-              while (file_exists_p (hs->local_file));
-            }
-          *dt |= ADDED_HTML_EXTENSION;
+  if (type &&
+      0 == strncasecmp (type, TEXTCSS_S, strlen (TEXTCSS_S)))
+    *dt |= TEXTCSS;
+  else
+    *dt &= ~TEXTCSS;
+
+  if (opt.adjust_extension)
+    {
+      if (*dt & TEXTHTML)
+        /* -E / --adjust-extension / adjust_extension = on was specified,
+           and this is a text/html file.  If some case-insensitive
+           variation on ".htm[l]" isn't already the file's suffix,
+           tack on ".html". */
+        {
+          ensure_extension (hs, ".html", dt);
+        }
+      else if (*dt & TEXTCSS)
+        {
+          ensure_extension (hs, ".css", dt);
         }
     }
 
-  if (statcode == HTTP_STATUS_RANGE_NOT_SATISFIABLE)
+  if (statcode == HTTP_STATUS_RANGE_NOT_SATISFIABLE
+      || (!opt.timestamping && hs->restval > 0 && statcode == HTTP_STATUS_OK
+          && contrange == 0 && contlen >= 0 && hs->restval >= contlen))
     {
       /* 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.  */
+         after the end of file and the server response with 416
+         (or 200 with a <= Content-Length.  */
       logputs (LOG_VERBOSE, _("\
 \n    The file is already fully retrieved; nothing to do.\n\n"));
       /* In case the caller inspects. */
@@ -1932,6 +2773,7 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
       xfree_null (type);
       CLOSE_INVALIDATE (sock);        /* would be CLOSE_FINISH, but there
                                    might be more bytes in the body. */
+      xfree (head);
       return RETRUNNEEDED;
     }
   if ((contrange != 0 && contrange != hs->restval)
@@ -1941,9 +2783,13 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
          Bail out.  */
       xfree_null (type);
       CLOSE_INVALIDATE (sock);
+      xfree (head);
       return RANGEERR;
     }
-  hs->contlen = contlen + contrange;
+  if (contlen == -1)
+    hs->contlen = -1;
+  else
+    hs->contlen = contlen + contrange;
 
   if (opt.verbose)
     {
@@ -1958,13 +2804,13 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
               logputs (LOG_VERBOSE, number_to_static_string (contlen + contrange));
               if (contlen + contrange >= 1024)
                 logprintf (LOG_VERBOSE, " (%s)",
-                           human_readable (contlen + contrange));
+                           human_readable (contlen + contrange, 10, 1));
               if (contrange)
                 {
                   if (contlen >= 1024)
                     logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
                                number_to_static_string (contlen),
-                               human_readable (contlen));
+                               human_readable (contlen, 10, 1));
                   else
                     logprintf (LOG_VERBOSE, _(", %s remaining"),
                                number_to_static_string (contlen));
@@ -1974,43 +2820,73 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
             logputs (LOG_VERBOSE,
                      opt.ignore_length ? _("ignored") : _("unspecified"));
           if (type)
-            logprintf (LOG_VERBOSE, " [%s]\n", escnonprint (type));
+            logprintf (LOG_VERBOSE, " [%s]\n", quotearg_style (escape_quoting_style, type));
           else
             logputs (LOG_VERBOSE, "\n");
         }
     }
-  xfree_null (type);
-  type = NULL;                        /* We don't need it any more.  */
 
   /* Return if we have no intention of further downloading.  */
-  if (!(*dt & RETROKF) || head_only)
+  if ((!(*dt & RETROKF) && !opt.content_on_error) || head_only)
     {
       /* In case the caller cares to look...  */
       hs->len = 0;
       hs->res = 0;
-      xfree_null (type);
-      if (head_only)
-        /* Pre-1.10 Wget used CLOSE_INVALIDATE here.  Now we trust the
-           servers not to send body in response to a HEAD request, and
-           those that do will likely be caught by test_socket_open.
-           If not, they can be worked around using
-           `--no-http-keep-alive'.  */
-        CLOSE_FINISH (sock);
-      else if (keep_alive && skip_short_body (sock, contlen))
-        /* Successfully skipped the body; also keep using the socket. */
-        CLOSE_FINISH (sock);
+      hs->restval = 0;
+
+      /* Normally we are not interested in the response body of a error responses.
+         But if we are writing a WARC file we are: we like to keep everyting.  */
+      if (warc_enabled)
+        {
+          int _err = read_response_body (hs, sock, NULL, contlen, 0,
+                                        chunked_transfer_encoding,
+                                        u->url, warc_timestamp_str,
+                                        warc_request_uuid, warc_ip, type,
+                                        statcode, head);
+
+          if (_err != RETRFINISHED || hs->res < 0)
+            {
+              CLOSE_INVALIDATE (sock);
+              xfree (head);
+              xfree_null (type);
+              return _err;
+            }
+          else
+            CLOSE_FINISH (sock);
+        }
       else
-        CLOSE_INVALIDATE (sock);
+        {
+          /* Since WARC is disabled, we are not interested in the response body.  */
+          if (head_only)
+            /* Pre-1.10 Wget used CLOSE_INVALIDATE here.  Now we trust the
+               servers not to send body in response to a HEAD request, and
+               those that do will likely be caught by test_socket_open.
+               If not, they can be worked around using
+               `--no-http-keep-alive'.  */
+            CLOSE_FINISH (sock);
+          else if (keep_alive
+                   && skip_short_body (sock, contlen, chunked_transfer_encoding))
+            /* Successfully skipped the body; also keep using the socket. */
+            CLOSE_FINISH (sock);
+          else
+            CLOSE_INVALIDATE (sock);
+        }
+
+      xfree (head);
+      xfree_null (type);
       return RETRFINISHED;
     }
 
-  /* Print fetch message, if opt.verbose.  */
-  if (opt.verbose)
-    {
-      logprintf (LOG_NOTQUIET, _("Saving to: `%s'\n"), 
-                 HYPHENP (hs->local_file) ? "STDOUT" : hs->local_file);
-    }
-    
+/* 2005-06-17 SMS.
+   For VMS, define common fopen() optional arguments.
+*/
+#ifdef __VMS
+# define FOPEN_OPT_ARGS "fop=sqo", "acc", acc_cb, &open_id
+# define FOPEN_BIN_FLAG 3
+#else /* def __VMS */
+# define FOPEN_BIN_FLAG true
+#endif /* def __VMS [else] */
+
   /* Open the local file.  */
   if (!output_stream)
     {
@@ -2018,12 +2894,44 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
       if (opt.backups)
         rotate_backups (hs->local_file);
       if (hs->restval)
-        fp = fopen (hs->local_file, "ab");
-      else if (ALLOW_CLOBBER)
-        fp = fopen (hs->local_file, "wb");
+        {
+#ifdef __VMS
+          int open_id;
+
+          open_id = 21;
+          fp = fopen (hs->local_file, "ab", FOPEN_OPT_ARGS);
+#else /* def __VMS */
+          fp = fopen (hs->local_file, "ab");
+#endif /* def __VMS [else] */
+        }
+      else if (ALLOW_CLOBBER || count > 0)
+        {
+         if (opt.unlink && file_exists_p (hs->local_file))
+           {
+             int res = unlink (hs->local_file);
+             if (res < 0)
+               {
+                 logprintf (LOG_NOTQUIET, "%s: %s\n", hs->local_file,
+                            strerror (errno));
+                 CLOSE_INVALIDATE (sock);
+                 xfree (head);
+      xfree_null (type);
+                 return UNLINKERR;
+               }
+           }
+
+#ifdef __VMS
+          int open_id;
+
+          open_id = 22;
+          fp = fopen (hs->local_file, "wb", FOPEN_OPT_ARGS);
+#else /* def __VMS */
+          fp = fopen (hs->local_file, "wb");
+#endif /* def __VMS [else] */
+        }
       else
         {
-          fp = fopen_excl (hs->local_file, true);
+          fp = fopen_excl (hs->local_file, FOPEN_BIN_FLAG);
           if (!fp && errno == EEXIST)
             {
               /* We cannot just invent a new name and use it (which is
@@ -2034,6 +2942,8 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
                          _("%s has sprung into existence.\n"),
                          hs->local_file);
               CLOSE_INVALIDATE (sock);
+              xfree (head);
+              xfree_null (type);
               return FOPEN_EXCL_ERR;
             }
         }
@@ -2041,76 +2951,74 @@ File `%s' already there; not retrieving.\n\n"), hs->local_file);
         {
           logprintf (LOG_NOTQUIET, "%s: %s\n", hs->local_file, strerror (errno));
           CLOSE_INVALIDATE (sock);
+          xfree (head);
+          xfree_null (type);
           return FOPENERR;
         }
     }
   else
     fp = output_stream;
 
-  /* This confuses the timestamping code that checks for file size.
-     #### The timestamping code should be smarter about file size.  */
-  if (opt.save_headers && hs->restval == 0)
-    fwrite (head, 1, strlen (head), fp);
+  /* Print fetch message, if opt.verbose.  */
+      logprintf (LOG_VERBOSE, _("Saving to: %s\n"),
+                 HYPHENP (hs->local_file) ? quote ("STDOUT") : quote (hs->local_file));
+
+
+  err = read_response_body (hs, sock, fp, contlen, contrange,
+                            chunked_transfer_encoding,
+                            u->url, warc_timestamp_str,
+                            warc_request_uuid, warc_ip, type,
+                            statcode, head);
 
   /* Now we no longer need to store the response header. */
   xfree (head);
-
-  /* Download the request body.  */
-  flags = 0;
-  if (contlen != -1)
-    /* If content-length is present, read that much; otherwise, read
-       until EOF.  The HTTP spec doesn't require the server to
-       actually close the connection when it's done sending data. */
-    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);
+  xfree_null (type);
 
   if (hs->res >= 0)
     CLOSE_FINISH (sock);
   else
-    {
-      if (hs->res < 0)
-        hs->rderrmsg = xstrdup (fd_errstr (sock));
-      CLOSE_INVALIDATE (sock);
-    }
+    CLOSE_INVALIDATE (sock);
 
   if (!output_stream)
     fclose (fp);
-  if (hs->res == -2)
-    return FWRITEERR;
-  return RETRFINISHED;
+
+  return err;
 }
 
 /* The genuine HTTP loop!  This is the part where the retrieval is
    retried, and retried, and retried, and...  */
 uerr_t
-http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
-           int *dt, struct url *proxy)
+http_loop (struct url *u, struct url *original_url, char **newloc,
+           char **local_file, const char *referer, int *dt, struct url *proxy,
+           struct iri *iri)
 {
   int count;
-  bool got_head = false;         /* used for time-stamping */
+  bool got_head = false;         /* used for time-stamping and filename detection */
+  bool time_came_from_head = false;
+  bool got_name = false;
   char *tms;
   const char *tmrate;
-  uerr_t err;
+  uerr_t err, ret = TRYLIMEXC;
   time_t tmr = -1;               /* remote time-stamp */
-  wgint local_size = 0;          /* the size of the local file */
   struct http_stat hstat;        /* HTTP status */
   struct_stat st;
+  bool send_head_first = true;
+  char *file_name;
+  bool force_full_retrieve = false;
+
+
+  /* If we are writing to a WARC file: always retrieve the whole file. */
+  if (opt.warc_filename != NULL)
+    force_full_retrieve = true;
+
 
   /* Assert that no value for *LOCAL_FILE was passed. */
   assert (local_file == NULL || *local_file == NULL);
-  
+
   /* Set LOCAL_FILE parameter. */
   if (local_file && opt.output_document)
     *local_file = HYPHENP (opt.output_document) ? NULL : xstrdup (opt.output_document);
-  
+
   /* Reset NEWLOC parameter. */
   *newloc = NULL;
 
@@ -2118,7 +3026,7 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
      here so that we don't go through the hoops if we're just using
      FTP or whatever. */
   if (opt.cookies)
-    load_cookies();
+    load_cookies ();
 
   /* Warn on (likely bogus) wildcard usage in HTTP. */
   if (opt.ftp_glob && has_wildcards_p (u->path))
@@ -2129,42 +3037,85 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   hstat.referer = referer;
 
   if (opt.output_document)
-    hstat.local_file = xstrdup (opt.output_document);
+    {
+      hstat.local_file = xstrdup (opt.output_document);
+      got_name = true;
+    }
+  else if (!opt.content_disposition)
+    {
+      hstat.local_file =
+        url_file_name (opt.trustservernames ? u : original_url, NULL);
+      got_name = true;
+    }
+
+  if (got_name && file_exists_p (hstat.local_file) && opt.noclobber && !opt.output_document)
+    {
+      /* If opt.noclobber is turned on and file already exists, do not
+         retrieve the file. But if the output_document was given, then this
+         test was already done and the file didn't exist. Hence the !opt.output_document */
+      get_file_flags (hstat.local_file, dt);
+      ret = RETROK;
+      goto exit;
+    }
 
   /* Reset the counter. */
   count = 0;
-  
+
   /* Reset the document type. */
   *dt = 0;
-  
+
+  /* Skip preliminary HEAD request if we're not in spider mode.  */
+  if (!opt.spider)
+    send_head_first = false;
+
+  /* Send preliminary HEAD request if --content-disposition and -c are used
+     together.  */
+  if (opt.content_disposition && opt.always_rest)
+    send_head_first = true;
+
+  /* Send preliminary HEAD request if -N is given and we have an existing
+   * destination file. */
+  if (!opt.output_document)
+      file_name = url_file_name (opt.trustservernames ? u : original_url, NULL);
+  else
+    file_name = xstrdup (opt.output_document);
+  if (opt.timestamping && (file_exists_p (file_name)
+                           || opt.content_disposition))
+    send_head_first = true;
+  xfree (file_name);
+
   /* THE loop */
   do
     {
       /* Increment the pass counter.  */
       ++count;
       sleep_between_retrievals (count);
-      
+
       /* Get the current time string.  */
-      tms = time_str (NULL);
-      
+      tms = datetime_str (time (NULL));
+
+      if (opt.spider && !got_head)
+        logprintf (LOG_VERBOSE, _("\
+Spider mode enabled. Check if remote file exists.\n"));
+
       /* Print fetch message, if opt.verbose.  */
       if (opt.verbose)
         {
-          char *hurl = url_string (u, true);
-          
-          if (count > 1) 
+          char *hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
+
+          if (count > 1)
             {
               char tmp[256];
               sprintf (tmp, _("(try:%2d)"), count);
               logprintf (LOG_NOTQUIET, "--%s--  %s  %s\n",
                          tms, tmp, hurl);
             }
-          else 
+          else
             {
               logprintf (LOG_NOTQUIET, "--%s--  %s\n",
                          tms, hurl);
             }
-          
+
 #ifdef WINDOWS
           ws_changetitle (hurl);
 #endif
@@ -2174,13 +3125,18 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
       /* Default document type is empty.  However, if spider mode is
          on or time-stamping is employed, HEAD_ONLY commands is
          encoded within *dt.  */
-      if (opt.spider || (opt.timestamping && !got_head))
+      if (send_head_first && !got_head)
         *dt |= HEAD_ONLY;
       else
         *dt &= ~HEAD_ONLY;
 
       /* Decide whether or not to restart.  */
-      if (opt.always_rest
+      if (force_full_retrieve)
+        hstat.restval = hstat.len;
+      else if (opt.start_pos >= 0)
+        hstat.restval = opt.start_pos;
+      else if (opt.always_rest
+          && got_name
           && stat (hstat.local_file, &st) == 0
           && S_ISREG (st.st_mode))
         /* When -c is used, continue from on-disk size.  (Can't use
@@ -2200,22 +3156,21 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
               we require a fresh get.
            b) caching is explicitly inhibited. */
       if ((proxy && count > 1)        /* a */
-          || !opt.allow_cache         /* b */
-          )
+          || !opt.allow_cache)        /* b */
         *dt |= SEND_NOCACHE;
       else
         *dt &= ~SEND_NOCACHE;
 
       /* Try fetching the document, or at least its head.  */
-      err = gethttp (u, &hstat, dt, proxy);
+      err = gethttp (u, &hstat, dt, proxy, iri, count);
 
       /* Time?  */
-      tms = time_str (NULL);
-      
+      tms = datetime_str (time (NULL));
+
       /* Get the new location (with or without the redirection).  */
       if (hstat.newloc)
         *newloc = xstrdup (hstat.newloc);
-      
+
       switch (err)
         {
         case HERR: case HEOF: case CONSOCKERR: case CONCLOSED:
@@ -2224,42 +3179,73 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
           /* Non-fatal errors continue executing the loop, which will
              bring them to "while" statement at the end, to judge
              whether the number of tries was exceeded.  */
-          /* free_hstat (&hstat); */
           printwhat (count, opt.ntry);
           continue;
-        case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED: 
-        case SSLINITFAILED: case CONTNOTSUPPORTED:
-          /* Fatal errors just return from the function.  */
-          free_hstat (&hstat);
-          return err;
         case FWRITEERR: case FOPENERR:
           /* Another fatal error.  */
           logputs (LOG_VERBOSE, "\n");
-          logprintf (LOG_NOTQUIET, _("Cannot write to `%s' (%s).\n"),
-                     hstat.local_file, strerror (errno));
-          free_hstat (&hstat);
-          return err;
+          logprintf (LOG_NOTQUIET, _("Cannot write to %s (%s).\n"),
+                     quote (hstat.local_file), strerror (errno));
+        case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case SSLINITFAILED:
+        case CONTNOTSUPPORTED: case VERIFCERTERR: case FILEBADFILE:
+        case UNKNOWNATTR:
+          /* Fatal errors just return from the function.  */
+          ret = err;
+          goto exit;
+        case ATTRMISSING:
+          /* A missing attribute in a Header is a fatal Protocol error. */
+          logputs (LOG_VERBOSE, "\n");
+          logprintf (LOG_NOTQUIET, _("Required attribute missing from Header received.\n"));
+          ret = err;
+          goto exit;
+        case AUTHFAILED:
+          logputs (LOG_VERBOSE, "\n");
+          logprintf (LOG_NOTQUIET, _("Username/Password Authentication Failed.\n"));
+          ret = err;
+          goto exit;
+        case WARC_ERR:
+          /* A fatal WARC error. */
+          logputs (LOG_VERBOSE, "\n");
+          logprintf (LOG_NOTQUIET, _("Cannot write to WARC file.\n"));
+          ret = err;
+          goto exit;
+        case WARC_TMP_FOPENERR: case WARC_TMP_FWRITEERR:
+          /* A fatal WARC error. */
+          logputs (LOG_VERBOSE, "\n");
+          logprintf (LOG_NOTQUIET, _("Cannot write to temporary WARC file.\n"));
+          ret = err;
+          goto exit;
         case CONSSLERR:
           /* Another fatal error.  */
           logprintf (LOG_NOTQUIET, _("Unable to establish SSL connection.\n"));
-          free_hstat (&hstat);
-          return err;
+          ret = err;
+          goto exit;
+        case UNLINKERR:
+          /* Another fatal error.  */
+          logputs (LOG_VERBOSE, "\n");
+          logprintf (LOG_NOTQUIET, _("Cannot unlink %s (%s).\n"),
+                     quote (hstat.local_file), strerror (errno));
+          ret = err;
+          goto exit;
         case NEWLOCATION:
+        case NEWLOCATION_KEEP_POST:
           /* Return the new location to the caller.  */
           if (!*newloc)
             {
               logprintf (LOG_NOTQUIET,
                          _("ERROR: Redirection (%d) without location.\n"),
                          hstat.statcode);
-              free_hstat (&hstat);
-              return WRONGCODE;
+              ret = WRONGCODE;
             }
-          free_hstat (&hstat);
-          return NEWLOCATION;
+          else
+            {
+              ret = err;
+            }
+          goto exit;
         case RETRUNNEEDED:
           /* The file was already fully retrieved. */
-          free_hstat (&hstat);
-          return RETROK;
+          ret = RETROK;
+          goto exit;
         case RETRFINISHED:
           /* Deal with you later.  */
           break;
@@ -2267,26 +3253,54 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
           /* All possibilities should have been exhausted.  */
           abort ();
         }
-      
+
       if (!(*dt & RETROKF))
         {
+          char *hurl = NULL;
           if (!opt.verbose)
             {
               /* #### Ugly ugly ugly! */
-              char *hurl = url_string (u, true);
+              hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
               logprintf (LOG_NONVERBOSE, "%s:\n", hurl);
-              xfree (hurl);
             }
-          logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
-                     tms, hstat.statcode, escnonprint (hstat.error));
+
+          /* Fall back to GET if HEAD fails with a 500 or 501 error code. */
+          if (*dt & HEAD_ONLY
+              && (hstat.statcode == 500 || hstat.statcode == 501))
+            {
+              got_head = true;
+              continue;
+            }
+          /* Maybe we should always keep track of broken links, not just in
+           * spider mode.
+           * Don't log error if it was UTF-8 encoded because we will try
+           * once unencoded. */
+          else if (opt.spider && !iri->utf8_encode)
+            {
+              /* #### Again: ugly ugly ugly! */
+              if (!hurl)
+                hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
+              nonexisting_url (hurl);
+              logprintf (LOG_NOTQUIET, _("\
+Remote file does not exist -- broken link!!!\n"));
+            }
+          else
+            {
+              logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
+                         tms, hstat.statcode,
+                         quotearg_style (escape_quoting_style, hstat.error));
+            }
           logputs (LOG_VERBOSE, "\n");
-          free_hstat (&hstat);
-          return WRONGCODE;
+          ret = WRONGCODE;
+          xfree_null (hurl);
+          goto exit;
         }
 
       /* Did we get the time-stamp? */
       if (!got_head)
         {
+          got_head = true;    /* no more time-stamping */
+
           if (opt.timestamping && !hstat.remote_time)
             {
               logputs (LOG_NOTQUIET, _("\
@@ -2299,79 +3313,136 @@ Last-modified header missing -- time-stamps turned off.\n"));
               if (tmr == (time_t) (-1))
                 logputs (LOG_VERBOSE, _("\
 Last-modified header invalid -- time-stamp ignored.\n"));
+              if (*dt & HEAD_ONLY)
+                time_came_from_head = true;
             }
-        }
 
-      /* The time-stamping section.  */
-      if (opt.timestamping && !got_head)
-        {
-          got_head = true;    /* no more time-stamping */
-          *dt &= ~HEAD_ONLY;
-          count = 0;          /* the retrieve count for HEAD is reset */
-          
-          if (hstat.remote_time && tmr != (time_t) (-1))
+          if (send_head_first)
             {
-              /* Now time-stamping can be used validly.  Time-stamping
-                 means that if the sizes of the local and remote file
-                 match, and local file is newer than the remote file,
-                 it will not be retrieved.  Otherwise, the normal
-                 download procedure is resumed.  */
-              if (hstat.orig_file_tstamp >= tmr)
+              /* The time-stamping section.  */
+              if (opt.timestamping)
                 {
-                  if (hstat.contlen == -1 || hstat.orig_file_size == hstat.contlen)
+                  if (hstat.orig_file_name) /* Perform the following
+                                               checks only if the file
+                                               we're supposed to
+                                               download already exists.  */
                     {
-                      logprintf (LOG_VERBOSE, _("\
-Server file no newer than local file `%s' -- not retrieving.\n\n"),
-                                 hstat.orig_file_name);
-                      free_hstat (&hstat);
-                      return RETROK;
+                      if (hstat.remote_time &&
+                          tmr != (time_t) (-1))
+                        {
+                          /* Now time-stamping can be used validly.
+                             Time-stamping means that if the sizes of
+                             the local and remote file match, and local
+                             file is newer than the remote file, it will
+                             not be retrieved.  Otherwise, the normal
+                             download procedure is resumed.  */
+                          if (hstat.orig_file_tstamp >= tmr)
+                            {
+                              if (hstat.contlen == -1
+                                  || hstat.orig_file_size == hstat.contlen)
+                                {
+                                  logprintf (LOG_VERBOSE, _("\
+Server file no newer than local file %s -- not retrieving.\n\n"),
+                                             quote (hstat.orig_file_name));
+                                  ret = RETROK;
+                                  goto exit;
+                                }
+                              else
+                                {
+                                  logprintf (LOG_VERBOSE, _("\
+The sizes do not match (local %s) -- retrieving.\n"),
+                                             number_to_static_string (hstat.orig_file_size));
+                                }
+                            }
+                          else
+                            {
+                              force_full_retrieve = true;
+                              logputs (LOG_VERBOSE,
+                                       _("Remote file is newer, retrieving.\n"));
+                            }
+
+                          logputs (LOG_VERBOSE, "\n");
+                        }
+                    }
+
+                  /* free_hstat (&hstat); */
+                  hstat.timestamp_checked = true;
+                }
+
+              if (opt.spider)
+                {
+                  bool finished = true;
+                  if (opt.recursive)
+                    {
+                      if (*dt & TEXTHTML)
+                        {
+                          logputs (LOG_VERBOSE, _("\
+Remote file exists and could contain links to other resources -- retrieving.\n\n"));
+                          finished = false;
+                        }
+                      else
+                        {
+                          logprintf (LOG_VERBOSE, _("\
+Remote file exists but does not contain any link -- not retrieving.\n\n"));
+                          ret = RETROK; /* RETRUNNEEDED is not for caller. */
+                        }
                     }
                   else
                     {
-                      logprintf (LOG_VERBOSE, _("\
-The sizes do not match (local %s) -- retrieving.\n"),
-                                 number_to_static_string (local_size));
+                      if (*dt & TEXTHTML)
+                        {
+                          logprintf (LOG_VERBOSE, _("\
+Remote file exists and could contain further links,\n\
+but recursion is disabled -- not retrieving.\n\n"));
+                        }
+                      else
+                        {
+                          logprintf (LOG_VERBOSE, _("\
+Remote file exists.\n\n"));
+                        }
+                      ret = RETROK; /* RETRUNNEEDED is not for caller. */
+                    }
+
+                  if (finished)
+                    {
+                      logprintf (LOG_NONVERBOSE,
+                                 _("%s URL: %s %2d %s\n"),
+                                 tms, u->url, hstat.statcode,
+                                 hstat.message ? quotearg_style (escape_quoting_style, hstat.message) : "");
+                      goto exit;
                     }
                 }
-              else
-                logputs (LOG_VERBOSE,
-                         _("Remote file is newer, retrieving.\n"));
 
-              logputs (LOG_VERBOSE, "\n");
-            }
-          
-          /* free_hstat (&hstat); */
-          hstat.timestamp_checked = true;
-          continue;
-        }
-      
-      if ((tmr != (time_t) (-1))
-          && !opt.spider
+              got_name = true;
+              *dt &= ~HEAD_ONLY;
+              count = 0;          /* the retrieve count for HEAD is reset */
+              continue;
+            } /* send_head_first */
+        } /* !got_head */
+
+      if (opt.useservertimestamps
+          && (tmr != (time_t) (-1))
           && ((hstat.len == hstat.contlen) ||
               ((hstat.res == 0) && (hstat.contlen == -1))))
         {
-          /* #### This code repeats in http.c and ftp.c.  Move it to a
-             function!  */
           const char *fl = NULL;
-          if (opt.output_document)
+          set_local_file (&fl, hstat.local_file);
+          if (fl)
             {
-              if (output_stream_regular)
-                fl = opt.output_document;
+              time_t newtmr = -1;
+              /* Reparse time header, in case it's changed. */
+              if (time_came_from_head
+                  && hstat.remote_time && hstat.remote_time[0])
+                {
+                  newtmr = http_atotm (hstat.remote_time);
+                  if (newtmr != (time_t)-1)
+                    tmr = newtmr;
+                }
+              touch (fl, tmr);
             }
-          else
-            fl = hstat.local_file;
-          if (fl)
-            touch (fl, tmr);
         }
       /* End of time-stamping section. */
 
-      if (opt.spider)
-        {
-          logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode,
-                     escnonprint (hstat.error));
-          return RETROK;
-        }
-
       tmrate = retr_rate (hstat.rd_size, hstat.dltime);
       total_download_time += hstat.dltime;
 
@@ -2379,9 +3450,14 @@ The sizes do not match (local %s) -- retrieving.\n"),
         {
           if (*dt & RETROKF)
             {
+              bool write_to_stdout = (opt.output_document && HYPHENP (opt.output_document));
+
               logprintf (LOG_VERBOSE,
-                         _("%s (%s) - `%s' saved [%s/%s]\n\n"),
-                         tms, tmrate, hstat.local_file,
+                         write_to_stdout
+                         ? _("%s (%s) - written to stdout %s[%s/%s]\n\n")
+                         : _("%s (%s) - %s saved [%s/%s]\n\n"),
+                         tms, tmrate,
+                         write_to_stdout ? "" : quote (hstat.local_file),
                          number_to_static_string (hstat.len),
                          number_to_static_string (hstat.contlen));
               logprintf (LOG_NONVERBOSE,
@@ -2391,45 +3467,50 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          number_to_static_string (hstat.contlen),
                          hstat.local_file, count);
             }
-          ++opt.numurls;
-          total_downloaded_bytes += hstat.len;
+          ++numurls;
+          total_downloaded_bytes += hstat.rd_size;
 
           /* Remember that we downloaded the file for later ".orig" code. */
           if (*dt & ADDED_HTML_EXTENSION)
-            downloaded_file(FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED, hstat.local_file);
+            downloaded_file (FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED, hstat.local_file);
           else
-            downloaded_file(FILE_DOWNLOADED_NORMALLY, hstat.local_file);
+            downloaded_file (FILE_DOWNLOADED_NORMALLY, hstat.local_file);
 
-          free_hstat (&hstat);
-          return RETROK;
+          ret = RETROK;
+          goto exit;
         }
       else if (hstat.res == 0) /* No read error */
         {
           if (hstat.contlen == -1)  /* We don't know how much we were supposed
-                                       to get, so assume we succeeded. */ 
+                                       to get, so assume we succeeded. */
             {
               if (*dt & RETROKF)
                 {
+                  bool write_to_stdout = (opt.output_document && HYPHENP (opt.output_document));
+
                   logprintf (LOG_VERBOSE,
-                             _("%s (%s) - `%s' saved [%s]\n\n"),
-                             tms, tmrate, hstat.local_file,
+                             write_to_stdout
+                             ? _("%s (%s) - written to stdout %s[%s]\n\n")
+                             : _("%s (%s) - %s saved [%s]\n\n"),
+                             tms, tmrate,
+                             write_to_stdout ? "" : quote (hstat.local_file),
                              number_to_static_string (hstat.len));
                   logprintf (LOG_NONVERBOSE,
                              "%s URL:%s [%s] -> \"%s\" [%d]\n",
                              tms, u->url, number_to_static_string (hstat.len),
                              hstat.local_file, count);
                 }
-              ++opt.numurls;
-              total_downloaded_bytes += hstat.len;
+              ++numurls;
+              total_downloaded_bytes += hstat.rd_size;
 
               /* Remember that we downloaded the file for later ".orig" code. */
               if (*dt & ADDED_HTML_EXTENSION)
-                downloaded_file(FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED, hstat.local_file);
+                downloaded_file (FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED, hstat.local_file);
               else
-                downloaded_file(FILE_DOWNLOADED_NORMALLY, hstat.local_file);
-              
-              free_hstat (&hstat);
-              return RETROK;
+                downloaded_file (FILE_DOWNLOADED_NORMALLY, hstat.local_file);
+
+              ret = RETROK;
+              goto exit;
             }
           else if (hstat.len < hstat.contlen) /* meaning we lost the
                                                  connection too soon */
@@ -2438,13 +3519,20 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          _("%s (%s) - Connection closed at byte %s. "),
                          tms, tmrate, number_to_static_string (hstat.len));
               printwhat (count, opt.ntry);
-              /* free_hstat (&hstat); */
               continue;
             }
-          else
+          else if (hstat.len != hstat.restval)
             /* Getting here would mean reading more data than
                requested with content-length, which we never do.  */
             abort ();
+          else
+            {
+              /* Getting here probably means that the content-length was
+               * _less_ than the original, local size. We should probably
+               * truncate or re-read, or something. FIXME */
+              ret = RETROK;
+              goto exit;
+            }
         }
       else /* from now on hstat.res can only be -1 */
         {
@@ -2455,7 +3543,6 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          tms, tmrate, number_to_static_string (hstat.len),
                          hstat.rderrmsg);
               printwhat (count, opt.ntry);
-              /* free_hstat (&hstat); */
               continue;
             }
           else /* hstat.res == -1 and contlen is given */
@@ -2467,15 +3554,19 @@ The sizes do not match (local %s) -- retrieving.\n"),
                          number_to_static_string (hstat.contlen),
                          hstat.rderrmsg);
               printwhat (count, opt.ntry);
-              /* free_hstat (&hstat); */
               continue;
             }
         }
       /* not reached */
     }
   while (!opt.ntry || (count < opt.ntry));
-  
-  return TRYLIMEXC;
+
+exit:
+  if (ret == RETROK && local_file)
+    *local_file = xstrdup (hstat.local_file);
+  free_hstat (&hstat);
+
+  return ret;
 }
 \f
 /* Check whether the result of strptime() indicates success.
@@ -2491,11 +3582,11 @@ check_end (const char *p)
 {
   if (!p)
     return false;
-  while (ISSPACE (*p))
+  while (c_isspace (*p))
     ++p;
   if (!*p
       || (p[0] == 'G' && p[1] == 'M' && p[2] == 'T')
-      || ((p[0] == '+' || p[0] == '-') && ISDIGIT (p[1])))
+      || ((p[0] == '+' || p[0] == '-') && c_isdigit (p[1])))
     return true;
   else
     return false;
@@ -2546,13 +3637,24 @@ http_atotm (const char *time_string)
                                    Netscape cookie specification.) */
   };
   const char *oldlocale;
-  int i;
+  char savedlocale[256];
+  size_t i;
   time_t ret = (time_t) -1;
 
   /* Solaris strptime fails to recognize English month names in
      non-English locales, which we work around by temporarily setting
      locale to C before invoking strptime.  */
   oldlocale = setlocale (LC_TIME, NULL);
+  if (oldlocale)
+    {
+      size_t l = strlen (oldlocale) + 1;
+      if (l >= sizeof savedlocale)
+        savedlocale[0] = '\0';
+      else
+        memcpy (savedlocale, oldlocale, l);
+    }
+  else savedlocale[0] = '\0';
+
   setlocale (LC_TIME, "C");
 
   for (i = 0; i < countof (time_formats); i++)
@@ -2572,7 +3674,8 @@ http_atotm (const char *time_string)
     }
 
   /* Restore the previous locale. */
-  setlocale (LC_TIME, oldlocale);
+  if (savedlocale[0])
+    setlocale (LC_TIME, savedlocale);
 
   return ret;
 }
@@ -2611,50 +3714,11 @@ basic_authentication_encode (const char *user, const char *passwd)
 }
 
 #define SKIP_WS(x) do {                         \
-  while (ISSPACE (*(x)))                        \
+  while (c_isspace (*(x)))                        \
     ++(x);                                      \
 } while (0)
 
 #ifdef ENABLE_DIGEST
-/* Parse HTTP `WWW-Authenticate:' header.  AU points to the beginning
-   of a field in such a header.  If the field is the one specified by
-   ATTR_NAME ("realm", "opaque", and "nonce" are used by the current
-   digest authorization code), extract its value in the (char*)
-   variable pointed by RET.  Returns negative on a malformed header,
-   or number of bytes that have been parsed by this call.  */
-static int
-extract_header_attr (const char *au, const char *attr_name, char **ret)
-{
-  const char *ep;
-  const char *cp = au;
-
-  if (strncmp (cp, attr_name, strlen (attr_name)) == 0)
-    {
-      cp += strlen (attr_name);
-      if (!*cp)
-        return -1;
-      SKIP_WS (cp);
-      if (*cp != '=')
-        return -1;
-      if (!*++cp)
-        return -1;
-      SKIP_WS (cp);
-      if (*cp != '\"')
-        return -1;
-      if (!*++cp)
-        return -1;
-      for (ep = cp; *ep && *ep != '\"'; ep++)
-        ;
-      if (!*ep)
-        return -1;
-      xfree_null (*ret);
-      *ret = strdupdelim (cp, ep);
-      return ep - au + 1;
-    }
-  else
-    return 0;
-}
-
 /* Dump the hexadecimal representation of HASH to BUF.  HASH should be
    an array of 16 bytes containing the hash keys, and BUF should be a
    buffer of 33 writable characters (32 for hex digits plus one for
@@ -2664,7 +3728,7 @@ dump_hash (char *buf, const unsigned char *hash)
 {
   int i;
 
-  for (i = 0; i < MD5_HASHLEN; i++, hash++)
+  for (i = 0; i < MD5_DIGEST_SIZE; i++, hash++)
     {
       *buf++ = XNUM_TO_digit (*hash >> 4);
       *buf++ = XNUM_TO_digit (*hash & 0xf);
@@ -2677,128 +3741,195 @@ dump_hash (char *buf, const unsigned char *hash)
 static char *
 digest_authentication_encode (const char *au, const char *user,
                               const char *passwd, const char *method,
-                              const char *path)
+                              const char *path, uerr_t *auth_err)
 {
-  static char *realm, *opaque, *nonce;
+  static char *realm, *opaque, *nonce, *qop, *algorithm;
   static struct {
     const char *name;
     char **variable;
   } options[] = {
     { "realm", &realm },
     { "opaque", &opaque },
-    { "nonce", &nonce }
+    { "nonce", &nonce },
+    { "qop", &qop },
+    { "algorithm", &algorithm }
   };
+  char cnonce[16] = "";
   char *res;
+  int res_len;
+  size_t res_size;
+  param_token name, value;
 
-  realm = opaque = nonce = NULL;
+
+  realm = opaque = nonce = algorithm = qop = NULL;
 
   au += 6;                      /* skip over `Digest' */
-  while (*au)
+  while (extract_param (&au, &name, &value, ',', NULL))
     {
-      int i;
-
-      SKIP_WS (au);
+      size_t i;
+      size_t namelen = name.e - name.b;
       for (i = 0; i < countof (options); i++)
-        {
-          int skip = extract_header_attr (au, options[i].name,
-                                          options[i].variable);
-          if (skip < 0)
-            {
-              xfree_null (realm);
-              xfree_null (opaque);
-              xfree_null (nonce);
-              return NULL;
-            }
-          else if (skip)
-            {
-              au += skip;
-              break;
-            }
-        }
-      if (i == countof (options))
-        {
-          while (*au && *au != '=')
-            au++;
-          if (*au && *++au)
-            {
-              SKIP_WS (au);
-              if (*au == '\"')
-                {
-                  au++;
-                  while (*au && *au != '\"')
-                    au++;
-                  if (*au)
-                    au++;
-                }
-            }
-        }
-      while (*au && *au != ',')
-        au++;
-      if (*au)
-        au++;
+        if (namelen == strlen (options[i].name)
+            && 0 == strncmp (name.b, options[i].name,
+                             namelen))
+          {
+            *options[i].variable = strdupdelim (value.b, value.e);
+            break;
+          }
     }
-  if (!realm || !nonce || !user || !passwd || !path || !method)
+
+  if (qop != NULL && strcmp(qop,"auth"))
+    {
+      logprintf (LOG_NOTQUIET, _("Unsupported quality of protection '%s'.\n"), qop);
+      xfree_null (qop); /* force freeing mem and return */
+      qop = NULL;
+    }
+  else if (algorithm != NULL && strcmp (algorithm,"MD5") && strcmp (algorithm,"MD5-sess"))
+    {
+      logprintf (LOG_NOTQUIET, _("Unsupported algorithm '%s'.\n"), algorithm);
+      xfree_null (qop); /* force freeing mem and return */
+      qop = NULL;
+    }
+
+  if (!realm || !nonce || !user || !passwd || !path || !method || !qop)
     {
       xfree_null (realm);
       xfree_null (opaque);
       xfree_null (nonce);
+      xfree_null (qop);
+      xfree_null (algorithm);
+      if (!qop)
+        *auth_err = UNKNOWNATTR;
+      else
+        *auth_err = ATTRMISSING;
       return NULL;
     }
 
   /* Calculate the digest value.  */
   {
-    ALLOCA_MD5_CONTEXT (ctx);
-    unsigned char hash[MD5_HASHLEN];
-    char a1buf[MD5_HASHLEN * 2 + 1], a2buf[MD5_HASHLEN * 2 + 1];
-    char response_digest[MD5_HASHLEN * 2 + 1];
+    struct md5_ctx ctx;
+    unsigned char hash[MD5_DIGEST_SIZE];
+    char a1buf[MD5_DIGEST_SIZE * 2 + 1], a2buf[MD5_DIGEST_SIZE * 2 + 1];
+    char response_digest[MD5_DIGEST_SIZE * 2 + 1];
 
     /* A1BUF = H(user ":" realm ":" password) */
-    gen_md5_init (ctx);
-    gen_md5_update ((unsigned char *)user, strlen (user), ctx);
-    gen_md5_update ((unsigned char *)":", 1, ctx);
-    gen_md5_update ((unsigned char *)realm, strlen (realm), ctx);
-    gen_md5_update ((unsigned char *)":", 1, ctx);
-    gen_md5_update ((unsigned char *)passwd, strlen (passwd), ctx);
-    gen_md5_finish (ctx, hash);
+    md5_init_ctx (&ctx);
+    md5_process_bytes ((unsigned char *)user, strlen (user), &ctx);
+    md5_process_bytes ((unsigned char *)":", 1, &ctx);
+    md5_process_bytes ((unsigned char *)realm, strlen (realm), &ctx);
+    md5_process_bytes ((unsigned char *)":", 1, &ctx);
+    md5_process_bytes ((unsigned char *)passwd, strlen (passwd), &ctx);
+    md5_finish_ctx (&ctx, hash);
+
     dump_hash (a1buf, hash);
 
+    if (algorithm && !strcmp (algorithm, "MD5-sess"))
+      {
+        /* A1BUF = H( H(user ":" realm ":" password) ":" nonce ":" cnonce ) */
+        snprintf (cnonce, sizeof (cnonce), "%08x", random_number(INT_MAX));
+
+        md5_init_ctx (&ctx);
+        // md5_process_bytes (hash, MD5_DIGEST_SIZE, &ctx);
+        md5_process_bytes (a1buf, MD5_DIGEST_SIZE * 2, &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)nonce, strlen (nonce), &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)cnonce, strlen (cnonce), &ctx);
+        md5_finish_ctx (&ctx, hash);
+
+        dump_hash (a1buf, hash);
+      }
+
     /* A2BUF = H(method ":" path) */
-    gen_md5_init (ctx);
-    gen_md5_update ((unsigned char *)method, strlen (method), ctx);
-    gen_md5_update ((unsigned char *)":", 1, ctx);
-    gen_md5_update ((unsigned char *)path, strlen (path), ctx);
-    gen_md5_finish (ctx, hash);
+    md5_init_ctx (&ctx);
+    md5_process_bytes ((unsigned char *)method, strlen (method), &ctx);
+    md5_process_bytes ((unsigned char *)":", 1, &ctx);
+    md5_process_bytes ((unsigned char *)path, strlen (path), &ctx);
+    md5_finish_ctx (&ctx, hash);
     dump_hash (a2buf, hash);
 
-    /* RESPONSE_DIGEST = H(A1BUF ":" nonce ":" A2BUF) */
-    gen_md5_init (ctx);
-    gen_md5_update ((unsigned char *)a1buf, MD5_HASHLEN * 2, ctx);
-    gen_md5_update ((unsigned char *)":", 1, ctx);
-    gen_md5_update ((unsigned char *)nonce, strlen (nonce), ctx);
-    gen_md5_update ((unsigned char *)":", 1, ctx);
-    gen_md5_update ((unsigned char *)a2buf, MD5_HASHLEN * 2, ctx);
-    gen_md5_finish (ctx, hash);
+    if (qop && (!strcmp(qop, "auth") || !strcmp (qop, "auth-int")))
+      {
+        /* RFC 2617 Digest Access Authentication */
+        /* generate random hex string */
+        if (!*cnonce)
+          snprintf(cnonce, sizeof(cnonce), "%08x", random_number(INT_MAX));
+
+        /* RESPONSE_DIGEST = H(A1BUF ":" nonce ":" noncecount ":" clientnonce ":" qop ": " A2BUF) */
+        md5_init_ctx (&ctx);
+        md5_process_bytes ((unsigned char *)a1buf, MD5_DIGEST_SIZE * 2, &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)nonce, strlen (nonce), &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)"00000001", 8, &ctx); /* TODO: keep track of server nonce values */
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)cnonce, strlen(cnonce), &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)qop, strlen(qop), &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)a2buf, MD5_DIGEST_SIZE * 2, &ctx);
+        md5_finish_ctx (&ctx, hash);
+      }
+    else
+      {
+        /* RFC 2069 Digest Access Authentication */
+        /* RESPONSE_DIGEST = H(A1BUF ":" nonce ":" A2BUF) */
+        md5_init_ctx (&ctx);
+        md5_process_bytes ((unsigned char *)a1buf, MD5_DIGEST_SIZE * 2, &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)nonce, strlen (nonce), &ctx);
+        md5_process_bytes ((unsigned char *)":", 1, &ctx);
+        md5_process_bytes ((unsigned char *)a2buf, MD5_DIGEST_SIZE * 2, &ctx);
+        md5_finish_ctx (&ctx, hash);
+      }
+
     dump_hash (response_digest, hash);
 
-    res = xmalloc (strlen (user)
-                   + strlen (user)
-                   + strlen (realm)
-                   + strlen (nonce)
-                   + strlen (path)
-                   + 2 * MD5_HASHLEN /*strlen (response_digest)*/
-                   + (opaque ? strlen (opaque) : 0)
-                   + 128);
-    sprintf (res, "Digest \
-username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
-             user, realm, nonce, path, response_digest);
+    res_size = strlen (user)
+             + strlen (realm)
+             + strlen (nonce)
+             + strlen (path)
+             + 2 * MD5_DIGEST_SIZE /*strlen (response_digest)*/
+             + (opaque ? strlen (opaque) : 0)
+             + (algorithm ? strlen (algorithm) : 0)
+             + (qop ? 128: 0)
+             + strlen (cnonce)
+             + 128;
+
+    res = xmalloc (res_size);
+
+    if (qop && !strcmp (qop, "auth"))
+      {
+        res_len = snprintf (res, res_size, "Digest "\
+                "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\""\
+                ", qop=auth, nc=00000001, cnonce=\"%s\"",
+                  user, realm, nonce, path, response_digest, cnonce);
+
+      }
+    else
+      {
+        res_len = snprintf (res, res_size, "Digest "\
+                "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
+                  user, realm, nonce, path, response_digest);
+      }
+
     if (opaque)
       {
-        char *p = res + strlen (res);
-        strcat (p, ", opaque=\"");
-        strcat (p, opaque);
-        strcat (p, "\"");
+        res_len += snprintf(res + res_len, res_size - res_len, ", opaque=\"%s\"", opaque);
+      }
+
+    if (algorithm)
+      {
+        snprintf(res + res_len, res_size - res_len, ", algorithm=\"%s\"", algorithm);
       }
   }
+
+  xfree_null (realm);
+  xfree_null (opaque);
+  xfree_null (nonce);
+  xfree_null (qop);
+  xfree_null (algorithm);
+
   return res;
 }
 #endif /* ENABLE_DIGEST */
@@ -2811,10 +3942,11 @@ username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
    first argument and are followed by whitespace or terminating \0.
    The comparison is case-insensitive.  */
 #define STARTS(literal, b, e)                           \
-  ((e) - (b) >= STRSIZE (literal)                       \
+  ((e > b) \
+   && ((size_t) ((e) - (b))) >= STRSIZE (literal)   \
    && 0 == strncasecmp (b, literal, STRSIZE (literal))  \
-   && ((e) - (b) == STRSIZE (literal)                   \
-       || ISSPACE (b[STRSIZE (literal)])))
+   && ((size_t) ((e) - (b)) == STRSIZE (literal)          \
+       || c_isspace (b[STRSIZE (literal)])))
 
 static bool
 known_authentication_scheme_p (const char *hdrbeg, const char *hdrend)
@@ -2839,11 +3971,11 @@ known_authentication_scheme_p (const char *hdrbeg, const char *hdrend)
 static char *
 create_authorization_line (const char *au, const char *user,
                            const char *passwd, const char *method,
-                           const char *path, bool *finished)
+                           const char *path, bool *finished, uerr_t *auth_err)
 {
   /* We are called only with known schemes, so we can dispatch on the
      first letter. */
-  switch (TOUPPER (*au))
+  switch (c_toupper (*au))
     {
     case 'B':                   /* Basic */
       *finished = true;
@@ -2851,7 +3983,7 @@ create_authorization_line (const char *au, const char *user,
 #ifdef ENABLE_DIGEST
     case 'D':                   /* Digest */
       *finished = true;
-      return digest_authentication_encode (au, user, passwd, method, path);
+      return digest_authentication_encode (au, user, passwd, method, path, auth_err);
 #endif
 #ifdef ENABLE_NTLM
     case 'N':                   /* NTLM */
@@ -2896,7 +4028,81 @@ http_cleanup (void)
     cookie_jar_delete (wget_cookie_jar);
 }
 
+void
+ensure_extension (struct http_stat *hs, const char *ext, int *dt)
+{
+  char *last_period_in_local_filename = strrchr (hs->local_file, '.');
+  char shortext[8];
+  int len = strlen (ext);
+  if (len == 5)
+    {
+      strncpy (shortext, ext, len - 1);
+      shortext[len - 1] = '\0';
+    }
+
+  if (last_period_in_local_filename == NULL
+      || !(0 == strcasecmp (last_period_in_local_filename, shortext)
+           || 0 == strcasecmp (last_period_in_local_filename, ext)))
+    {
+      int local_filename_len = strlen (hs->local_file);
+      /* Resize the local file, allowing for ".html" preceded by
+         optional ".NUMBER".  */
+      hs->local_file = xrealloc (hs->local_file,
+                                 local_filename_len + 24 + len);
+      strcpy (hs->local_file + local_filename_len, ext);
+      /* If clobbering is not allowed and the file, as named,
+         exists, tack on ".NUMBER.html" instead. */
+      if (!ALLOW_CLOBBER && file_exists_p (hs->local_file))
+        {
+          int ext_num = 1;
+          do
+            sprintf (hs->local_file + local_filename_len,
+                     ".%d%s", ext_num++, ext);
+          while (file_exists_p (hs->local_file));
+        }
+      *dt |= ADDED_HTML_EXTENSION;
+    }
+}
+
+
+#ifdef TESTING
+
+const char *
+test_parse_content_disposition(void)
+{
+  unsigned i;
+  static const struct {
+    const char *hdrval;
+    const char *filename;
+    bool result;
+  } test_array[] = {
+    { "filename=\"file.ext\"", "file.ext", true },
+    { "attachment; filename=\"file.ext\"", "file.ext", true },
+    { "attachment; filename=\"file.ext\"; dummy", "file.ext", true },
+    { "attachment", NULL, false },
+    { "attachement; filename*=UTF-8'en-US'hello.txt", "hello.txt", true },
+    { "attachement; filename*0=\"hello\"; filename*1=\"world.txt\"", "helloworld.txt", true },
+  };
+
+  for (i = 0; i < countof(test_array); ++i)
+    {
+      char *filename;
+      bool res;
+
+      res = parse_content_disposition (test_array[i].hdrval, &filename);
+
+      mu_assert ("test_parse_content_disposition: wrong result",
+                 res == test_array[i].result
+                 && (res == false
+                     || 0 == strcmp (test_array[i].filename, filename)));
+    }
+
+  return NULL;
+}
+
+#endif /* TESTING */
+
 /*
- * vim: et ts=2 sw=2
+ * vim: et sts=2 sw=2 cino+={s
  */