]> sjero.net Git - wget/blobdiff - src/http.c
[svn] Fix K&R incompatibilities reported by `gcc -Wtraditional'.
[wget] / src / http.c
index c54eadf499bf92e02785cf1ef462f95159a9d9d4..97a773a1e2d720abe45ac5041c3245e0477d49a7 100644 (file)
@@ -64,7 +64,7 @@ extern int errno;
 #include "connect.h"
 #include "netrc.h"
 #ifdef HAVE_SSL
-# include "gen_sslfunc.h"
+# include "ssl.h"
 #endif
 #ifdef ENABLE_NTLM
 # include "http-ntlm.h"
@@ -202,7 +202,7 @@ release_header (struct request_header *hdr)
 /* Set the request named NAME to VALUE.  Specifically, this means that
    a "NAME: VALUE\r\n" header line will be used in the request.  If a
    header with the same name previously existed in the request, its
-   value will be replaced by this one.
+   value will be replaced by this one.  A NULL value means do nothing.
 
    RELEASE_POLICY determines whether NAME and VALUE should be released
    (freed) with request_free.  Allowed values are:
@@ -233,6 +233,7 @@ request_set_header (struct request *req, char *name, char *value,
 {
   struct request_header *hdr;
   int i;
+
   if (!value)
     {
       /* A NULL value is a no-op; if freeing the name is requested,
@@ -241,6 +242,7 @@ request_set_header (struct request *req, char *name, char *value,
        xfree (name);
       return;
     }
+
   for (i = 0; i < req->hcount; i++)
     {
       hdr = &req->headers[i];
@@ -257,11 +259,10 @@ request_set_header (struct request *req, char *name, char *value,
 
   /* Install new header. */
 
-  if (req->hcount >= req->hcount)
+  if (req->hcount >= req->hcapacity)
     {
       req->hcapacity <<= 1;
-      req->headers = xrealloc (req->headers,
-                              req->hcapacity * sizeof (struct request_header));
+      req->headers = xrealloc (req->headers, req->hcapacity * sizeof (*hdr));
     }
   hdr = &req->headers[req->hcount++];
   hdr->name = name;
@@ -288,6 +289,29 @@ request_set_user_header (struct request *req, const char *header)
   request_set_header (req, xstrdup (name), (char *) p, rel_name);
 }
 
+/* Remove the header with specified name from REQ.  Returns 1 if the
+   header was actually removed, 0 otherwise.  */
+
+static int
+request_remove_header (struct request *req, char *name)
+{
+  int i;
+  for (i = 0; i < req->hcount; i++)
+    {
+      struct request_header *hdr = &req->headers[i];
+      if (0 == strcasecmp (name, hdr->name))
+       {
+         release_header (hdr);
+         /* Move the remaining headers by one. */
+         if (i < req->hcount - 1)
+           memmove (hdr, hdr + 1, (req->hcount - i - 1) * sizeof (*hdr));
+         --req->hcount;
+         return 1;
+       }
+    }
+  return 0;
+}
+
 #define APPEND(p, str) do {                    \
   int A_len = strlen (str);                    \
   memcpy (p, str, A_len);                      \
@@ -344,7 +368,7 @@ request_send (const struct request *req, int fd)
 
   /* Send the request to the server. */
 
-  write_error = fd_write (fd, request_string, size - 1, -1);
+  write_error = fd_write (fd, request_string, size - 1, -1.0);
   if (write_error < 0)
     logprintf (LOG_VERBOSE, _("Failed writing HTTP request: %s.\n"),
               strerror (errno));
@@ -388,7 +412,7 @@ post_file (int sock, const char *file_name, wgint promised_size)
       if (length == 0)
        break;
       towrite = MIN (promised_size - written, length);
-      write_error = fd_write (sock, chunk, towrite, -1);
+      write_error = fd_write (sock, chunk, towrite, -1.0);
       if (write_error < 0)
        {
          fclose (fp);
@@ -816,7 +840,7 @@ skip_short_body (int fd, wgint contlen)
 
   while (contlen > 0)
     {
-      int ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);
+      int ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1.0);
       if (ret <= 0)
        {
          /* Don't normally report the error since this is an
@@ -855,6 +879,12 @@ static struct {
   /* Whether a ssl handshake has occoured on this connection.  */
   int ssl;
 
+  /* Whether the connection was authorized.  This is only done by
+     NTLM, which authorizes *connections* rather than individual
+     requests.  (That practice is peculiar for HTTP, but it is a
+     useful optimization.)  */
+  int authorized;
+
 #ifdef ENABLE_NTLM
   /* NTLM data of the current connection.  */
   struct ntlmdata ntlm;
@@ -909,6 +939,7 @@ register_persistent (const char *host, int port, int fd, int ssl)
   pconn.host = xstrdup (host);
   pconn.port = port;
   pconn.ssl = ssl;
+  pconn.authorized = 0;
 
   DEBUGP (("Registered socket %d for persistent reuse.\n", fd));
 }
@@ -1071,9 +1102,9 @@ free_hstat (struct http_stat *hs)
 
 static char *create_authorization_line PARAMS ((const char *, const char *,
                                                const char *, const char *,
-                                               const char *));
+                                               const char *, int *));
 static char *basic_authentication_encode PARAMS ((const char *, const char *));
-static int known_authentication_scheme_p PARAMS ((const char *));
+static int known_authentication_scheme_p PARAMS ((const char *, const char *));
 
 time_t http_atotm PARAMS ((const char *));
 
@@ -1082,6 +1113,14 @@ time_t http_atotm PARAMS ((const char *));
    && (ISSPACE (line[sizeof (string_constant) - 1])                    \
        || !line[sizeof (string_constant) - 1]))
 
+#define SET_USER_AGENT(req)                                            \
+  if (opt.useragent)                                                   \
+    request_set_header (req, "User-Agent", opt.useragent, rel_none);   \
+  else                                                                 \
+    request_set_header (req, "User-Agent",                             \
+                       aprintf ("Wget/%s", version_string), rel_value);
+
+
 /* Retrieve a document through HTTP protocol.  It recognizes status
    code, and correctly handles redirections.  It closes the network
    socket.  If it receives an error from the functions below it, it
@@ -1109,8 +1148,12 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   int sock = -1;
   int flags;
 
-  /* Whether authorization has been already tried. */
-  int auth_tried_already;
+  /* Set to 1 when the authorization has failed permanently and should
+     not be tried again. */
+  int auth_finished = 0;
+
+  /* Whether NTLM authentication is used for this request. */
+  int ntlm_seen = 0;
 
   /* Whether our connection to the remote host is through SSL.  */
   int using_ssl = 0;
@@ -1144,29 +1187,12 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
     {
       /* Initialize the SSL context.  After this has once been done,
         it becomes a no-op.  */
-      switch (ssl_init ())
+      if (!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 */
+         scheme_disable (SCHEME_HTTPS);
          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;
+                    _("Disabling SSL due to encountered errors.\n"));
+         return SSLINITFAILED;
        }
     }
 #endif /* HAVE_SSL */
@@ -1176,8 +1202,6 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        know the local filename so we can save to it. */
     assert (*hs->local_file != NULL);
 
-  auth_tried_already = 0;
-
   /* Initialize certain elements of struct http_stat.  */
   hs->len = 0;
   hs->contlen = -1;
@@ -1212,19 +1236,15 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                        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
-    request_set_header (req, "User-Agent",
-                       aprintf ("Wget/%s", version_string), rel_value);
+  SET_USER_AGENT (req);
   request_set_header (req, "Accept", "*/*", rel_none);
 
   /* Find the username and password for authentication. */
   user = u->user;
   passwd = u->passwd;
   search_netrc (u->host, (const char **)&user, (const char **)&passwd, 0);
-  user = user ? user : opt.http_user;
-  passwd = passwd ? passwd : opt.http_passwd;
+  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)
     {
@@ -1382,6 +1402,11 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"),
                     escnonprint (pconn.host), pconn.port);
          DEBUGP (("Reusing fd %d.\n", sock));
+         if (pconn.authorized)
+           /* If the connection is already authorized, the "Basic"
+              authorization added by code above is unnecessary and
+              only hurts us.  */
+           request_remove_header (req, "Authorization");
        }
     }
 
@@ -1417,6 +1442,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          struct request *connreq = request_new ();
          request_set_method (connreq, "CONNECT",
                              aprintf ("%s:%d", u->host, u->port));
+         SET_USER_AGENT (connreq);
          if (proxyauth)
            {
              request_set_header (connreq, "Proxy-Authorization",
@@ -1426,6 +1452,10 @@ 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.  */
 
          write_error = request_send (connreq, sock);
          request_free (connreq);
@@ -1493,7 +1523,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       if (opt.post_data)
        {
          DEBUGP (("[POST data: %s]\n", opt.post_data));
-         write_error = fd_write (sock, opt.post_data, post_data_size, -1);
+         write_error = fd_write (sock, opt.post_data, post_data_size, -1.0);
        }
       else if (opt.post_file_name && post_data_size != 0)
        write_error = post_file (sock, opt.post_file_name, post_data_size);
@@ -1584,11 +1614,15 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
   if (statcode == HTTP_STATUS_UNAUTHORIZED)
     {
       /* Authorization is required.  */
-      if (skip_short_body (sock, contlen))
-       CLOSE_FINISH (sock);
-      else
-       CLOSE_INVALIDATE (sock);
-      if (auth_tried_already || !(user && passwd))
+      if (keep_alive)
+       {
+         if (skip_short_body (sock, contlen))
+           CLOSE_FINISH (sock);
+         else
+           CLOSE_INVALIDATE (sock);
+       }
+      pconn.authorized = 0;
+      if (auth_finished || !(user && passwd))
        {
          /* If we have tried it already, then there is not point
             retrying it.  */
@@ -1596,13 +1630,26 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
        }
       else
        {
-         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.  */
+         /* IIS sometimes sends two instances of WWW-Authenticate
+            header, one with the keyword "negotiate", and other with
+            useful data.  Loop over all occurrences of this header
+            and use 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))
+             {
+               www_authenticate = strdupdelim (wabeg, waend);
+               break;
+             }
+         /* If the authentication header is missing or recognized, or
+            if the authentication scheme is "Basic" (which we send by
+            default), there's no sense in retrying.  */
          if (!www_authenticate
-             || !known_authentication_scheme_p (www_authenticate)
              || BEGINS_WITH (www_authenticate, "Basic"))
            {
              xfree_null (www_authenticate);
@@ -1611,14 +1658,16 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
          else
            {
              char *pth;
-             auth_tried_already = 1;
              pth = url_full_path (u);
              request_set_header (req, "Authorization",
                                  create_authorization_line (www_authenticate,
                                                             user, passwd,
                                                             request_method (req),
-                                                            pth),
+                                                            pth,
+                                                            &auth_finished),
                                  rel_value);
+             if (BEGINS_WITH (www_authenticate, "NTLM"))
+               ntlm_seen = 1;
              xfree (pth);
              xfree (www_authenticate);
              goto retry_with_auth;
@@ -1627,6 +1676,12 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
       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 = 1;
+    }
   request_free (req);
 
   hs->statcode = statcode;
@@ -1654,6 +1709,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
 
   /* Handle (possibly multiple instances of) the Set-Cookie header. */
   {
+    char *pth = NULL;
     int scpos;
     const char *scbeg, *scend;
     /* The jar should have been created by now. */
@@ -1663,10 +1719,16 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
                                      &scbeg, &scend)) != -1;
         ++scpos)
       {
-       char *set_cookie = strdupdelim (scbeg, scend);
-       cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port, u->path,
+       char *set_cookie; BOUNDED_TO_ALLOCA (scbeg, scend, set_cookie);
+       if (pth == NULL)
+         {
+           /* u->path doesn't begin with /, which cookies.c expects. */
+           pth = (char *) alloca (1 + strlen (u->path) + 1);
+           pth[0] = '/';
+           strcpy (pth + 1, u->path);
+         }
+       cookie_handle_set_cookie (wget_cookie_jar, u->host, u->port, pth,
                                  set_cookie);
-       xfree (set_cookie);
       }
   }
 
@@ -2151,14 +2213,12 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
                locf = opt.output_document;
            }
          continue;
-         break;
        case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED: 
-       case SSLERRCTXCREATE: case CONTNOTSUPPORTED:
+       case SSLINITFAILED: case CONTNOTSUPPORTED:
          /* Fatal errors just return from the function.  */
          free_hstat (&hstat);
          xfree_null (dummy);
          return err;
-         break;
        case FWRITEERR: case FOPENERR:
          /* Another fatal error.  */
          logputs (LOG_VERBOSE, "\n");
@@ -2167,7 +2227,6 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
          free_hstat (&hstat);
          xfree_null (dummy);
          return err;
-         break;
        case CONSSLERR:
          /* Another fatal error.  */
          logputs (LOG_VERBOSE, "\n");
@@ -2175,7 +2234,6 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
          free_hstat (&hstat);
          xfree_null (dummy);
          return err;
-         break;
        case NEWLOCATION:
          /* Return the new location to the caller.  */
          if (!hstat.newloc)
@@ -2190,13 +2248,11 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
          free_hstat (&hstat);
          xfree_null (dummy);
          return NEWLOCATION;
-         break;
        case RETRUNNEEDED:
          /* The file was already fully retrieved. */
          free_hstat (&hstat);
          xfree_null (dummy);
          return RETROK;
-         break;
        case RETRFINISHED:
          /* Deal with you later.  */
          break;
@@ -2439,7 +2495,6 @@ The sizes do not match (local %s) -- retrieving.\n"),
            }
        }
       /* not reached */
-      break;
     }
   while (!opt.ntry || (count < opt.ntry));
   return TRYLIMEXC;
@@ -2607,29 +2662,35 @@ http_atotm (const char *time_string)
   return -1;
 }
 \f
-/* Authorization support: We support two authorization schemes:
+/* Authorization support: We support three authorization schemes:
 
    * `Basic' scheme, consisting of base64-ing USER:PASSWORD string;
 
    * `Digest' scheme, added by Junio Hamano <junio@twinsun.com>,
    consisting of answering to the server's challenge with the proper
-   MD5 digests.  */
+   MD5 digests.
+
+   * `NTLM' ("NT Lan Manager") scheme, based on code written by Daniel
+   Stenberg for libcurl.  Like digest, NTLM is based on a
+   challenge-response mechanism, but unlike digest, it is non-standard
+   (authenticates TCP connections rather than requests), undocumented
+   and Microsoft-specific.  */
 
 /* Create the authentication header contents for the `Basic' scheme.
    This is done by encoding the string `USER:PASS' in base64 and
    prepending `HEADER: Basic ' to it.  */
+
 static char *
 basic_authentication_encode (const char *user, const char *passwd)
 {
   char *t1, *t2;
   int len1 = strlen (user) + 1 + strlen (passwd);
-  int len2 = BASE64_LENGTH (len1);
 
   t1 = (char *)alloca (len1 + 1);
   sprintf (t1, "%s:%s", user, passwd);
 
-  t2 = (char *)alloca (len2 + 1);
-  base64_encode (t1, t2, len1);
+  t2 = (char *)alloca (BASE64_LENGTH (len1) + 1);
+  base64_encode (t1, len1, t2);
 
   return concat_strings ("Basic ", t2, (char *) 0);
 }
@@ -2827,26 +2888,33 @@ username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
 }
 #endif /* ENABLE_DIGEST */
 
+/* Computing the size of a string literal must take into account that
+   value returned by sizeof includes the terminating \0.  */
+#define STRSIZE(literal) (sizeof (literal) - 1)
 
-#define BEGINS_WITH(line, string_constant)                             \
-  (!strncasecmp (line, string_constant, sizeof (string_constant) - 1)  \
-   && (ISSPACE (line[sizeof (string_constant) - 1])                    \
-       || !line[sizeof (string_constant) - 1]))
+/* Whether chars in [b, e) begin with the literal string provided as
+   first argument and are followed by whitespace or terminating \0.
+   The comparison is case-insensitive.  */
+#define STARTS(literal, b, e)                          \
+  ((e) - (b) >= STRSIZE (literal)                      \
+   && 0 == strncasecmp (b, literal, STRSIZE (literal)) \
+   && ((e) - (b) == STRSIZE (literal)                  \
+       || ISSPACE (b[STRSIZE (literal)])))
 
 static int
-known_authentication_scheme_p (const char *au)
+known_authentication_scheme_p (const char *hdrbeg, const char *hdrend)
 {
-  return BEGINS_WITH (au, "Basic")
+  return STARTS ("Basic", hdrbeg, hdrend)
 #ifdef ENABLE_DIGEST
-    || BEGINS_WITH (au, "Digest")
+    || STARTS ("Digest", hdrbeg, hdrend)
 #endif
 #ifdef ENABLE_NTLM
-    || BEGINS_WITH (au, "NTLM")
+    || STARTS ("NTLM", hdrbeg, hdrend)
 #endif
     ;
 }
 
-#undef BEGINS_WITH
+#undef STARTS
 
 /* Create the HTTP authorization request header.  When the
    `WWW-Authenticate' response header is seen, according to the
@@ -2856,25 +2924,34 @@ known_authentication_scheme_p (const char *au)
 static char *
 create_authorization_line (const char *au, const char *user,
                           const char *passwd, const char *method,
-                          const char *path)
+                          const char *path, int *finished)
 {
-  if (0 == strncasecmp (au, "Basic", 5))
-    return basic_authentication_encode (user, passwd);
+  /* We are called only with known schemes, so we can dispatch on the
+     first letter. */
+  switch (TOUPPER (*au))
+    {
+    case 'B':                  /* Basic */
+      *finished = 1;
+      return basic_authentication_encode (user, passwd);
 #ifdef ENABLE_DIGEST
-  if (0 == strncasecmp (au, "Digest", 6))
-    return digest_authentication_encode (au, user, passwd, method, path);
+    case 'D':                  /* Digest */
+      *finished = 1;
+      return digest_authentication_encode (au, user, passwd, method, path);
 #endif
 #ifdef ENABLE_NTLM
-  if (0 == strncasecmp (au, "NTLM", 4))
-    {
-      int ok = ntlm_input (&pconn.ntlm, au);
-      if (!ok)
-       return NULL;
-      /* #### we shouldn't ignore the OK that ntlm_output returns. */
-      return ntlm_output (&pconn.ntlm, user, passwd, &ok);
-    }
+    case 'N':                  /* NTLM */
+      if (!ntlm_input (&pconn.ntlm, au))
+       {
+         *finished = 1;
+         return NULL;
+       }
+      return ntlm_output (&pconn.ntlm, user, passwd, finished);
 #endif
-  return NULL;
+    default:
+      /* We shouldn't get here -- this function should be only called
+        with values approved by known_authentication_scheme_p.  */
+      abort ();
+    }
 }
 \f
 void