/* HTTP support.
- Copyright (C) 1996-2005 Free Software Foundation, Inc.
+ Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+ 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
This file is part of GNU Wget.
GNU Wget is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
GNU Wget is distributed in the hope that it will be useful,
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with Wget; if not, write to the Free Software Foundation, Inc.,
-51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+along with Wget. If not, see <http://www.gnu.org/licenses/>.
-In addition, as a special exception, the Free Software Foundation
-gives permission to link the code of its release of Wget with the
-OpenSSL project's "OpenSSL" library (or with modified versions of it
-that use the same license as the "OpenSSL" library), and distribute
-the linked executables. You must obey the GNU General Public License
-in all respects for all of the code used other than "OpenSSL". If you
-modify this file, you may extend this exception to your version of the
-file, but you are not obligated to do so. If you do not wish to do
-so, delete this exception statement from your version. */
+Additional permission under GNU GPL version 3 section 7
-#include <config.h>
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work. */
+
+#include "wget.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <locale.h>
-#include "wget.h"
+#include "hash.h"
#include "http.h"
#include "utils.h"
#include "url.h"
# include "gen-md5.h"
#endif
#include "convert.h"
+#include "spider.h"
#ifdef TESTING
#include "test.h"
extern char *version_string;
+/* Forward decls. */
+static char *create_authorization_line (const char *, const char *,
+ const char *, const char *,
+ const char *, bool *);
+static char *basic_authentication_encode (const char *, const char *);
+static bool known_authentication_scheme_p (const char *, const char *);
+static void load_cookies (void);
+
#ifndef MIN
# define MIN(x, y) ((x) > (y) ? (y) : (x))
#endif
return;
BOUNDED_TO_ALLOCA (header, p, name);
++p;
- while (ISSPACE (*p))
+ while (c_isspace (*p))
++p;
request_set_header (req, xstrdup (name), (char *) p, rel_name);
}
xfree (req);
}
+static struct hash_table *basic_authed_hosts;
+
+/* Find out if this host has issued a Basic challenge yet; if so, give
+ * it the username, password. A temporary measure until we can get
+ * proper authentication in place. */
+
+static bool
+maybe_send_basic_creds (const char *hostname, const char *user,
+ const char *passwd, struct request *req)
+{
+ bool do_challenge = false;
+
+ if (opt.auth_without_challenge)
+ {
+ DEBUGP(("Auth-without-challenge set, sending Basic credentials.\n"));
+ do_challenge = true;
+ }
+ else if (basic_authed_hosts
+ && hash_table_contains(basic_authed_hosts, hostname))
+ {
+ DEBUGP(("Found `%s' in basic_authed_hosts.\n", hostname));
+ do_challenge = true;
+ }
+ else
+ {
+ DEBUGP(("Host `%s' has not issued a general basic challenge.\n",
+ hostname));
+ }
+ if (do_challenge)
+ {
+ request_set_header (req, "Authorization",
+ basic_authentication_encode (user, passwd),
+ rel_value);
+ }
+ return do_challenge;
+}
+
+static void
+register_basic_auth_host (const char *hostname)
+{
+ if (!basic_authed_hosts)
+ {
+ basic_authed_hosts = make_nocase_string_hash_table (1);
+ }
+ if (!hash_table_contains(basic_authed_hosts, hostname))
+ {
+ hash_table_put (basic_authed_hosts, xstrdup(hostname), NULL);
+ DEBUGP(("Inserted `%s' into basic_authed_hosts\n", hostname));
+ }
+}
+
+
/* Send the contents of FILE_NAME to SOCK. Make sure that exactly
PROMISED_SIZE bytes are sent over the wire -- if the file is
longer, read only that much; if the file is shorter, report an error. */
&& 0 == strncasecmp (b, name, name_len))
{
b += name_len + 1;
- while (b < e && ISSPACE (*b))
+ while (b < e && c_isspace (*b))
++b;
- while (b < e && ISSPACE (e[-1]))
+ while (b < e && c_isspace (e[-1]))
--e;
*begptr = b;
*endptr = e;
if (p < end && *p == '/')
{
++p;
- while (p < end && ISDIGIT (*p))
+ while (p < end && c_isdigit (*p))
++p;
if (p < end && *p == '.')
++p;
- while (p < end && ISDIGIT (*p))
+ while (p < end && c_isdigit (*p))
++p;
}
- while (p < end && ISSPACE (*p))
+ while (p < end && c_isspace (*p))
++p;
- if (end - p < 3 || !ISDIGIT (p[0]) || !ISDIGIT (p[1]) || !ISDIGIT (p[2]))
+ if (end - p < 3 || !c_isdigit (p[0]) || !c_isdigit (p[1]) || !c_isdigit (p[2]))
return -1;
status = 100 * (p[0] - '0') + 10 * (p[1] - '0') + (p[2] - '0');
if (message)
{
- while (p < end && ISSPACE (*p))
+ while (p < end && c_isspace (*p))
++p;
- while (p < end && ISSPACE (end[-1]))
+ while (p < end && c_isspace (end[-1]))
--end;
*message = strdupdelim (p, end);
}
xfree (resp);
}
+/* Print a single line of response, the characters [b, e). We tried
+ getting away with
+ logprintf (LOG_VERBOSE, "%s%.*s\n", prefix, (int) (e - b), b);
+ but that failed to escape the non-printable characters and, in fact,
+ caused crashes in UTF-8 locales. */
+
+static void
+print_response_line(const char *prefix, const char *b, const char *e)
+{
+ char *copy;
+ BOUNDED_TO_ALLOCA(b, e, copy);
+ logprintf (LOG_ALWAYS, "%s%s\n", prefix, escnonprint(copy));
+}
+
/* Print the server response, line by line, omitting the trailing CRLF
from individual header lines, and prefixed with PREFIX. */
--e;
if (b < e && e[-1] == '\r')
--e;
- /* This is safe even on printfs with broken handling of "%.<n>s"
- because resp->headers ends with \0. */
- logprintf (LOG_VERBOSE, "%s%.*s\n", prefix, e - b, b);
+ print_response_line(prefix, b, e);
}
}
HTTP spec. */
if (*hdr == ':')
++hdr;
- while (ISSPACE (*hdr))
+ while (c_isspace (*hdr))
++hdr;
if (!*hdr)
return false;
}
- if (!ISDIGIT (*hdr))
+ if (!c_isdigit (*hdr))
return false;
- for (num = 0; ISDIGIT (*hdr); hdr++)
+ for (num = 0; c_isdigit (*hdr); hdr++)
num = 10 * num + (*hdr - '0');
- if (*hdr != '-' || !ISDIGIT (*(hdr + 1)))
+ if (*hdr != '-' || !c_isdigit (*(hdr + 1)))
return false;
*first_byte_ptr = num;
++hdr;
- for (num = 0; ISDIGIT (*hdr); hdr++)
+ for (num = 0; c_isdigit (*hdr); hdr++)
num = 10 * num + (*hdr - '0');
- if (*hdr != '/' || !ISDIGIT (*(hdr + 1)))
+ if (*hdr != '/' || !c_isdigit (*(hdr + 1)))
return false;
*last_byte_ptr = num;
++hdr;
- for (num = 0; ISDIGIT (*hdr); hdr++)
- num = 10 * num + (*hdr - '0');
+ if (*hdr == '*')
+ num = -1;
+ else
+ for (num = 0; c_isdigit (*hdr); hdr++)
+ num = 10 * num + (*hdr - '0');
*entity_length_ptr = num;
return true;
}
return true;
}
-static bool
-extract_param_value_delim (const char *begin, const char *end,
- const char *param_name, char **param_value)
-{
- const char *p;
- int len;
-
- assert (begin);
- assert (end);
- assert (param_name);
- assert (param_value);
-
- len = strlen (param_name);
-
- /* skip initial whitespaces */
- p = begin;
- while (*p && ISSPACE (*p) && p < end) ++p;
-
- if (end - p > len
- && 0 == strncasecmp (p, param_name, len))
- {
- const char *e;
-
- /* skip white spaces, equal sign and inital quote */
- p += len;
- while (*p && (ISSPACE (*p) || *p == '\"' || *p == '=') && p < end) ++p;
-
- /* find last quote */
- e = p;
- while (*e && *e != '\"' && e < end) ++e;
-
- *param_value = strdupdelim (p, e);
-
- return true;
- }
-
- return false;
-}
-
-typedef struct {
- /* A token consists of characters in the [b, e) range. */
- const char *b, *e;
-} param_token;
-
-/* Extract a parameter from the HTTP header at *SOURCE and advance
- *SOURCE to the next parameter. Return false when there are no more
- parameters to extract. The name of the parameter is returned in
- NAME, and the value in VALUE. If the parameter has no value, the
- token's value is zeroed out.
+/* Extract a parameter from the string (typically an HTTP header) at
+ **SOURCE and advance SOURCE to the next parameter. Return false
+ when there are no more parameters to extract. The name of the
+ parameter is returned in NAME, and the value in VALUE. If the
+ parameter has no value, the token's value is zeroed out.
For example, if *SOURCE points to the string "attachment;
filename=\"foo bar\"", the first call to this function will return
return the token named "filename" and value "foo bar". The third
call will return false, indicating no more valid tokens. */
-static bool
-extract_param (const char **source, param_token *name, param_token *value)
+bool
+extract_param (const char **source, param_token *name, param_token *value,
+ char separator)
{
const char *p = *source;
- while (ISSPACE (*p)) ++p;
+ while (c_isspace (*p)) ++p;
if (!*p)
- return false; /* nothing more to extract */
+ {
+ *source = p;
+ return false; /* no error; nothing more to extract */
+ }
/* Extract name. */
name->b = p;
- while (*p && !ISSPACE (*p) && *p != '=' && *p != ';') ++p;
+ while (*p && !c_isspace (*p) && *p != '=' && *p != separator) ++p;
name->e = p;
- while (ISSPACE (*p)) ++p;
- if (*p == ';' || !*p) /* no value */
+ if (name->b == name->e)
+ return false; /* empty name: error */
+ while (c_isspace (*p)) ++p;
+ if (*p == separator || !*p) /* no value */
{
xzero (*value);
- if (*p == ';') ++p;
+ if (*p == separator) ++p;
*source = p;
return true;
}
if (*p != '=')
- return false; /* error */
+ return false; /* error */
/* *p is '=', extract value */
++p;
- while (ISSPACE (*p)) ++p;
- if (*p == '"') /* quoted */
+ while (c_isspace (*p)) ++p;
+ if (*p == '"') /* quoted */
{
value->b = ++p;
while (*p && *p != '"') ++p;
return false;
value->e = p++;
/* Currently at closing quote; find the end of param. */
- while (ISSPACE (*p)) ++p;
- while (*p && *p != ';') ++p;
- if (*p == ';')
- ++p;
+ while (c_isspace (*p)) ++p;
+ while (*p && *p != separator) ++p;
+ if (*p == separator)
+ ++p;
else if (*p)
- /* garbage after closed quote, e.g. foo="bar"baz */
- return false;
+ /* garbage after closed quote, e.g. foo="bar"baz */
+ return false;
}
- else /* unquoted */
+ else /* unquoted */
{
value->b = p;
- while (*p && *p != ';') ++p;
+ while (*p && *p != separator) ++p;
value->e = p;
- while (value->e != value->b && ISSPACE (value->e[-1]))
+ while (value->e != value->b && c_isspace (value->e[-1]))
--value->e;
- if (*p == ';') ++p;
+ if (*p == separator) ++p;
}
*source = p;
return true;
#undef MAX
#define MAX(p, q) ((p) > (q) ? (p) : (q))
+/* Parse the contents of the `Content-Disposition' header, extracting
+ the information useful to Wget. Content-Disposition is a header
+ borrowed from MIME; when used in HTTP, it typically serves for
+ specifying the desired file name of the resource. For example:
+
+ Content-Disposition: attachment; filename="flora.jpg"
+
+ Wget will skip the tokens it doesn't care about, such as
+ "attachment" in the previous example; it will also skip other
+ unrecognized params. If the header is syntactically correct and
+ contains a file name, a copy of the file name is stored in
+ *filename and true is returned. Otherwise, the function returns
+ false.
+
+ The file name is stripped of directory components and must not be
+ empty. */
+
static bool
parse_content_disposition (const char *hdr, char **filename)
{
param_token name, value;
- while (extract_param (&hdr, &name, &value))
+ while (extract_param (&hdr, &name, &value, ';'))
if (BOUNDED_EQUAL_NO_CASE (name.b, name.e, "filename") && value.b != NULL)
{
- /* Make the file name begin at the last slash or backslash. */
+ /* Make the file name begin at the last slash or backslash. */
const char *last_slash = memrchr (value.b, '/', value.e - value.b);
const char *last_bs = memrchr (value.b, '\\', value.e - value.b);
if (last_slash && last_bs)
value.b = 1 + MAX (last_slash, last_bs);
else if (last_slash || last_bs)
value.b = 1 + (last_slash ? last_slash : last_bs);
- if (value.b == value.e)
- continue;
- *filename = strdupdelim (value.b, value.e);
+ if (value.b == value.e)
+ continue;
+ /* Start with the directory prefix, if specified. */
+ if (opt.dir_prefix)
+ {
+ int prefix_length = strlen (opt.dir_prefix);
+ bool add_slash = (opt.dir_prefix[prefix_length - 1] != '/');
+ int total_length;
+
+ if (add_slash)
+ ++prefix_length;
+ total_length = prefix_length + (value.e - value.b);
+ *filename = xmalloc (total_length + 1);
+ strcpy (*filename, opt.dir_prefix);
+ if (add_slash)
+ (*filename)[prefix_length - 1] = '/';
+ memcpy (*filename + prefix_length, value.b, (value.e - value.b));
+ (*filename)[total_length] = '\0';
+ }
+ else
+ *filename = strdupdelim (value.b, value.e);
return true;
}
return false;
double dltime; /* time it took to download the data */
const char *referer; /* value of the referer header. */
char *local_file; /* local file name. */
+ bool existence_checked; /* true if we already checked for a file's
+ existence after having begun to download
+ (needed in gethttp for when connection is
+ interrupted/restarted. */
bool timestamp_checked; /* true if pre-download time-stamping checks
* have already been performed */
char *orig_file_name; /* name of file to compare for time-stamping
hs->error = NULL;
}
-static char *create_authorization_line (const char *, const char *,
- const char *, const char *,
- const char *, bool *);
-static char *basic_authentication_encode (const char *, const char *);
-static bool known_authentication_scheme_p (const char *, const char *);
-static void load_cookies (void);
-
#define BEGINS_WITH(line, string_constant) \
(!strncasecmp (line, string_constant, sizeof (string_constant) - 1) \
- && (ISSPACE (line[sizeof (string_constant) - 1]) \
+ && (c_isspace (line[sizeof (string_constant) - 1]) \
|| !line[sizeof (string_constant) - 1]))
#define SET_USER_AGENT(req) do { \
int sock = -1;
int flags;
- /* Set to 1 when the authorization has failed permanently and should
+ /* Set to 1 when the authorization has already been sent and should
not be tried again. */
bool auth_finished = false;
+ /* Set to 1 when just globally-set Basic authorization has been sent;
+ * should prevent further Basic negotiations, but not other
+ * mechanisms. */
+ bool basic_auth_finished = false;
+
/* Whether NTLM authentication is used for this request. */
bool ntlm_seen = false;
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)
+ if (user && passwd
+ && !u->user) /* We only do "site-wide" authentication with "global"
+ user/password values; URL user/password info overrides. */
{
- /* We have the username and the password, but haven't tried
- any authorization yet. Let's see if the "Basic" method
- works. If not, we'll come back here and construct a
- proper authorization method with the right challenges.
-
- If we didn't employ this kind of logic, every URL that
- requires authorization would have to be processed twice,
- which is very suboptimal and generates a bunch of false
- "unauthorized" errors in the server log.
-
- #### But this logic also has a serious problem when used
- with stronger authentications: we *first* transmit the
- username and the password in clear text, and *then* attempt a
- stronger authentication scheme. That cannot be right! We
- are only fortunate that almost everyone still uses the
- `Basic' scheme anyway.
-
- There should be an option to prevent this from happening, for
- those who use strong authentication schemes and value their
- passwords. */
- request_set_header (req, "Authorization",
- basic_authentication_encode (user, passwd),
- rel_value);
+ /* If this is a host for which we've already received a Basic
+ * challenge, we'll go ahead and send Basic authentication creds. */
+ basic_auth_finished = maybe_send_basic_creds(u->host, user, passwd, req);
}
proxyauth = NULL;
only hurts us. */
request_remove_header (req, "Authorization");
}
- }
-
- if (sock < 0)
- {
- /* In its current implementation, persistent_available_p will
- look up conn->host in some cases. If that lookup failed, we
- don't need to bother with connect_to_host. */
- if (host_lookup_failed)
+ else if (host_lookup_failed)
{
request_free (req);
+ logprintf(LOG_NOTQUIET,
+ _("%s: unable to resolve host address `%s'\n"),
+ exec_name, relevant->host);
return HOSTERR;
}
+ }
+ if (sock < 0)
+ {
sock = connect_to_host (conn->host, conn->port);
if (sock == E_HOST)
{
/* Determine the local filename if needed. Notice that if -O is used
* hstat.local_file is set by http_loop to the argument of -O. */
- if (!hs->local_file)
+ if (!hs->local_file)
{
/* Honor Content-Disposition whether possible. */
- if (!resp_header_copy (resp, "Content-Disposition", hdrval, sizeof (hdrval))
+ if (!opt.content_disposition
+ || !resp_header_copy (resp, "Content-Disposition",
+ hdrval, sizeof (hdrval))
|| !parse_content_disposition (hdrval, &hs->local_file))
{
- /* Choose filename according to URL name. */
+ /* The Content-Disposition header is missing or broken.
+ * Choose unique file name according to given URL. */
hs->local_file = url_file_name (u);
}
}
/* TODO: perform this check only once. */
- if (opt.noclobber && file_exists_p (hs->local_file))
+ if (!hs->existence_checked && file_exists_p (hs->local_file))
{
- /* If opt.noclobber is turned on and file already exists, do not
- retrieve the file */
- logprintf (LOG_VERBOSE, _("\
+ if (opt.noclobber)
+ {
+ /* If opt.noclobber is turned on and file already exists, do not
+ retrieve the file */
+ logprintf (LOG_VERBOSE, _("\
File `%s' already there; not retrieving.\n\n"), hs->local_file);
- /* If the file is there, we suppose it's retrieved OK. */
- *dt |= RETROKF;
+ /* If the file is there, we suppose it's retrieved OK. */
+ *dt |= RETROKF;
- /* #### Bogusness alert. */
- /* If its suffix is "html" or "htm" or similar, assume text/html. */
- if (has_html_suffix_p (hs->local_file))
- *dt |= TEXTHTML;
+ /* #### Bogusness alert. */
+ /* If its suffix is "html" or "htm" or similar, assume text/html. */
+ if (has_html_suffix_p (hs->local_file))
+ *dt |= TEXTHTML;
- return RETROK;
+ return RETRUNNEEDED;
+ }
+ else if (!ALLOW_CLOBBER)
+ {
+ char *unique = unique_name (hs->local_file, true);
+ if (unique != hs->local_file)
+ xfree (hs->local_file);
+ hs->local_file = unique;
+ }
}
+ hs->existence_checked = true;
/* Support timestamping */
/* TODO: move this code out of gethttp. */
/* Try to stat() the .orig file. */
if (stat (filename_plus_orig_suffix, &st) == 0)
{
- local_dot_orig_file_exists = 1;
+ local_dot_orig_file_exists = true;
local_filename = filename_plus_orig_suffix;
}
}
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;
+ {
+ /* Out of range.
+ #### If Content-Length is out of range, it most likely
+ means that the file is larger than 2G and that we're
+ compiled without LFS. In that case we should probably
+ refuse to even attempt to download the file. */
+ contlen = -1;
+ }
+ else if (parsed < 0)
+ {
+ /* Negative Content-Length; nonsensical, so we can't
+ assume any information about the content to receive. */
+ contlen = -1;
+ }
else
contlen = parsed;
}
}
if (!www_authenticate)
- /* If the authentication header is missing or
- unrecognized, there's no sense in retrying. */
- logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
- else if (BEGINS_WITH (www_authenticate, "Basic"))
- /* If the authentication scheme is "Basic", which we send
- by default, there's no sense in retrying either. (This
- should be changed when we stop sending "Basic" data by
- default.) */
- ;
- else
+ {
+ /* If the authentication header is missing or
+ unrecognized, there's no sense in retrying. */
+ logputs (LOG_NOTQUIET, _("Unknown authentication scheme.\n"));
+ }
+ else if (!basic_auth_finished
+ || !BEGINS_WITH (www_authenticate, "Basic"))
{
char *pth;
pth = url_full_path (u);
rel_value);
if (BEGINS_WITH (www_authenticate, "NTLM"))
ntlm_seen = true;
+ else if (!u->user && BEGINS_WITH (www_authenticate, "Basic"))
+ {
+ /* Need to register this host as using basic auth,
+ * so we automatically send creds next time. */
+ register_basic_auth_host (u->host);
+ }
xfree (pth);
goto retry_with_auth;
}
+ else
+ {
+ /* We already did Basic auth, and it failed. Gotta
+ * give up. */
+ }
}
logputs (LOG_NOTQUIET, _("Authorization failed.\n"));
request_free (req);
char *tmp = strchr (type, ';');
if (tmp)
{
- while (tmp > type && ISSPACE (tmp[-1]))
+ while (tmp > type && c_isspace (tmp[-1]))
--tmp;
*tmp = '\0';
}
wgint first_byte_pos, last_byte_pos, entity_length;
if (parse_content_range (hdrval, &first_byte_pos, &last_byte_pos,
&entity_length))
- contrange = first_byte_pos;
+ {
+ contrange = first_byte_pos;
+ contlen = last_byte_pos - first_byte_pos + 1;
+ }
}
resp_free (resp);
content-type. */
if (!type ||
0 == strncasecmp (type, TEXTHTML_S, strlen (TEXTHTML_S)) ||
- 0 == strncasecmp (type, TEXTXHTML_S, strlen (TEXTXHTML_S)))
+ 0 == strncasecmp (type, TEXTXHTML_S, strlen (TEXTXHTML_S)))
*dt |= TEXTHTML;
else
*dt &= ~TEXTHTML;
CLOSE_INVALIDATE (sock);
return RANGEERR;
}
- hs->contlen = contlen + contrange;
+ if (contlen == -1)
+ hs->contlen = -1;
+ else
+ hs->contlen = contlen + contrange;
if (opt.verbose)
{
return RETRFINISHED;
}
- /* Print fetch message, if opt.verbose. */
- if (opt.verbose)
- {
- logprintf (LOG_NOTQUIET, _("Saving to: `%s'\n"),
- HYPHENP (hs->local_file) ? "STDOUT" : hs->local_file);
- }
-
/* Open the local file. */
if (!output_stream)
{
else
fp = output_stream;
+ /* Print fetch message, if opt.verbose. */
+ if (opt.verbose)
+ {
+ logprintf (LOG_NOTQUIET, _("Saving to: `%s'\n"),
+ HYPHENP (hs->local_file) ? "STDOUT" : hs->local_file);
+ }
+
/* This confuses the timestamping code that checks for file size.
#### The timestamping code should be smarter about file size. */
if (opt.save_headers && hs->restval == 0)
int *dt, struct url *proxy)
{
int count;
- bool got_head = false; /* used for time-stamping */
+ bool got_head = false; /* used for time-stamping and filename detection */
+ bool time_came_from_head = false;
+ bool got_name = false;
char *tms;
const char *tmrate;
- uerr_t err;
+ uerr_t err, ret = TRYLIMEXC;
time_t tmr = -1; /* remote time-stamp */
- wgint local_size = 0; /* the size of the local file */
struct http_stat hstat; /* HTTP status */
- struct_stat st;
+ struct_stat st;
+ bool send_head_first = true;
/* Assert that no value for *LOCAL_FILE was passed. */
assert (local_file == NULL || *local_file == NULL);
hstat.referer = referer;
if (opt.output_document)
- hstat.local_file = xstrdup (opt.output_document);
+ {
+ hstat.local_file = xstrdup (opt.output_document);
+ got_name = true;
+ }
+ else if (!opt.content_disposition)
+ {
+ hstat.local_file = url_file_name (u);
+ got_name = true;
+ }
+
+ /* TODO: Ick! This code is now in both gethttp and http_loop, and is
+ * screaming for some refactoring. */
+ if (got_name && file_exists_p (hstat.local_file) && opt.noclobber)
+ {
+ /* If opt.noclobber is turned on and file already exists, do not
+ retrieve the file */
+ logprintf (LOG_VERBOSE, _("\
+File `%s' already there; not retrieving.\n\n"),
+ hstat.local_file);
+ /* If the file is there, we suppose it's retrieved OK. */
+ *dt |= RETROKF;
+
+ /* #### Bogusness alert. */
+ /* If its suffix is "html" or "htm" or similar, assume text/html. */
+ if (has_html_suffix_p (hstat.local_file))
+ *dt |= TEXTHTML;
+
+ return RETRUNNEEDED;
+ }
/* Reset the counter. */
count = 0;
/* Reset the document type. */
*dt = 0;
+ /* Skip preliminary HEAD request if we're not in spider mode AND
+ * if -O was given or HTTP Content-Disposition support is disabled. */
+ if (!opt.spider
+ && (got_name || !opt.content_disposition))
+ send_head_first = false;
+
+ /* Send preliminary HEAD request if -N is given and we have an existing
+ * destination file. */
+ if (opt.timestamping
+ && !opt.content_disposition
+ && file_exists_p (url_file_name (u)))
+ send_head_first = true;
+
/* THE loop */
do
{
sleep_between_retrievals (count);
/* Get the current time string. */
- tms = time_str (NULL);
+ tms = datetime_str (time (NULL));
+ if (opt.spider && !got_head)
+ logprintf (LOG_VERBOSE, _("\
+Spider mode enabled. Check if remote file exists.\n"));
+
/* Print fetch message, if opt.verbose. */
if (opt.verbose)
{
- char *hurl = url_string (u, true);
+ char *hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
if (count > 1)
{
/* Default document type is empty. However, if spider mode is
on or time-stamping is employed, HEAD_ONLY commands is
encoded within *dt. */
- if (opt.spider || (opt.timestamping && !got_head))
+ if (send_head_first && !got_head)
*dt |= HEAD_ONLY;
else
*dt &= ~HEAD_ONLY;
/* Decide whether or not to restart. */
if (opt.always_rest
+ && got_name
&& stat (hstat.local_file, &st) == 0
&& S_ISREG (st.st_mode))
/* When -c is used, continue from on-disk size. (Can't use
we require a fresh get.
b) caching is explicitly inhibited. */
if ((proxy && count > 1) /* a */
- || !opt.allow_cache /* b */
- )
+ || !opt.allow_cache) /* b */
*dt |= SEND_NOCACHE;
else
*dt &= ~SEND_NOCACHE;
err = gethttp (u, &hstat, dt, proxy);
/* Time? */
- tms = time_str (NULL);
+ tms = datetime_str (time (NULL));
/* Get the new location (with or without the redirection). */
if (hstat.newloc)
*newloc = xstrdup (hstat.newloc);
-
+
switch (err)
{
case HERR: case HEOF: case CONSOCKERR: case CONCLOSED:
/* Non-fatal errors continue executing the loop, which will
bring them to "while" statement at the end, to judge
whether the number of tries was exceeded. */
- /* free_hstat (&hstat); */
printwhat (count, opt.ntry);
continue;
- case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED:
- case SSLINITFAILED: case CONTNOTSUPPORTED:
- /* Fatal errors just return from the function. */
- free_hstat (&hstat);
- return err;
case FWRITEERR: case FOPENERR:
/* Another fatal error. */
logputs (LOG_VERBOSE, "\n");
logprintf (LOG_NOTQUIET, _("Cannot write to `%s' (%s).\n"),
hstat.local_file, strerror (errno));
- free_hstat (&hstat);
- return err;
+ case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED:
+ case SSLINITFAILED: case CONTNOTSUPPORTED:
+ /* Fatal errors just return from the function. */
+ ret = err;
+ goto exit;
case CONSSLERR:
/* Another fatal error. */
logprintf (LOG_NOTQUIET, _("Unable to establish SSL connection.\n"));
- free_hstat (&hstat);
- return err;
+ ret = err;
+ goto exit;
case NEWLOCATION:
/* Return the new location to the caller. */
if (!*newloc)
logprintf (LOG_NOTQUIET,
_("ERROR: Redirection (%d) without location.\n"),
hstat.statcode);
- free_hstat (&hstat);
- return WRONGCODE;
+ ret = WRONGCODE;
}
- free_hstat (&hstat);
- return NEWLOCATION;
+ else
+ {
+ ret = NEWLOCATION;
+ }
+ goto exit;
case RETRUNNEEDED:
/* The file was already fully retrieved. */
- free_hstat (&hstat);
- return RETROK;
+ ret = RETROK;
+ goto exit;
case RETRFINISHED:
/* Deal with you later. */
break;
if (!(*dt & RETROKF))
{
+ char *hurl = NULL;
if (!opt.verbose)
{
/* #### Ugly ugly ugly! */
- char *hurl = url_string (u, true);
+ hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
logprintf (LOG_NONVERBOSE, "%s:\n", hurl);
- xfree (hurl);
}
- logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
- tms, hstat.statcode, escnonprint (hstat.error));
+
+ /* Fall back to GET if HEAD fails with a 500 or 501 error code. */
+ if (*dt & HEAD_ONLY
+ && (hstat.statcode == 500 || hstat.statcode == 501))
+ {
+ got_head = true;
+ continue;
+ }
+ /* Maybe we should always keep track of broken links, not just in
+ * spider mode. */
+ else if (opt.spider)
+ {
+ /* #### Again: ugly ugly ugly! */
+ if (!hurl)
+ hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
+ nonexisting_url (hurl);
+ logprintf (LOG_NOTQUIET, _("\
+Remote file does not exist -- broken link!!!\n"));
+ }
+ else
+ {
+ logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
+ tms, hstat.statcode, escnonprint (hstat.error));
+ }
logputs (LOG_VERBOSE, "\n");
- free_hstat (&hstat);
- return WRONGCODE;
+ ret = WRONGCODE;
+ xfree_null (hurl);
+ goto exit;
}
/* Did we get the time-stamp? */
if (!got_head)
{
+ got_head = true; /* no more time-stamping */
+
if (opt.timestamping && !hstat.remote_time)
{
logputs (LOG_NOTQUIET, _("\
if (tmr == (time_t) (-1))
logputs (LOG_VERBOSE, _("\
Last-modified header invalid -- time-stamp ignored.\n"));
+ if (*dt & HEAD_ONLY)
+ time_came_from_head = true;
}
- }
-
- /* The time-stamping section. */
- if (opt.timestamping && !got_head)
- {
- got_head = true; /* no more time-stamping */
- *dt &= ~HEAD_ONLY;
- count = 0; /* the retrieve count for HEAD is reset */
-
- if (hstat.remote_time && tmr != (time_t) (-1))
+
+ if (send_head_first)
{
- /* Now time-stamping can be used validly. Time-stamping
- means that if the sizes of the local and remote file
- match, and local file is newer than the remote file,
- it will not be retrieved. Otherwise, the normal
- download procedure is resumed. */
- if (hstat.orig_file_tstamp >= tmr)
+ /* The time-stamping section. */
+ if (opt.timestamping)
{
- if (hstat.contlen == -1 || hstat.orig_file_size == hstat.contlen)
+ if (hstat.orig_file_name) /* Perform the following
+ checks only if the file
+ we're supposed to
+ download already exists. */
{
- logprintf (LOG_VERBOSE, _("\
+ if (hstat.remote_time &&
+ tmr != (time_t) (-1))
+ {
+ /* Now time-stamping can be used validly.
+ Time-stamping means that if the sizes of
+ the local and remote file match, and local
+ file is newer than the remote file, it will
+ not be retrieved. Otherwise, the normal
+ download procedure is resumed. */
+ if (hstat.orig_file_tstamp >= tmr)
+ {
+ if (hstat.contlen == -1
+ || hstat.orig_file_size == hstat.contlen)
+ {
+ logprintf (LOG_VERBOSE, _("\
Server file no newer than local file `%s' -- not retrieving.\n\n"),
- hstat.orig_file_name);
- free_hstat (&hstat);
- return RETROK;
+ hstat.orig_file_name);
+ ret = RETROK;
+ goto exit;
+ }
+ else
+ {
+ logprintf (LOG_VERBOSE, _("\
+The sizes do not match (local %s) -- retrieving.\n"),
+ number_to_static_string (hstat.orig_file_size));
+ }
+ }
+ else
+ logputs (LOG_VERBOSE,
+ _("Remote file is newer, retrieving.\n"));
+
+ logputs (LOG_VERBOSE, "\n");
+ }
+ }
+
+ /* free_hstat (&hstat); */
+ hstat.timestamp_checked = true;
+ }
+
+ if (opt.spider)
+ {
+ if (opt.recursive)
+ {
+ if (*dt & TEXTHTML)
+ {
+ logputs (LOG_VERBOSE, _("\
+Remote file exists and could contain links to other resources -- retrieving.\n\n"));
+ }
+ else
+ {
+ logprintf (LOG_VERBOSE, _("\
+Remote file exists but does not contain any link -- not retrieving.\n\n"));
+ ret = RETROK; /* RETRUNNEEDED is not for caller. */
+ goto exit;
+ }
}
else
{
- logprintf (LOG_VERBOSE, _("\
-The sizes do not match (local %s) -- retrieving.\n"),
- number_to_static_string (local_size));
+ if (*dt & TEXTHTML)
+ {
+ logprintf (LOG_VERBOSE, _("\
+Remote file exists and could contain further links,\n\
+but recursion is disabled -- not retrieving.\n\n"));
+ }
+ else
+ {
+ logprintf (LOG_VERBOSE, _("\
+Remote file exists.\n\n"));
+ }
+ ret = RETROK; /* RETRUNNEEDED is not for caller. */
+ goto exit;
}
}
- else
- logputs (LOG_VERBOSE,
- _("Remote file is newer, retrieving.\n"));
- logputs (LOG_VERBOSE, "\n");
- }
+ got_name = true;
+ *dt &= ~HEAD_ONLY;
+ count = 0; /* the retrieve count for HEAD is reset */
+ continue;
+ } /* send_head_first */
+ } /* !got_head */
- /* free_hstat (&hstat); */
- hstat.timestamp_checked = true;
- continue;
- }
-
if ((tmr != (time_t) (-1))
- && !opt.spider
&& ((hstat.len == hstat.contlen) ||
((hstat.res == 0) && (hstat.contlen == -1))))
{
else
fl = hstat.local_file;
if (fl)
- touch (fl, tmr);
+ {
+ time_t newtmr = -1;
+ /* Reparse time header, in case it's changed. */
+ if (time_came_from_head
+ && hstat.remote_time && hstat.remote_time[0])
+ {
+ newtmr = http_atotm (hstat.remote_time);
+ if (newtmr != -1)
+ tmr = newtmr;
+ }
+ touch (fl, tmr);
+ }
}
/* End of time-stamping section. */
- if (opt.spider)
- {
- logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode,
- escnonprint (hstat.error));
- return RETROK;
- }
-
tmrate = retr_rate (hstat.rd_size, hstat.dltime);
total_download_time += hstat.dltime;
else
downloaded_file(FILE_DOWNLOADED_NORMALLY, hstat.local_file);
- free_hstat (&hstat);
- return RETROK;
+ ret = RETROK;
+ goto exit;
}
else if (hstat.res == 0) /* No read error */
{
else
downloaded_file(FILE_DOWNLOADED_NORMALLY, hstat.local_file);
- free_hstat (&hstat);
- return RETROK;
+ ret = RETROK;
+ goto exit;
}
else if (hstat.len < hstat.contlen) /* meaning we lost the
connection too soon */
_("%s (%s) - Connection closed at byte %s. "),
tms, tmrate, number_to_static_string (hstat.len));
printwhat (count, opt.ntry);
- /* free_hstat (&hstat); */
continue;
}
else
tms, tmrate, number_to_static_string (hstat.len),
hstat.rderrmsg);
printwhat (count, opt.ntry);
- /* free_hstat (&hstat); */
continue;
}
else /* hstat.res == -1 and contlen is given */
number_to_static_string (hstat.contlen),
hstat.rderrmsg);
printwhat (count, opt.ntry);
- /* free_hstat (&hstat); */
continue;
}
}
/* not reached */
}
while (!opt.ntry || (count < opt.ntry));
+
+exit:
+ if (ret == RETROK)
+ *local_file = xstrdup (hstat.local_file);
+ free_hstat (&hstat);
- return TRYLIMEXC;
+ return ret;
}
\f
/* Check whether the result of strptime() indicates success.
{
if (!p)
return false;
- while (ISSPACE (*p))
+ while (c_isspace (*p))
++p;
if (!*p
|| (p[0] == 'G' && p[1] == 'M' && p[2] == 'T')
- || ((p[0] == '+' || p[0] == '-') && ISDIGIT (p[1])))
+ || ((p[0] == '+' || p[0] == '-') && c_isdigit (p[1])))
return true;
else
return false;
}
#define SKIP_WS(x) do { \
- while (ISSPACE (*(x))) \
+ while (c_isspace (*(x))) \
++(x); \
} while (0)
#ifdef ENABLE_DIGEST
-/* Parse HTTP `WWW-Authenticate:' header. AU points to the beginning
- of a field in such a header. If the field is the one specified by
- ATTR_NAME ("realm", "opaque", and "nonce" are used by the current
- digest authorization code), extract its value in the (char*)
- variable pointed by RET. Returns negative on a malformed header,
- or number of bytes that have been parsed by this call. */
-static int
-extract_header_attr (const char *au, const char *attr_name, char **ret)
-{
- const char *ep;
- const char *cp = au;
-
- if (strncmp (cp, attr_name, strlen (attr_name)) == 0)
- {
- cp += strlen (attr_name);
- if (!*cp)
- return -1;
- SKIP_WS (cp);
- if (*cp != '=')
- return -1;
- if (!*++cp)
- return -1;
- SKIP_WS (cp);
- if (*cp != '\"')
- return -1;
- if (!*++cp)
- return -1;
- for (ep = cp; *ep && *ep != '\"'; ep++)
- ;
- if (!*ep)
- return -1;
- xfree_null (*ret);
- *ret = strdupdelim (cp, ep);
- return ep - au + 1;
- }
- else
- return 0;
-}
-
/* Dump the hexadecimal representation of HASH to BUF. HASH should be
an array of 16 bytes containing the hash keys, and BUF should be a
buffer of 33 writable characters (32 for hex digits plus one for
{ "nonce", &nonce }
};
char *res;
+ param_token name, value;
realm = opaque = nonce = NULL;
au += 6; /* skip over `Digest' */
- while (*au)
+ while (extract_param (&au, &name, &value, ','))
{
int i;
-
- SKIP_WS (au);
for (i = 0; i < countof (options); i++)
- {
- int skip = extract_header_attr (au, options[i].name,
- options[i].variable);
- if (skip < 0)
- {
- xfree_null (realm);
- xfree_null (opaque);
- xfree_null (nonce);
- return NULL;
- }
- else if (skip)
- {
- au += skip;
- break;
- }
- }
- if (i == countof (options))
- {
- while (*au && *au != '=')
- au++;
- if (*au && *++au)
- {
- SKIP_WS (au);
- if (*au == '\"')
- {
- au++;
- while (*au && *au != '\"')
- au++;
- if (*au)
- au++;
- }
- }
- }
- while (*au && *au != ',')
- au++;
- if (*au)
- au++;
+ if (name.e - name.b == strlen (options[i].name)
+ && 0 == strncmp (name.b, options[i].name, name.e - name.b))
+ {
+ *options[i].variable = strdupdelim (value.b, value.e);
+ break;
+ }
}
if (!realm || !nonce || !user || !passwd || !path || !method)
{
((e) - (b) >= STRSIZE (literal) \
&& 0 == strncasecmp (b, literal, STRSIZE (literal)) \
&& ((e) - (b) == STRSIZE (literal) \
- || ISSPACE (b[STRSIZE (literal)])))
+ || c_isspace (b[STRSIZE (literal)])))
static bool
known_authentication_scheme_p (const char *hdrbeg, const char *hdrend)
{
/* We are called only with known schemes, so we can dispatch on the
first letter. */
- switch (TOUPPER (*au))
+ switch (c_toupper (*au))
{
case 'B': /* Basic */
*finished = true;
#ifdef TESTING
-char *
+const char *
test_parse_content_disposition()
{
int i;
struct {
char *hdrval;
+ char *opt_dir_prefix;
char *filename;
bool result;
} test_array[] = {
- { "filename=\"file.ext\"", "file.ext", true },
- { "attachment; filename=\"file.ext\"", "file.ext", true },
- { "attachment; filename=\"file.ext\"; dummy", "file.ext", true },
- { "attachment", NULL, false },
+ { "filename=\"file.ext\"", NULL, "file.ext", true },
+ { "filename=\"file.ext\"", "somedir", "somedir/file.ext", true },
+ { "attachment; filename=\"file.ext\"", NULL, "file.ext", true },
+ { "attachment; filename=\"file.ext\"", "somedir", "somedir/file.ext", true },
+ { "attachment; filename=\"file.ext\"; dummy", NULL, "file.ext", true },
+ { "attachment; filename=\"file.ext\"; dummy", "somedir", "somedir/file.ext", true },
+ { "attachment", NULL, NULL, false },
+ { "attachment", "somedir", NULL, false },
};
for (i = 0; i < sizeof(test_array)/sizeof(test_array[0]); ++i)
{
char *filename;
- bool res = parse_content_disposition (test_array[i].hdrval, &filename);
+ bool res;
+
+ opt.dir_prefix = test_array[i].opt_dir_prefix;
+ res = parse_content_disposition (test_array[i].hdrval, &filename);
mu_assert ("test_parse_content_disposition: wrong result",
res == test_array[i].result
&& (res == false
|| 0 == strcmp (test_array[i].filename, filename)));
-
- /* printf ("test %d: %s\n", i, res == false ? "false" : filename); */
}
return NULL;
#endif /* TESTING */
/*
- * vim: et ts=2 sw=2
+ * vim: et sts=2 sw=2 cino+={s
*/