]> sjero.net Git - wget/blobdiff - src/http.c
[svn] Fix a possible race condition when opening files.
[wget] / src / http.c
index 71e2ba388124e31c3559aae2290538e89549edf3..6bc4f3be5be0a41994c7bbf57bf837b2ed8d9540 100644 (file)
@@ -1,6 +1,5 @@
 /* HTTP support.
-   Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001, 2002
-   Free Software Foundation, Inc.
+   Copyright (C) 2003 Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
 
@@ -76,6 +75,9 @@ extern int errno;
 extern char *version_string;
 extern LARGE_INT total_downloaded_bytes;
 
+extern FILE *output_stream;
+extern int output_stream_regular;
+
 #ifndef MIN
 # define MIN(x, y) ((x) > (y) ? (y) : (x))
 #endif
@@ -90,8 +92,9 @@ struct cookie_jar *wget_cookie_jar;
 /* Some status code validation macros: */
 #define H_20X(x)        (((x) >= 200) && ((x) < 300))
 #define H_PARTIAL(x)    ((x) == HTTP_STATUS_PARTIAL_CONTENTS)
-#define H_REDIRECTED(x) ((x) == HTTP_STATUS_MOVED_PERMANENTLY  \
-                         || (x) == HTTP_STATUS_MOVED_TEMPORARILY \
+#define H_REDIRECTED(x) ((x) == HTTP_STATUS_MOVED_PERMANENTLY          \
+                         || (x) == HTTP_STATUS_MOVED_TEMPORARILY       \
+                        || (x) == HTTP_STATUS_SEE_OTHER                \
                         || (x) == HTTP_STATUS_TEMPORARY_REDIRECT)
 
 /* HTTP/1.0 status codes from RFC1945, provided for reference.  */
@@ -106,14 +109,16 @@ struct cookie_jar *wget_cookie_jar;
 #define HTTP_STATUS_MULTIPLE_CHOICES   300
 #define HTTP_STATUS_MOVED_PERMANENTLY  301
 #define HTTP_STATUS_MOVED_TEMPORARILY  302
+#define HTTP_STATUS_SEE_OTHER           303 /* from HTTP/1.1 */
 #define HTTP_STATUS_NOT_MODIFIED       304
-#define HTTP_STATUS_TEMPORARY_REDIRECT  307
+#define HTTP_STATUS_TEMPORARY_REDIRECT  307 /* from HTTP/1.1 */
 
 /* Client error 4xx.  */
 #define HTTP_STATUS_BAD_REQUEST                400
 #define HTTP_STATUS_UNAUTHORIZED       401
 #define HTTP_STATUS_FORBIDDEN          403
 #define HTTP_STATUS_NOT_FOUND          404
+#define HTTP_STATUS_RANGE_NOT_SATISFIABLE 416
 
 /* Server errors 5xx.  */
 #define HTTP_STATUS_INTERNAL           500
@@ -214,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);
    */
 
@@ -348,6 +354,53 @@ request_free (struct request *req)
   xfree_null (req->headers);
   xfree (req);
 }
+
+/* Send the contents of FILE_NAME to SOCK/SSL.  Make sure that exactly
+   PROMISED_SIZE bytes are sent over the wire -- if the file is
+   longer, read only that much; if the file is shorter, report an error.  */
+
+static int
+post_file (int sock, const char *file_name, wgint promised_size)
+{
+  static char chunk[8192];
+  wgint written = 0;
+  int write_error;
+  FILE *fp;
+
+  DEBUGP (("[writing POST file %s ... ", file_name));
+
+  fp = fopen (file_name, "rb");
+  if (!fp)
+    return -1;
+  while (!feof (fp) && written < promised_size)
+    {
+      int towrite;
+      int length = fread (chunk, 1, sizeof (chunk), fp);
+      if (length == 0)
+       break;
+      towrite = MIN (promised_size - written, length);
+      write_error = fd_write (sock, chunk, towrite, -1);
+      if (write_error < 0)
+       {
+         fclose (fp);
+         return -1;
+       }
+      written += towrite;
+    }
+  fclose (fp);
+
+  /* If we've written less than was promised, report a (probably
+     nonsensical) error rather than break the promise.  */
+  if (written < promised_size)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  assert (written == promised_size);
+  DEBUGP (("done]\n"));
+  return 0;
+}
 \f
 static const char *
 head_terminator (const char *hunk, int oldlen, int peeklen)
@@ -529,8 +582,8 @@ response_header_copy (const struct response *resp, const char *name,
     return 0;
   if (bufsize)
     {
-      int len = MIN (e - b, bufsize);
-      strncpy (buf, b, len);
+      int len = MIN (e - b, bufsize - 1);
+      memcpy (buf, b, len);
       buf[len] = '\0';
     }
   return 1;
@@ -564,7 +617,7 @@ response_status (const struct response *resp, char **message)
 
   if (!resp->headers)
     {
-      /* For a HTTP/0.9 response, always assume 200 response. */
+      /* For a HTTP/0.9 response, assume status 200. */
       if (message)
        *message = xstrdup (_("No headers, assuming HTTP/0.9"));
       return 200;
@@ -581,8 +634,8 @@ response_status (const struct response *resp, char **message)
     return -1;
   p += 4;
 
-  /* "/x.x" (optional because some Gnutella servers have been reported
-     as not sending the "/x.x" part.  */
+  /* Match the HTTP version.  This is optional because Gnutella
+     servers have been reported to not specify HTTP version.  */
   if (p < end && *p == '/')
     {
       ++p;
@@ -653,10 +706,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"
@@ -692,52 +745,32 @@ parse_content_range (const char *hdr, long *first_byte_ptr,
   *entity_length_ptr = num;
   return 1;
 }
-\f
-/* Send the contents of FILE_NAME to SOCK/SSL.  Make sure that exactly
-   PROMISED_SIZE bytes are sent over the wire -- if the file is
-   longer, read only that much; if the file is shorter, report an error.  */
 
-static int
-post_file (int sock, const char *file_name, long promised_size)
-{
-  static char chunk[8192];
-  long written = 0;
-  int write_error;
-  FILE *fp;
+/* Read the body of the request, but don't store it anywhere and don't
+   display a progress gauge.  This is useful for reading the error
+   responses whose bodies don't need to be displayed or logged, but
+   which need to be read anyway.  */
 
-  DEBUGP (("[writing POST file %s ... ", file_name));
-
-  fp = fopen (file_name, "rb");
-  if (!fp)
-    return -1;
-  while (!feof (fp) && written < promised_size)
-    {
-      int towrite;
-      int length = fread (chunk, 1, sizeof (chunk), fp);
-      if (length == 0)
-       break;
-      towrite = MIN (promised_size - written, length);
-      write_error = fd_write (sock, chunk, towrite, -1);
-      if (write_error < 0)
-       {
-         fclose (fp);
-         return -1;
-       }
-      written += towrite;
-    }
-  fclose (fp);
+static void
+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
+     used.  (#### This is not the case with HTTP/1.1 where they can
+     still be used with the magic of the "chunked" transfer!)  */
+  if (contlen == -1)
+    return;
+  DEBUGP (("Skipping %s bytes of body data... ", number_to_static_string (contlen)));
 
-  /* If we've written less than was promised, report a (probably
-     nonsensical) error rather than break the promise.  */
-  if (written < promised_size)
+  while (contlen > 0)
     {
-      errno = EINVAL;
-      return -1;
+      char dlbuf[512];
+      int ret = fd_read (fd, dlbuf, MIN (contlen, sizeof (dlbuf)), -1);
+      if (ret <= 0)
+       return;
+      contlen -= ret;
     }
-
-  assert (written == promised_size);
-  DEBUGP (("done]\n"));
-  return 0;
+  DEBUGP (("done.\n"));
 }
 \f
 /* Persistent connections.  Currently, we cache the most recently used
@@ -925,7 +958,10 @@ persistent_available_p (const char *host, int port, int ssl,
       if (pconn_active && (fd) == pconn.socket)        \
        invalidate_persistent ();               \
       else                                     \
-       fd_close (fd);                          \
+       {                                       \
+         fd_close (fd);                        \
+         fd = -1;                              \
+       }                                       \
     }                                          \
 } while (0)
 
@@ -939,17 +975,16 @@ 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 */
-  double dltime;               /* time of the download in msecs */
-  int no_truncate;             /* whether truncating the file is
-                                  forbidden. */
+  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. */
 };
@@ -1000,11 +1035,12 @@ 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;
 
   int sock = -1;
+  int flags;
 
   /* Whether authorization has been already tried. */
   int auth_tried_already = 0;
@@ -1021,43 +1057,43 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
      is done. */
   int keep_alive;
 
-  /* Flag that detects having received a keep-alive response.  */
-  int keep_alive_confirmed;
-
   /* Whether keep-alive should be inhibited. */
-  int inhibit_keep_alive = !opt.http_keep_alive;
+  int inhibit_keep_alive = !opt.http_keep_alive || opt.ignore_length;
 
   /* Headers sent when using POST. */
-  long post_data_size = 0;
+  wgint post_data_size = 0;
 
   int host_lookup_failed = 0;
 
 #ifdef HAVE_SSL
-  /* Initialize the SSL context.  After the first run, this is a
-     no-op.  */
-  switch (ssl_init ())
+  if (u->scheme == SCHEME_HTTPS)
     {
-    case SSLERRCTXCREATE:
-      /* this is fatal */
-      logprintf (LOG_NOTQUIET, _("Failed to set up an SSL context\n"));
-      return SSLERRCTXCREATE;
-    case SSLERRCERTFILE:
-      /* try without certfile */
-      logprintf (LOG_NOTQUIET,
-                _("Failed to load certificates from %s\n"),
-                opt.sslcertfile);
-      logprintf (LOG_NOTQUIET,
-                _("Trying without the specified certificate\n"));
-      break;
-    case SSLERRCERTKEY:
-      logprintf (LOG_NOTQUIET,
-                _("Failed to get certificate key from %s\n"),
-                opt.sslcertkey);
-      logprintf (LOG_NOTQUIET,
-                _("Trying without the specified certificate\n"));
-      break;
-    default:
-      break;
+      /* Initialize the SSL context.  After this has once been done,
+        it becomes a no-op.  */
+      switch (ssl_init ())
+       {
+       case SSLERRCTXCREATE:
+         /* this is fatal */
+         logprintf (LOG_NOTQUIET, _("Failed to set up an SSL context\n"));
+         return SSLERRCTXCREATE;
+       case SSLERRCERTFILE:
+         /* try without certfile */
+         logprintf (LOG_NOTQUIET,
+                    _("Failed to load certificates from %s\n"),
+                    opt.sslcertfile);
+         logprintf (LOG_NOTQUIET,
+                    _("Trying without the specified certificate\n"));
+         break;
+       case SSLERRCERTKEY:
+         logprintf (LOG_NOTQUIET,
+                    _("Failed to get certificate key from %s\n"),
+                    opt.sslcertkey);
+         logprintf (LOG_NOTQUIET,
+                    _("Trying without the specified certificate\n"));
+         break;
+       default:
+         break;
+       }
     }
 #endif /* HAVE_SSL */
 
@@ -1078,35 +1114,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
   conn = u;
 
-  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
-        normally the "permanent" ones, so command-line args
-        should take precedence.  */
-      if (opt.proxy_user && opt.proxy_passwd)
-       {
-         proxy_user = opt.proxy_user;
-         proxy_passwd = opt.proxy_passwd;
-       }
-      else
-       {
-         proxy_user = proxy->user;
-         proxy_passwd = proxy->passwd;
-       }
-      /* #### This does not appear right.  Can't the proxy request,
-        say, `Digest' authentication?  */
-      if (proxy_user && proxy_passwd)
-       proxyauth = basic_authentication_encode (proxy_user, proxy_passwd);
-
-      /* If we're using a proxy, we will be connecting to the proxy
-        server.  */
-      conn = proxy;
-    }
-
   /* Prepare the request to send. */
 
   req = request_new ();
@@ -1128,7 +1135,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
@@ -1170,6 +1179,41 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                          rel_value);
     }
 
+  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
+        normally the "permanent" ones, so command-line args
+        should take precedence.  */
+      if (opt.proxy_user && opt.proxy_passwd)
+       {
+         proxy_user = opt.proxy_user;
+         proxy_passwd = opt.proxy_passwd;
+       }
+      else
+       {
+         proxy_user = proxy->user;
+         proxy_passwd = proxy->passwd;
+       }
+      /* #### This does not appear right.  Can't the proxy request,
+        say, `Digest' authentication?  */
+      if (proxy_user && proxy_passwd)
+       proxyauth = basic_authentication_encode (proxy_user, proxy_passwd);
+
+      /* If we're using a proxy, we will be connecting to the proxy
+        server.  */
+      conn = proxy;
+
+      /* Proxy authorization over SSL is handled below. */
+#ifdef HAVE_SSL
+      if (u->scheme != SCHEME_HTTPS)
+#endif
+       request_set_header (req, "Proxy-Authorization", proxyauth, rel_value);
+    }
+
   {
     /* Whether we need to print the host header with braces around
        host, e.g. "Host: [3ffe:8100:200:2::2]:1234" instead of the
@@ -1218,7 +1262,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
            }
        }
       request_set_header (req, "Content-Length",
-                         aprintf ("Content-Length: %ld", post_data_size),
+                         xstrdup (number_to_static_string (post_data_size)),
                          rel_value);
     }
 
@@ -1236,7 +1280,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
      for the Digest authorization scheme.)  */
 
   keep_alive = 0;
-  keep_alive_confirmed = 0;
 
   /* Establish the connection.  */
 
@@ -1371,7 +1414,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       else if (opt.post_file_name && post_data_size != 0)
        write_error = post_file (sock, opt.post_file_name, post_data_size);
     }
-  DEBUGP (("---request end---\n"));
 
   if (write_error < 0)
     {
@@ -1383,7 +1425,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     }
   logprintf (LOG_VERBOSE, _("%s request sent, awaiting response... "),
             proxy ? "Proxy" : "HTTP");
-  contlen = contrange = -1;
+  contlen = -1;
+  contrange = 0;
   type = NULL;
   statcode = -1;
   *dt &= ~RETROKF;
@@ -1422,11 +1465,45 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       print_server_response (resp, "  ");
     }
 
+  if (!opt.ignore_length
+      && response_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))
+       keep_alive = 1;
+      else if (response_header_copy (resp, "Connection", hdrval,
+                                    sizeof (hdrval)))
+       {
+         if (0 == strcasecmp (hdrval, "Keep-Alive"))
+           keep_alive = 1;
+       }
+    }
+  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.  */
-      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                  might be more bytes in the body. */
+      skip_short_body (sock, contlen);
+      CLOSE_FINISH (sock);
       if (auth_tried_already || !(user && passwd))
        {
          /* If we have tried it already, then there is not point
@@ -1476,8 +1553,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   else
     hs->error = xstrdup (message);
 
-  if (response_header_copy (resp, "Content-Length", hdrval, sizeof (hdrval)))
-    contlen = strtol (hdrval, NULL, 10);
   type = response_header_strdup (resp, "Content-Type");
   if (type)
     {
@@ -1504,31 +1579,13 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   }
   if (response_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;
     }
-
-  /* Check for keep-alive related responses. */
-  if (!inhibit_keep_alive && contlen != -1)
-    {
-      if (response_header_copy (resp, "Keep-Alive", NULL, 0))
-       keep_alive = 1;
-      else if (response_header_copy (resp, "Connection", hdrval,
-                                    sizeof (hdrval)))
-       {
-         if (0 == strcasecmp (hdrval, "Keep-Alive"))
-           keep_alive = 1;
-       }
-    }
   response_free (resp);
 
-  if (keep_alive)
-    /* The server has promised that it will not close the connection
-       when we're done.  This means that we can register it.  */
-    register_persistent (conn->host, conn->port, sock, using_ssl);
-
   /* 20x responses are counted among successful by default.  */
   if (H_20X (statcode))
     *dt |= RETROKF;
@@ -1549,8 +1606,9 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                     _("Location: %s%s\n"),
                     hs->newloc ? hs->newloc : _("unspecified"),
                     hs->newloc ? _(" [following]") : "");
-         CLOSE_INVALIDATE (sock);      /* would be CLOSE_FINISH, but there
-                                          might be more bytes in the body. */
+         if (keep_alive)
+           skip_short_body (sock, contlen);
+         CLOSE_FINISH (sock);
          xfree_null (type);
          return NEWLOCATION;
        }
@@ -1587,83 +1645,33 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        }
     }
 
-  if (contrange == -1)
+  if (statcode == HTTP_STATUS_RANGE_NOT_SATISFIABLE)
     {
-      /* We did not get a content-range header.  This means that the
-        server did not honor our `Range' request.  Normally, this
-        means we should reset hs->restval and continue normally.  */
-
-      /* However, if `-c' is used, we need to be a bit more careful:
-
-         1. If `-c' is specified and the file already existed when
-         Wget was started, it would be a bad idea for us to start
-         downloading it from scratch, effectively truncating it.  I
-         believe this cannot happen unless `-c' was specified.
-
-        2. If `-c' is used on a file that is already fully
-        downloaded, we're requesting bytes after the end of file,
-        which can result in server not honoring `Range'.  If this is
-        the case, `Content-Length' will be equal to the length of the
-        file.  */
-      if (opt.always_rest)
-       {
-         /* Check for condition #2. */
-         if (hs->restval > 0               /* restart was requested. */
-             && contlen != -1              /* we got content-length. */
-             && hs->restval >= contlen     /* file fully downloaded
-                                              or has shrunk.  */
-             )
-           {
-             logputs (LOG_VERBOSE, _("\
+      /* If `-c' is in use and the file has been fully downloaded (or
+        the remote file has shrunk), Wget effectively requests bytes
+        after the end of file and the server response with 416.  */
+      logputs (LOG_VERBOSE, _("\
 \n    The file is already fully retrieved; nothing to do.\n\n"));
-             /* In case the caller inspects. */
-             hs->len = contlen;
-             hs->res = 0;
-             /* Mark as successfully retrieved. */
-             *dt |= RETROKF;
-             xfree_null (type);
-             CLOSE_INVALIDATE (sock);  /* would be CLOSE_FINISH, but there
-                                          might be more bytes in the body. */
-             return RETRUNNEEDED;
-           }
-
-         /* Check for condition #1. */
-         if (hs->no_truncate)
-           {
-             logprintf (LOG_NOTQUIET,
-                        _("\
-\n\
-Continued download failed on this file, which conflicts with `-c'.\n\
-Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
-             xfree_null (type);
-             CLOSE_INVALIDATE (sock);
-             return CONTNOTSUPPORTED;
-           }
-
-         /* Fallthrough */
-       }
-
-      hs->restval = 0;
+      /* In case the caller inspects. */
+      hs->len = contlen;
+      hs->res = 0;
+      /* Mark as successfully retrieved. */
+      *dt |= RETROKF;
+      xfree_null (type);
+      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
+                                  might be more bytes in the body. */
+      return RETRUNNEEDED;
     }
-  else if (contrange != hs->restval ||
-          (H_PARTIAL (statcode) && contrange == -1))
+  if ((contrange != 0 && contrange != hs->restval)
+      || (H_PARTIAL (statcode) && !contrange))
     {
-      /* This means the whole request was somehow misunderstood by the
-        server.  Bail out.  */
+      /* The Range request was somehow misunderstood by the server.
+        Bail out.  */
       xfree_null (type);
       CLOSE_INVALIDATE (sock);
       return RANGEERR;
     }
-
-  if (hs->restval)
-    {
-      if (contlen != -1)
-       contlen += contrange;
-      else
-       contrange = -1;        /* If conent-length was not sent,
-                                 content-range will be ignored.  */
-    }
-  hs->contlen = contlen;
+  hs->contlen = contlen + contrange;
 
   if (opt.verbose)
     {
@@ -1675,10 +1683,9 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
          logputs (LOG_VERBOSE, _("Length: "));
          if (contlen != -1)
            {
-             logputs (LOG_VERBOSE, legible (contlen));
-             if (contrange != -1)
-               logprintf (LOG_VERBOSE, _(" (%s to go)"),
-                          legible (contlen - contrange));
+             logputs (LOG_VERBOSE, legible (contlen + contrange));
+             if (contrange)
+               logprintf (LOG_VERBOSE, _(" (%s to go)"), legible (contlen));
            }
          else
            logputs (LOG_VERBOSE,
@@ -1699,68 +1706,69 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
       hs->len = 0L;
       hs->res = 0;
       xfree_null (type);
-      CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                  might be more bytes in the body. */
+      /* Pre-1.10 Wget used CLOSE_INVALIDATE here.  Now we trust the
+        servers not to send body in response to a HEAD request.  If
+        you encounter such a server (more likely a broken CGI), use
+        `--no-http-keep-alive'.  */
+      CLOSE_FINISH (sock);
       return RETRFINISHED;
     }
 
   /* Open the local file.  */
-  if (!opt.dfp)
+  if (!output_stream)
     {
       mkalldirs (*hs->local_file);
       if (opt.backups)
        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));
-         CLOSE_INVALIDATE (sock); /* would be CLOSE_FINISH, but there
-                                     might be more bytes in the body. */
+         CLOSE_INVALIDATE (sock);
          return FOPENERR;
        }
     }
-  else                         /* opt.dfp */
-    {
-      extern int global_download_count;
-      fp = opt.dfp;
-      /* To ensure that repeated "from scratch" downloads work for -O
-        files, we rewind the file pointer, unless restval is
-        non-zero.  (This works only when -O is used on regular files,
-        but it's still a valuable feature.)
-
-        However, this loses when more than one URL is specified on
-        the command line the second rewinds eradicates the contents
-        of the first download.  Thus we disable the above trick for
-        all the downloads except the very first one.
-
-         #### A possible solution to this would be to remember the
-        file position in the output document and to seek to that
-        position, instead of rewinding.
-
-         We don't truncate stdout, since that breaks
-        "wget -O - [...] >> foo".
-      */
-      if (!hs->restval && global_download_count == 0 && opt.dfp != stdout)
-       {
-         /* This will silently fail for streams that don't correspond
-            to regular files, but that's OK.  */
-         rewind (fp);
-         /* ftruncate is needed because opt.dfp is opened in append
-            mode if opt.always_rest is set.  */
-         ftruncate (fileno (fp), 0);
-         clearerr (fp);
-       }
-    }
+  else
+    fp = output_stream;
 
-  /* #### This confuses the code that checks for file size.  There
-     should be some overhead information.  */
+  /* #### This confuses the timestamping code that checks for file
+     size.  Maybe we should save some additional information?  */
   if (opt.save_headers)
     fwrite (head, 1, strlen (head), fp);
 
-  /* Get the contents of the document.  */
-  hs->res = fd_read_body (sock, fp, &hs->len, hs->restval,
-                         (contlen != -1 ? contlen : 0),
-                         keep_alive, &hs->dltime);
+  /* Download the request body.  */
+  flags = 0;
+  if (keep_alive)
+    flags |= rb_read_exactly;
+  if (hs->restval > 0 && contrange == 0)
+    /* If the server ignored our range request, instruct fd_read_body
+       to skip the first RESTVAL bytes of body.  */
+    flags |= rb_skip_startpos;
+  hs->len = hs->restval;
+  hs->rd_size = 0;
+  hs->res = fd_read_body (sock, fp, contlen != -1 ? contlen : 0,
+                         hs->restval, &hs->rd_size, &hs->len, &hs->dltime,
+                         flags);
 
   if (hs->res >= 0)
     CLOSE_FINISH (sock);
@@ -1772,7 +1780,7 @@ Refusing to truncate existing file `%s'.\n\n"), *hs->local_file);
        error here.  Checking the result of fwrite() is not enough --
        errors could go unnoticed!  */
     int flush_res;
-    if (!opt.dfp)
+    if (!output_stream)
       flush_res = fclose (fp);
     else
       flush_res = fflush (fp);
@@ -1797,10 +1805,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
@@ -1825,10 +1833,12 @@ http_loop (struct url *u, char **newloc, char **local_file, const char *referer,
   if (strchr (u->url, '*'))
     logputs (LOG_VERBOSE, _("Warning: wildcards not supported in HTTP.\n"));
 
+  xzero (hstat);
+
   /* Determine the local filename.  */
   if (local_file && *local_file)
     hstat.local_file = local_file;
-  else if (local_file)
+  else if (local_file && !opt.output_document)
     {
       *local_file = url_file_name (u);
       hstat.local_file = local_file;
@@ -1837,6 +1847,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)
@@ -1888,7 +1901,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,
@@ -1925,7 +1938,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
     }
   /* Reset the counter.  */
   count = 0;
-  *dt = 0 | ACCEPTRANGES;
+  *dt = 0;
   /* THE loop */
   do
     {
@@ -1938,14 +1951,14 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
       if (opt.verbose)
        {
          char *hurl = url_string (u, 1);
-         char tmp[15];
+         char tmp[256];
          strcpy (tmp, "        ");
          if (count > 1)
            sprintf (tmp, _("(try:%2d)"), count);
          logprintf (LOG_VERBOSE, "--%s--  %s\n  %s => `%s'\n",
                     tms, hurl, tmp, locf);
 #ifdef WINDOWS
-         ws_changetitle (hurl, 1);
+         ws_changetitle (hurl);
 #endif
          xfree (hurl);
        }
@@ -1957,21 +1970,15 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
        *dt |= HEAD_ONLY;
       else
        *dt &= ~HEAD_ONLY;
-      /* Assume no restarting.  */
-      hstat.restval = 0L;
+
       /* Decide whether or not to restart.  */
-      if (((count > 1 && (*dt & ACCEPTRANGES)) || opt.always_rest)
-         /* #### this calls access() and then stat(); could be optimized. */
-         && file_exists_p (locf))
-       if (stat (locf, &st) == 0 && S_ISREG (st.st_mode))
-         hstat.restval = st.st_size;
-
-      /* In `-c' is used and the file is existing and non-empty,
-        refuse to truncate it if the server doesn't support continued
-        downloads.  */
-      hstat.no_truncate = 0;
-      if (opt.always_rest && hstat.restval)
-       hstat.no_truncate = 1;
+      hstat.restval = 0;
+      if (count > 1)
+       hstat.restval = hstat.len; /* continue where we left off */
+      else if (opt.always_rest
+              && stat (locf, &st) == 0
+              && S_ISREG (st.st_mode))
+       hstat.restval = st.st_size;
 
       /* Decide whether to send the no-cache directive.  We send it in
         two cases:
@@ -1995,8 +2002,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);
@@ -2007,12 +2012,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: 
@@ -2129,7 +2157,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"));
@@ -2149,7 +2178,7 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          const char *fl = NULL;
          if (opt.output_document)
            {
-             if (opt.od_known_regular)
+             if (output_stream_regular)
                fl = opt.output_document;
            }
          else
@@ -2166,18 +2195,23 @@ The sizes do not match (local %ld) -- retrieving.\n"), local_size);
          return RETROK;
        }
 
-      tmrate = retr_rate (hstat.len - hstat.restval, hstat.dltime, 0);
+      tmrate = retr_rate (hstat.rd_size, hstat.dltime, 0);
 
       if (hstat.len == hstat.contlen)
        {
          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;
@@ -2200,11 +2234,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;
@@ -2223,8 +2259,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;
@@ -2232,11 +2268,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;
 
@@ -2253,8 +2294,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;
@@ -2265,8 +2308,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;
@@ -2274,8 +2318,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);