]> sjero.net Git - wget/blobdiff - src/http.c
[svn] Support human-readable file size printing.
[wget] / src / http.c
index dfb7c00204084172c622af9c42cb389627949095..85d73404a315cd3ca13336c22eb159ef628ac273 100644 (file)
@@ -1,5 +1,5 @@
 /* HTTP support.
-   Copyright (C) 2003 Free Software Foundation, Inc.
+   Copyright (C) 2005 Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
 
@@ -84,7 +84,7 @@ extern int output_stream_regular;
 
 \f
 static int cookies_loaded_p;
-struct cookie_jar *wget_cookie_jar;
+static struct cookie_jar *wget_cookie_jar;
 
 #define TEXTHTML_S "text/html"
 #define TEXTXHTML_S "application/xhtml+xml"
@@ -219,7 +219,8 @@ release_header (struct request_header *hdr)
      request_set_header (req, "Referer", opt.referer, rel_none);
 
      // Value freshly allocated, free it when done.
-     request_set_header (req, "Range", aprintf ("bytes=%ld-", hs->restval),
+     request_set_header (req, "Range",
+                         aprintf ("bytes=%s-", number_to_static_string (hs->restval)),
                         rel_value);
    */
 
@@ -359,10 +360,10 @@ request_free (struct request *req)
    longer, read only that much; if the file is shorter, report an error.  */
 
 static int
-post_file (int sock, const char *file_name, long promised_size)
+post_file (int sock, const char *file_name, wgint promised_size)
 {
   static char chunk[8192];
-  long written = 0;
+  wgint written = 0;
   int write_error;
   FILE *fp;
 
@@ -402,7 +403,7 @@ post_file (int sock, const char *file_name, long promised_size)
 }
 \f
 static const char *
-head_terminator (const char *hunk, int oldlen, int peeklen)
+response_head_terminator (const char *hunk, int oldlen, int peeklen)
 {
   const char *start, *end;
 
@@ -431,6 +432,13 @@ head_terminator (const char *hunk, int oldlen, int peeklen)
   return NULL;
 }
 
+/* The maximum size of a single HTTP response we care to read.  This
+   is not meant to impose an arbitrary limit, but to protect the user
+   from Wget slurping up available memory upon encountering malicious
+   or buggy server output.  Define it to 0 to remove the limit.  */
+
+#define HTTP_RESPONSE_MAX_SIZE 65536
+
 /* Read the HTTP request head from FD and return it.  The error
    conditions are the same as with fd_read_hunk.
 
@@ -440,9 +448,10 @@ head_terminator (const char *hunk, int oldlen, int peeklen)
    data can be treated as body.  */
 
 static char *
-fd_read_http_head (int fd)
+read_http_response_head (int fd)
 {
-  return fd_read_hunk (fd, head_terminator, 512);
+  return fd_read_hunk (fd, response_head_terminator, 512,
+                      HTTP_RESPONSE_MAX_SIZE);
 }
 
 struct response {
@@ -473,10 +482,10 @@ struct response {
 /* Create a new response object from the text of the HTTP response,
    available in HEAD.  That text is automatically split into
    constituent header lines for fast retrieval using
-   response_header_*.  */
+   resp_header_*.  */
 
 static struct response *
-response_new (const char *head)
+resp_new (const char *head)
 {
   const char *hdr;
   int count, size;
@@ -492,7 +501,7 @@ response_new (const char *head)
       return resp;
     }
 
-  /* Split HEAD into header lines, so that response_header_* functions
+  /* Split HEAD into header lines, so that resp_header_* functions
      don't need to do this over and over again.  */
 
   size = count = 0;
@@ -518,32 +527,41 @@ response_new (const char *head)
       while (*hdr == ' ' || *hdr == '\t');
     }
   DO_REALLOC (resp->headers, size, count + 1, const char *);
-  resp->headers[count++] = NULL;
+  resp->headers[count] = NULL;
 
   return resp;
 }
 
-/* Locate the header named NAME in the request data.  If found, set
-   *BEGPTR to its starting, and *ENDPTR to its ending position, and
-   return 1.  Otherwise return 0.
+/* Locate the header named NAME in the request data, starting with
+   position START.  This allows the code to loop through the request
+   data, filtering for all requests of a given name.  Returns the
+   found position, or -1 for failure.  The code that uses this
+   function typically looks like this:
 
-   This function is used as a building block for response_header_copy
-   and response_header_strdup.  */
+     for (pos = 0; (pos = resp_header_locate (...)) != -1; pos++)
+       ... do something with header ...
+
+   If you only care about one header, use resp_header_get instead of
+   this function.  */
 
 static int
-response_header_bounds (const struct response *resp, const char *name,
-                       const char **begptr, const char **endptr)
+resp_header_locate (const struct response *resp, const char *name, int start,
+                   const char **begptr, const char **endptr)
 {
   int i;
   const char **headers = resp->headers;
   int name_len;
 
   if (!headers || !headers[1])
-    return 0;
+    return -1;
 
   name_len = strlen (name);
+  if (start > 0)
+    i = start;
+  else
+    i = 1;
 
-  for (i = 1; headers[i + 1]; i++)
+  for (; headers[i + 1]; i++)
     {
       const char *b = headers[i];
       const char *e = headers[i + 1];
@@ -558,26 +576,41 @@ response_header_bounds (const struct response *resp, const char *name,
            --e;
          *begptr = b;
          *endptr = e;
-         return 1;
+         return i;
        }
     }
-  return 0;
+  return -1;
+}
+
+/* Find and retrieve the header named NAME in the request data.  If
+   found, set *BEGPTR to its starting, and *ENDPTR to its ending
+   position, and return 1.  Otherwise return 0.
+
+   This function is used as a building block for resp_header_copy
+   and resp_header_strdup.  */
+
+static int
+resp_header_get (const struct response *resp, const char *name,
+                const char **begptr, const char **endptr)
+{
+  int pos = resp_header_locate (resp, name, 0, begptr, endptr);
+  return pos != -1;
 }
 
 /* Copy the response header named NAME to buffer BUF, no longer than
    BUFSIZE (BUFSIZE includes the terminating 0).  If the header
    exists, 1 is returned, otherwise 0.  If there should be no limit on
-   the size of the header, use response_header_strdup instead.
+   the size of the header, use resp_header_strdup instead.
 
    If BUFSIZE is 0, no data is copied, but the boolean indication of
    whether the header is present is still returned.  */
 
 static int
-response_header_copy (const struct response *resp, const char *name,
-                     char *buf, int bufsize)
+resp_header_copy (const struct response *resp, const char *name,
+                 char *buf, int bufsize)
 {
   const char *b, *e;
-  if (!response_header_bounds (resp, name, &b, &e))
+  if (!resp_header_get (resp, name, &b, &e))
     return 0;
   if (bufsize)
     {
@@ -592,10 +625,10 @@ response_header_copy (const struct response *resp, const char *name,
    malloc.  If such a header does not exist in RESP, return NULL.  */
 
 static char *
-response_header_strdup (const struct response *resp, const char *name)
+resp_header_strdup (const struct response *resp, const char *name)
 {
   const char *b, *e;
-  if (!response_header_bounds (resp, name, &b, &e))
+  if (!resp_header_get (resp, name, &b, &e))
     return NULL;
   return strdupdelim (b, e);
 }
@@ -609,7 +642,7 @@ response_header_strdup (const struct response *resp, const char *name)
    returned in *MESSAGE.  */
 
 static int
-response_status (const struct response *resp, char **message)
+resp_status (const struct response *resp, char **message)
 {
   int status;
   const char *p, *end;
@@ -669,7 +702,7 @@ response_status (const struct response *resp, char **message)
 /* Release the resources used by RESP.  */
 
 static void
-response_free (struct response *resp)
+resp_free (struct response *resp)
 {
   xfree_null (resp->headers);
   xfree (resp);
@@ -686,7 +719,7 @@ print_server_response_1 (const char *prefix, const char *b, const char *e)
   if (b < e && e[-1] == '\r')
     --e;
   BOUNDED_TO_ALLOCA (b, e, ln);
-  logprintf (LOG_VERBOSE, "%s%s\n", prefix, ln);
+  logprintf (LOG_VERBOSE, "%s%s\n", prefix, escnonprint (ln));
 }
 
 /* Print the server response, line by line, omitting the trailing CR
@@ -705,10 +738,10 @@ print_server_response (const struct response *resp, const char *prefix)
 /* Parse the `Content-Range' header and extract the information it
    contains.  Returns 1 if successful, -1 otherwise.  */
 static int
-parse_content_range (const char *hdr, long *first_byte_ptr,
-                    long *last_byte_ptr, long *entity_length_ptr)
+parse_content_range (const char *hdr, wgint *first_byte_ptr,
+                    wgint *last_byte_ptr, wgint *entity_length_ptr)
 {
-  long num;
+  wgint num;
 
   /* Ancient versions of Netscape proxy server, presumably predating
      rfc2068, sent out `Content-Range' without the "bytes"
@@ -751,7 +784,7 @@ parse_content_range (const char *hdr, long *first_byte_ptr,
    which need to be read anyway.  */
 
 static void
-skip_short_body (int fd, long contlen)
+skip_short_body (int fd, wgint contlen)
 {
   /* Skipping the body doesn't make sense if the content length is
      unknown because, in that case, persistent connections cannot be
@@ -759,7 +792,7 @@ skip_short_body (int fd, long contlen)
      still be used with the magic of the "chunked" transfer!)  */
   if (contlen == -1)
     return;
-  DEBUGP (("Skipping %ld bytes of body data... ", contlen));
+  DEBUGP (("Skipping %s bytes of body data... ", number_to_static_string (contlen)));
 
   while (contlen > 0)
     {
@@ -974,15 +1007,15 @@ persistent_available_p (const char *host, int port, int ssl,
 \f
 struct http_stat
 {
-  long len;                    /* received length */
-  long contlen;                        /* expected length */
-  long restval;                        /* the restart value */
+  wgint len;                   /* received length */
+  wgint contlen;                       /* expected length */
+  wgint restval;                       /* the restart value */
   int res;                     /* the result of last read */
   char *newloc;                        /* new location (redirection) */
   char *remote_time;           /* remote time-stamp string */
   char *error;                 /* textual HTTP error */
   int statcode;                        /* status code */
-  long rd_size;                        /* amount of data read from socket */
+  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. */
@@ -1034,7 +1067,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   char *proxyauth;
   int statcode;
   int write_error;
-  long contlen, contrange;
+  wgint contlen, contrange;
   struct url *conn;
   FILE *fp;
 
@@ -1042,7 +1075,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   int flags;
 
   /* Whether authorization has been already tried. */
-  int auth_tried_already = 0;
+  int auth_tried_already;
 
   /* Whether our connection to the remote host is through SSL.  */
   int using_ssl = 0;
@@ -1056,11 +1089,18 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
      is done. */
   int keep_alive;
 
-  /* Whether keep-alive should be inhibited. */
-  int inhibit_keep_alive = !opt.http_keep_alive || opt.ignore_length;
+  /* Whether keep-alive should be inhibited.
+
+     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.  */
+  int inhibit_keep_alive =
+    !opt.http_keep_alive || opt.ignore_length /*|| proxy != NULL*/;
 
   /* Headers sent when using POST. */
-  long post_data_size = 0;
+  wgint post_data_size = 0;
 
   int host_lookup_failed = 0;
 
@@ -1134,7 +1174,9 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     request_set_header (req, "Pragma", "no-cache", rel_none);
   if (hs->restval)
     request_set_header (req, "Range",
-                       aprintf ("bytes=%ld-", hs->restval), rel_value);
+                       aprintf ("bytes=%s-",
+                                number_to_static_string (hs->restval)),
+                       rel_value);
   if (opt.useragent)
     request_set_header (req, "User-Agent", opt.useragent, rel_none);
   else
@@ -1206,7 +1248,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
       /* Proxy authorization over SSL is handled below. */
 #ifdef HAVE_SSL
-      if (u->scheme != SCHEME_SSL)
+      if (u->scheme != SCHEME_HTTPS)
 #endif
        request_set_header (req, "Proxy-Authorization", proxyauth, rel_value);
     }
@@ -1259,7 +1301,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
            }
        }
       request_set_header (req, "Content-Length",
-                         aprintf ("%ld", post_data_size), rel_value);
+                         xstrdup (number_to_static_string (post_data_size)),
+                         rel_value);
     }
 
   /* Add the user headers. */
@@ -1302,7 +1345,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          sock = pconn.socket;
          using_ssl = pconn.ssl;
          logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"),
-                    pconn.host, pconn.port);
+                    escnonprint (pconn.host), pconn.port);
          DEBUGP (("Reusing fd %d.\n", sock));
        }
     }
@@ -1313,14 +1356,23 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
         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)
-       return HOSTERR;
+       {
+         request_free (req);
+         return HOSTERR;
+       }
 
       sock = connect_to_host (conn->host, conn->port);
       if (sock == E_HOST)
-       return HOSTERR;
+       {
+         request_free (req);
+         return HOSTERR;
+       }
       else if (sock < 0)
-       return (retryable_socket_connect_error (errno)
-               ? CONERROR : CONIMPOSSIBLE);
+       {
+         request_free (req);
+         return (retryable_socket_connect_error (errno)
+                 ? CONERROR : CONIMPOSSIBLE);
+       }
 
 #ifdef HAVE_SSL
       if (proxy && u->scheme == SCHEME_HTTPS)
@@ -1350,7 +1402,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
              return WRITEFAILED;
            }
 
-         head = fd_read_http_head (sock);
+         head = read_http_response_head (sock);
          if (!head)
            {
              logprintf (LOG_VERBOSE, _("Failed reading proxy response: %s\n"),
@@ -1366,18 +1418,19 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
            }
          DEBUGP (("proxy responded with: [%s]\n", head));
 
-         resp = response_new (head);
-         statcode = response_status (resp, &message);
-         response_free (resp);
+         resp = resp_new (head);
+         statcode = resp_status (resp, &message);
+         resp_free (resp);
+         xfree (head);
          if (statcode != 200)
            {
            failed_tunnel:
              logprintf (LOG_NOTQUIET, _("Proxy tunneling failed: %s"),
-                        message ? message : "?");
+                        message ? escnonprint (message) : "?");
              xfree_null (message);
              return CONSSLERR;
            }
-         xfree (message);
+         xfree_null (message);
 
          /* SOCK is now *really* connected to u->host, so update CONN
             to reflect this.  That way register_persistent will
@@ -1423,11 +1476,9 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
             proxy ? "Proxy" : "HTTP");
   contlen = -1;
   contrange = 0;
-  type = NULL;
-  statcode = -1;
   *dt &= ~RETROKF;
 
-  head = fd_read_http_head (sock);
+  head = read_http_response_head (sock);
   if (!head)
     {
       if (errno == 0)
@@ -1448,13 +1499,14 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     }
   DEBUGP (("\n---response begin---\n%s---response end---\n", head));
 
-  resp = response_new (head);
+  resp = resp_new (head);
 
   /* Check for status line.  */
   message = NULL;
-  statcode = response_status (resp, &message);
+  statcode = resp_status (resp, &message);
   if (!opt.server_response)
-    logprintf (LOG_VERBOSE, "%2d %s\n", statcode, message ? message : "");
+    logprintf (LOG_VERBOSE, "%2d %s\n", statcode,
+              message ? escnonprint (message) : "");
   else
     {
       logprintf (LOG_VERBOSE, "\n");
@@ -1462,16 +1514,28 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     }
 
   if (!opt.ignore_length
-      && response_header_copy (resp, "Content-Length", hdrval, sizeof (hdrval)))
-    contlen = strtol (hdrval, NULL, 10);
+      && 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 (response_header_copy (resp, "Keep-Alive", NULL, 0))
+      if (resp_header_copy (resp, "Keep-Alive", NULL, 0))
        keep_alive = 1;
-      else if (response_header_copy (resp, "Connection", hdrval,
-                                    sizeof (hdrval)))
+      else if (resp_header_copy (resp, "Connection", hdrval, sizeof (hdrval)))
        {
          if (0 == strcasecmp (hdrval, "Keep-Alive"))
            keep_alive = 1;
@@ -1495,8 +1559,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        }
       else
        {
-         char *www_authenticate = response_header_strdup (resp,
-                                                          "WWW-Authenticate");
+         char *www_authenticate = resp_header_strdup (resp,
+                                                      "WWW-Authenticate");
          /* If the authentication scheme is unknown or if it's the
             "Basic" authentication (which we try by default), there's
             no sense in retrying.  */
@@ -1535,8 +1599,9 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     hs->error = xstrdup (_("(no description)"));
   else
     hs->error = xstrdup (message);
+  xfree (message);
 
-  type = response_header_strdup (resp, "Content-Type");
+  type = resp_header_strdup (resp, "Content-Type");
   if (type)
     {
       char *tmp = strchr (type, ';');
@@ -1547,27 +1612,36 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          *tmp = '\0';
        }
     }
-  hs->newloc = response_header_strdup (resp, "Location");
-  hs->remote_time = response_header_strdup (resp, "Last-Modified");
+  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. */
   {
-    char *set_cookie = response_header_strdup (resp, "Set-Cookie");
-    if (set_cookie)
+    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)
       {
-       /* The jar should have been created by now. */
-       assert (wget_cookie_jar != NULL);
+       char *set_cookie = strdupdelim (scbeg, scend);
        cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port, u->path,
                                  set_cookie);
        xfree (set_cookie);
       }
   }
-  if (response_header_copy (resp, "Content-Range", hdrval, sizeof (hdrval)))
+
+  if (resp_header_copy (resp, "Content-Range", hdrval, sizeof (hdrval)))
     {
-      long first_byte_pos, last_byte_pos, entity_length;
+      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;
     }
-  response_free (resp);
+  resp_free (resp);
+  xfree (head);
 
   /* 20x responses are counted among successful by default.  */
   if (H_20X (statcode))
@@ -1587,7 +1661,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        {
          logprintf (LOG_VERBOSE,
                     _("Location: %s%s\n"),
-                    hs->newloc ? hs->newloc : _("unspecified"),
+                    hs->newloc ? escnonprint_uri (hs->newloc) : _("unspecified"),
                     hs->newloc ? _(" [following]") : "");
          if (keep_alive)
            skip_short_body (sock, contlen);
@@ -1666,15 +1740,26 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          logputs (LOG_VERBOSE, _("Length: "));
          if (contlen != -1)
            {
-             logputs (LOG_VERBOSE, legible (contlen + contrange));
+             logputs (LOG_VERBOSE, with_thousand_seps (contlen + contrange));
+             if (contlen + contrange >= 1024)
+               logprintf (LOG_VERBOSE, " (%s)",
+                          human_readable (contlen + contrange));
              if (contrange)
-               logprintf (LOG_VERBOSE, _(" (%s to go)"), legible (contlen));
+               {
+                 if (contlen >= 1024)
+                   logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
+                              with_thousand_seps (contlen),
+                              human_readable (contlen));
+                 else
+                   logprintf (LOG_VERBOSE, _(", %s remaining"),
+                              with_thousand_seps (contlen));
+               }
            }
          else
            logputs (LOG_VERBOSE,
                     opt.ignore_length ? _("ignored") : _("unspecified"));
          if (type)
-           logprintf (LOG_VERBOSE, " [%s]\n", type);
+           logprintf (LOG_VERBOSE, " [%s]\n", escnonprint (type));
          else
            logputs (LOG_VERBOSE, "\n");
        }
@@ -1703,7 +1788,27 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       mkalldirs (*hs->local_file);
       if (opt.backups)
        rotate_backups (*hs->local_file);
-      fp = fopen (*hs->local_file, hs->restval ? "ab" : "wb");
+      if (hs->restval)
+       fp = fopen (*hs->local_file, "ab");
+      else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
+              || opt.output_document)
+       fp = fopen (*hs->local_file, "wb");
+      else
+       {
+         fp = fopen_excl (*hs->local_file, 0);
+         if (!fp && errno == EEXIST)
+           {
+             /* We cannot just invent a new name and use it (which is
+                what functions like unique_create typically do)
+                because we told the user we'd use this name.
+                Instead, return and retry the download.  */
+             logprintf (LOG_NOTQUIET,
+                        _("%s has sprung into existence.\n"),
+                        *hs->local_file);
+             CLOSE_INVALIDATE (sock);
+             return FOPEN_EXCL_ERR;
+           }
+       }
       if (!fp)
        {
          logprintf (LOG_NOTQUIET, "%s: %s\n", *hs->local_file, strerror (errno));
@@ -1768,10 +1873,10 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   char *tms, *locf, *tmrate;
   uerr_t err;
   time_t tml = -1, tmr = -1;   /* local and remote time-stamps */
-  long local_size = 0;         /* the size of the local file */
+  wgint local_size = 0;                /* the size of the local file */
   size_t filename_len;
   struct http_stat hstat;      /* HTTP status */
-  struct stat st;
+  struct_stat st;
   char *dummy = NULL;
 
   /* This used to be done in main(), but it's a better idea to do it
@@ -1801,7 +1906,7 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   /* Determine the local filename.  */
   if (local_file && *local_file)
     hstat.local_file = local_file;
-  else if (local_file)
+  else if (local_file && !opt.output_document)
     {
       *local_file = url_file_name (u);
       hstat.local_file = local_file;
@@ -1810,6 +1915,9 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
     {
       dummy = url_file_name (u);
       hstat.local_file = &dummy;
+      /* be honest about where we will save the file */
+      if (local_file && opt.output_document)
+        *local_file = HYPHENP (opt.output_document) ? NULL : xstrdup (opt.output_document);
     }
 
   if (!opt.output_document)
@@ -1861,7 +1969,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
             point I profiled Wget, and found that a measurable and
             non-negligible amount of time was lost calling sprintf()
             in url.c.  Replacing sprintf with inline calls to
-            strcpy() and long_to_string() made a difference.
+            strcpy() and number_to_string() made a difference.
             --hniksic */
          memcpy (filename_plus_orig_suffix, *hstat.local_file, filename_len);
          memcpy (filename_plus_orig_suffix + filename_len,
@@ -1962,8 +2070,6 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
         *hstat.local_file to tack on ".html". */
       if (!opt.output_document)
        locf = *hstat.local_file;
-      else
-       locf = opt.output_document;
 
       /* Time?  */
       tms = time_str (NULL);
@@ -1974,12 +2080,35 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
        {
        case HERR: case HEOF: case CONSOCKERR: case CONCLOSED:
        case CONERROR: case READERR: case WRITEFAILED:
-       case RANGEERR:
+       case RANGEERR: case FOPEN_EXCL_ERR:
          /* 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);
+         if (err == FOPEN_EXCL_ERR)
+           {
+             /* Re-determine the file name. */
+             if (local_file && *local_file)
+               {
+                 xfree (*local_file);
+                 *local_file = url_file_name (u);
+                 hstat.local_file = local_file;
+               }
+             else
+               {
+                 xfree (dummy);
+                 dummy = url_file_name (u);
+                 hstat.local_file = &dummy;
+               }
+             /* be honest about where we will save the file */
+             if (local_file && opt.output_document)
+               *local_file = HYPHENP (opt.output_document) ? NULL : xstrdup (opt.output_document);
+             if (!opt.output_document)
+               locf = *hstat.local_file;
+             else
+               locf = opt.output_document;
+           }
          continue;
          break;
        case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED: 
@@ -2044,7 +2173,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
              xfree (hurl);
            }
          logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
-                    tms, hstat.statcode, hstat.error);
+                    tms, hstat.statcode, escnonprint (hstat.error));
          logputs (LOG_VERBOSE, "\n");
          free_hstat (&hstat);
          xfree_null (dummy);
@@ -2096,7 +2225,8 @@ Server file no newer than local file `%s' -- not retrieving.\n\n"),
                }
              else if (tml >= tmr)
                logprintf (LOG_VERBOSE, _("\
-The sizes do not match (local %ld) -- retrieving.\n"), local_size);
+The sizes do not match (local %s) -- retrieving.\n"),
+                          number_to_static_string (local_size));
              else
                logputs (LOG_VERBOSE,
                         _("Remote file is newer, retrieving.\n"));
@@ -2128,7 +2258,8 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
 
       if (opt.spider)
        {
-         logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode, hstat.error);
+         logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode,
+                    escnonprint (hstat.error));
          xfree_null (dummy);
          return RETROK;
        }
@@ -2140,11 +2271,16 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          if (*dt & RETROKF)
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - `%s' saved [%ld/%ld]\n\n"),
-                        tms, tmrate, locf, hstat.len, hstat.contlen);
+                        _("%s (%s) - `%s' saved [%s/%s]\n\n"),
+                        tms, tmrate, locf,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen));
              logprintf (LOG_NONVERBOSE,
-                        "%s URL:%s [%ld/%ld] -> \"%s\" [%d]\n",
-                        tms, u->url, hstat.len, hstat.contlen, locf, count);
+                        "%s URL:%s [%s/%s] -> \"%s\" [%d]\n",
+                        tms, u->url,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen),
+                        locf, count);
            }
          ++opt.numurls;
          total_downloaded_bytes += hstat.len;
@@ -2167,11 +2303,13 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
              if (*dt & RETROKF)
                {
                  logprintf (LOG_VERBOSE,
-                            _("%s (%s) - `%s' saved [%ld]\n\n"),
-                            tms, tmrate, locf, hstat.len);
+                            _("%s (%s) - `%s' saved [%s]\n\n"),
+                            tms, tmrate, locf,
+                            number_to_static_string (hstat.len));
                  logprintf (LOG_NONVERBOSE,
-                            "%s URL:%s [%ld] -> \"%s\" [%d]\n",
-                            tms, u->url, hstat.len, locf, count);
+                            "%s URL:%s [%s] -> \"%s\" [%d]\n",
+                            tms, u->url, number_to_static_string (hstat.len),
+                            locf, count);
                }
              ++opt.numurls;
              total_downloaded_bytes += hstat.len;
@@ -2190,8 +2328,8 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
                                                 connection too soon */
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - Connection closed at byte %ld. "),
-                        tms, tmrate, hstat.len);
+                        _("%s (%s) - Connection closed at byte %s. "),
+                        tms, tmrate, number_to_static_string (hstat.len));
              printwhat (count, opt.ntry);
              free_hstat (&hstat);
              continue;
@@ -2199,11 +2337,16 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          else if (!opt.kill_longer) /* meaning we got more than expected */
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - `%s' saved [%ld/%ld])\n\n"),
-                        tms, tmrate, locf, hstat.len, hstat.contlen);
+                        _("%s (%s) - `%s' saved [%s/%s])\n\n"),
+                        tms, tmrate, locf,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen));
              logprintf (LOG_NONVERBOSE,
-                        "%s URL:%s [%ld/%ld] -> \"%s\" [%d]\n",
-                        tms, u->url, hstat.len, hstat.contlen, locf, count);
+                        "%s URL:%s [%s/%s] -> \"%s\" [%d]\n",
+                        tms, u->url,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen),
+                        locf, count);
              ++opt.numurls;
              total_downloaded_bytes += hstat.len;
 
@@ -2220,8 +2363,10 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          else                  /* the same, but not accepted */
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - Connection closed at byte %ld/%ld. "),
-                        tms, tmrate, hstat.len, hstat.contlen);
+                        _("%s (%s) - Connection closed at byte %s/%s. "),
+                        tms, tmrate,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen));
              printwhat (count, opt.ntry);
              free_hstat (&hstat);
              continue;
@@ -2232,8 +2377,9 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          if (hstat.contlen == -1)
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - Read error at byte %ld (%s)."),
-                        tms, tmrate, hstat.len, strerror (errno));
+                        _("%s (%s) - Read error at byte %s (%s)."),
+                        tms, tmrate, number_to_static_string (hstat.len),
+                        strerror (errno));
              printwhat (count, opt.ntry);
              free_hstat (&hstat);
              continue;
@@ -2241,8 +2387,10 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          else                  /* hstat.res == -1 and contlen is given */
            {
              logprintf (LOG_VERBOSE,
-                        _("%s (%s) - Read error at byte %ld/%ld (%s). "),
-                        tms, tmrate, hstat.len, hstat.contlen,
+                        _("%s (%s) - Read error at byte %s/%s (%s). "),
+                        tms, tmrate,
+                        number_to_static_string (hstat.len),
+                        number_to_static_string (hstat.contlen),
                         strerror (errno));
              printwhat (count, opt.ntry);
              free_hstat (&hstat);
@@ -2473,7 +2621,7 @@ base64_encode (const char *s, char *store, int length)
 static char *
 basic_authentication_encode (const char *user, const char *passwd)
 {
-  char *t1, *t2, *res;
+  char *t1, *t2;
   int len1 = strlen (user) + 1 + strlen (passwd);
   int len2 = BASE64_LENGTH (len1);
 
@@ -2483,10 +2631,7 @@ basic_authentication_encode (const char *user, const char *passwd)
   t2 = (char *)alloca (len2 + 1);
   base64_encode (t1, t2, len1);
 
-  res = (char *)xmalloc (6 + len2 + 1);
-  sprintf (res, "Basic %s", t2);
-
-  return res;
+  return concat_strings ("Basic ", t2, (char *) 0);
 }
 
 #define SKIP_WS(x) do {                                \
@@ -2504,9 +2649,8 @@ basic_authentication_encode (const char *user, const char *passwd)
 static int
 extract_header_attr (const char *au, const char *attr_name, char **ret)
 {
-  const char *cp, *ep;
-
-  ep = cp = au;
+  const char *ep;
+  const char *cp = au;
 
   if (strncmp (cp, attr_name, strlen (attr_name)) == 0)
     {
@@ -2718,7 +2862,17 @@ create_authorization_line (const char *au, const char *user,
   return NULL;
 }
 \f
+void
+save_cookies (void)
+{
+  if (wget_cookie_jar)
+    cookie_jar_save (wget_cookie_jar, opt.cookies_output);
+}
+
 void
 http_cleanup (void)
 {
+  xfree_null (pconn.host);
+  if (wget_cookie_jar)
+    cookie_jar_delete (wget_cookie_jar);
 }