do
{
for (i = 0; 1; i++)
- {
- int res;
- if (i > bufsize - 1)
- *line = (char *)xrealloc (*line, (bufsize <<= 1));
- res = RBUF_READCHAR (rbuf, *line + i);
- /* RES is number of bytes read. */
- if (res == 1)
- {
- if ((*line)[i] == '\n')
- {
- (*line)[i] = '\0';
- /* Get rid of \r. */
- if (i > 0 && (*line)[i - 1] == '\r')
- (*line)[i - 1] = '\0';
- break;
- }
- }
- else
- return FTPRERR;
- }
+ {
+ int res;
+ if (i > bufsize - 1)
+ *line = (char *)xrealloc (*line, (bufsize <<= 1));
+ res = RBUF_READCHAR (rbuf, *line + i);
+ /* RES is number of bytes read. */
+ if (res == 1)
+ {
+ if ((*line)[i] == '\n')
+ {
+ (*line)[i] = '\0';
+ /* Get rid of \r. */
+ if (i > 0 && (*line)[i - 1] == '\r')
+ (*line)[i - 1] = '\0';
+ break;
+ }
+ }
+ else
+ return FTPRERR;
+ }
if (opt.server_response)
- logprintf (LOG_ALWAYS, "%s\n", *line);
+ logprintf (LOG_ALWAYS, "%s\n", *line);
else
- DEBUGP (("%s\n", *line));
+ DEBUGP (("%s\n", *line));
}
while (!(i >= 3 && ISDIGIT (**line) && ISDIGIT ((*line)[1]) &&
- ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
+ ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
strncpy (ftp_last_respline, *line, sizeof (ftp_last_respline));
ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
return FTPOK;
ftp_request (const char *command, const char *value)
{
char *res = (char *)xmalloc (strlen (command)
- + (value ? (1 + strlen (value)) : 0)
- + 2 + 1);
+ + (value ? (1 + strlen (value)) : 0)
+ + 2 + 1);
sprintf (res, "%s%s%s\r\n", command, value ? " " : "", value ? value : "");
if (opt.server_response)
{
/* Hack: don't print out password. */
if (strncmp (res, "PASS", 4) != 0)
- logprintf (LOG_ALWAYS, "--> %s\n", res);
+ logprintf (LOG_ALWAYS, "--> %s\n", res);
else
- logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
+ logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
}
else
DEBUGP (("\n--> %s\n", res));
for (i = 0; i < ARRAY_SIZE (skey_head); i++)
{
- if (strncasecmp (skey_head[i], respline, strlen (skey_head[i])) == 0)
- break;
+ if (strncasecmp (skey_head[i], respline, strlen (skey_head[i])) == 0)
+ break;
}
if (i < ARRAY_SIZE (skey_head))
{
- const char *cp;
- int skey_sequence = 0;
-
- for (cp = respline + strlen (skey_head[i]);
- '0' <= *cp && *cp <= '9';
- cp++)
- {
- skey_sequence = skey_sequence * 10 + *cp - '0';
- }
- if (*cp == ' ')
- cp++;
- else
- {
- bad:
- xfree (respline);
- return FTPLOGREFUSED;
- }
- if ((cp = calculate_skey_response (skey_sequence, cp, pass)) == 0)
- goto bad;
- pass = cp;
+ const char *cp;
+ int skey_sequence = 0;
+
+ for (cp = respline + strlen (skey_head[i]);
+ '0' <= *cp && *cp <= '9';
+ cp++)
+ {
+ skey_sequence = skey_sequence * 10 + *cp - '0';
+ }
+ if (*cp == ' ')
+ cp++;
+ else
+ {
+ bad:
+ xfree (respline);
+ return FTPLOGREFUSED;
+ }
+ if ((cp = calculate_skey_response (skey_sequence, cp, pass)) == 0)
+ goto bad;
+ pass = cp;
}
}
#endif /* USE_OPIE */
/* Construct the argument of PORT (of the form a,b,c,d,e,f). */
bytes = (char *)alloca (6 * 4 + 1);
sprintf (bytes, "%d,%d,%d,%d,%d,%d", in_addr[0], in_addr[1],
- in_addr[2], in_addr[3], (unsigned) (port & 0xff00) >> 8,
- port & 0xff);
+ in_addr[2], in_addr[3], (unsigned) (port & 0xff00) >> 8,
+ port & 0xff);
/* Send PORT request. */
request = ftp_request ("PORT", bytes);
nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
{
addr[i] = 0;
for (; ISDIGIT (*s); s++)
- addr[i] = (*s - '0') + 10 * addr[i];
+ addr[i] = (*s - '0') + 10 * addr[i];
if (*s == ',')
- s++;
+ s++;
else if (i < 5)
- {
- /* When on the last number, anything can be a terminator. */
- xfree (respline);
- return FTPINVPASV;
- }
+ {
+ /* When on the last number, anything can be a terminator. */
+ xfree (respline);
+ return FTPINVPASV;
+ }
}
xfree (respline);
return FTPOK;
if (!strcasecmp (request, "WINDOWS_NT"))
*server_type = ST_WINNT;
else
- *server_type = ST_OTHER;
+ if (!strcasecmp (request, "MACOS"))
+ *server_type = ST_MACOS;
+ else
+ *server_type = ST_OTHER;
xfree (respline);
/* All OK. */
/* Parsing FTP `ls' output.
- Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
+ Copyright (C) 1995, 1996, 1997, 2000, 2001 Free Software Foundation,
+ Inc.
This file is part of Wget.
#include "ftp.h"
#include "url.h"
-/* Undef this if FTPPARSE is not available. In that case, Wget will
- still work with Unix FTP servers, which covers most cases. */
-
-#define HAVE_FTPPARSE
-
-#ifdef HAVE_FTPPARSE
-#include "ftpparse.h"
-#endif
-
/* Converts symbolic permissions to number-style ones, e.g. string
rwxr-xr-x to 755. For now, it knows nothing of
setuid/setgid/sticky. ACLs are ignored. */
}
+/* Cleans a line of text so that it can be consistently parsed. Destroys
+ <CR> and <LF> in case that thay occur at the end of the line and
+ replaces all <TAB> character with <SPACE>. Returns the length of the
+ modified line. */
+static int
+clean_line(char *line)
+{
+ int len = strlen (line);
+ if (!len) return 0;
+ if (line[len - 1] == '\n')
+ line[--len] = '\0';
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+ for ( ; *line ; line++ ) if (*line == '\t') *line = ' ';
+ return len;
+}
+
/* Convert the Un*x-ish style directory listing stored in FILE to a
linked list of fileinfo (system-independent) entries. The contents
of FILE are considered to be produced by the standard Unix `ls -la'
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
- DEBUGP (("%s\n", line));
- len = strlen (line);
- /* Destroy <CR><LF> if present. */
- if (len && line[len - 1] == '\n')
- line[--len] = '\0';
- if (len && line[len - 1] == '\r')
- line[--len] = '\0';
-
+ len = clean_line (line);
/* Skip if total... */
if (!strncasecmp (line, "total", 5))
{
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
- DEBUGP (("%s\n", line));
- len = strlen (line);
- /* Destroy <CR><LF> if present. */
- if (len && line[len - 1] == '\n')
- line[--len] = '\0';
- if (len && line[len - 1] == '\r')
- line[--len] = '\0';
+ len = clean_line (line);
/* Extracting name is a bit of black magic and we have to do it
before `strtok' inserted extra \0 characters in the line
return dir;
}
+/* Converts VMS symbolic permissions to number-style ones, e.g. string
+ RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
+ (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
+static int
+vmsperms (const char *s)
+{
+ int perms = 0;
-#ifdef HAVE_FTPPARSE
+ do
+ {
+ switch (*s) {
+ case ',': perms <<= 3; break;
+ case 'R': perms |= 4; break;
+ case 'W': perms |= 2; break;
+ case 'D': perms |= 2; break;
+ case 'E': perms |= 1; break;
+ default: DEBUGP(("wrong VMS permissons!\n"));
+ }
+ }
+ while (*++s);
+ return perms;
+}
-/* This is a "glue function" that connects the ftpparse interface to
- the interface Wget expects. ftpparse is used to parse listings
- from servers other than Unix, like those running VMS or NT. */
static struct fileinfo *
-ftp_parse_nonunix_ls (const char *file)
+ftp_parse_vms_ls (const char *file)
{
FILE *fp;
- int len;
+ /* #### A third copy of more-or-less the same array ? */
+ static const char *months[] = {
+ "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+ "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
+ };
+ int i;
+ int year, month, day; /* for time analysis */
+ int hour, min, sec;
+ struct tm timestruct;
- char *line; /* tokenizer */
+ char *line, *tok, *p; /* tokenizer */
struct fileinfo *dir, *l, cur; /* list creation */
fp = fopen (file, "rb");
}
dir = l = NULL;
+ /* Empty line */
+ read_whole_line (fp);
+ /* "Directory PUB$DEVICE[PUB]" */
+ read_whole_line (fp);
+ /* Empty line */
+ read_whole_line (fp);
+
/* Line loop to end of file: */
while ((line = read_whole_line (fp)))
{
- struct ftpparse fp;
+ i = clean_line (line);
+ if (!i) break;
+
+ /* First column: Name. A bit of black magic again. The name my be
+ either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
+ line. Therefore we will first try to get the complete name
+ until the first space character; if it fails, we assume that the name
+ occupies the whole line. After that we search for the version
+ separator ";", we remove it and check the extension of the file;
+ extension .DIR denotes directory. */
+
+ tok = strtok(line, " ");
+ if (tok == NULL) tok = line;
+ DEBUGP(("file name: '%s'\n", tok));
+ for (p = tok ; *p && *p != ';' ; p++);
+ if (*p == ';') *p = '\0';
+ p = tok + strlen(tok) - 4;
+ if (!strcmp(p, ".DIR")) *p = '\0';
+ cur.name = xstrdup(tok);
+ DEBUGP(("Name: '%s'\n", cur.name));
+
+ /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
+ the file size to zero as the listing does tell us only the size in
+ filesystem blocks - for an integrity check (when mirroring, for
+ example) we would need the size in bytes. */
+
+ if (! *p)
+ {
+ cur.type = FT_DIRECTORY;
+ cur.size = 0;
+ DEBUGP(("Directory\n"));
+ }
+ else
+ {
+ cur.type = FT_PLAINFILE;
+ DEBUGP(("File\n"));
+ }
- DEBUGP (("%s\n", line));
- len = strlen (line);
- /* Destroy <CR><LF> if present. */
- if (len && line[len - 1] == '\n')
- line[--len] = '\0';
- if (len && line[len - 1] == '\r')
- line[--len] = '\0';
+ cur.size = 0;
- if (ftpparse(&fp, line, len))
+ /* Second column, if exists, or the first column of the next line
+ contain file size in blocks. We will skip it. */
+
+ tok = strtok(NULL, " ");
+ if (tok == NULL)
+ {
+ DEBUGP(("Getting additional line\n"));
+ xfree (line);
+ line = read_whole_line (fp);
+ if (!line)
{
- cur.size = fp.size;
- cur.name = (char *)xmalloc (fp.namelen + 1);
- memcpy (cur.name, fp.name, fp.namelen);
- cur.name[fp.namelen] = '\0';
- DEBUGP (("%s\n", cur.name));
- /* No links on non-UNIX systems */
- cur.linkto = NULL;
- /* ftpparse won't tell us correct permisions. So lets just invent
- something. */
- if (fp.flagtrycwd)
- {
- cur.type = FT_DIRECTORY;
- cur.perms = 0755;
- }
- else
- {
- cur.type = FT_PLAINFILE;
- cur.perms = 0644;
- }
- if (!dir)
- {
- l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
- memcpy (l, &cur, sizeof (cur));
- l->prev = l->next = NULL;
- }
- else
- {
- cur.prev = l;
- l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
- l = l->next;
- memcpy (l, &cur, sizeof (cur));
- l->next = NULL;
- }
- l->tstamp = fp.mtime;
+ DEBUGP(("empty line read, leaving listing parser\n"));
+ break;
+ }
+ i = clean_line (line);
+ if (!i)
+ {
+ DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
+ break;
+ }
+ tok = strtok(line, " ");
+ }
+ DEBUGP(("second token: '%s'\n", tok));
+
+ /* Third/Second column: Date DD-MMM-YYYY. */
+
+ tok = strtok(NULL, "-");
+ DEBUGP(("day: '%s'\n",tok));
+ day = atoi(tok);
+ tok = strtok(NULL, "-");
+ if (!tok)
+ {
+ /* If the server produces garbage like
+ 'EA95_0PS.GZ;1 No privilege for attempted operation'
+ the first strtok(NULL, "-") will return everything until the end
+ of the line and only the next strtok() call will return NULL. */
+ DEBUGP(("nonsense in VMS listing, skipping this line\n"));
+ break;
}
+ for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
+ /* Uknown months are mapped to January */
+ month = (i%12)+1;
+ tok = strtok(NULL, " ");
+ year = atoi(tok)-1900;
+ DEBUGP(("date parsed\n"));
+
+ /* Fourth/Third column: Time hh:mm:ss */
+ tok = strtok(NULL, ":");
+ hour = atoi(tok);
+ tok = strtok(NULL, ":");
+ min = atoi(tok);
+ tok = strtok(NULL, " ");
+ sec = atoi(tok);
+
+ DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n",
+ year+1900, month, day, hour, min, sec));
+
+ /* Build the time-stamp (copy & paste from above) */
+ timestruct.tm_sec = sec;
+ timestruct.tm_min = min;
+ timestruct.tm_hour = hour;
+ timestruct.tm_mday = day;
+ timestruct.tm_mon = month;
+ timestruct.tm_year = year;
+ timestruct.tm_wday = 0;
+ timestruct.tm_yday = 0;
+ timestruct.tm_isdst = -1;
+ cur.tstamp = mktime (×truct); /* store the time-stamp */
+
+ DEBUGP(("Timestamp: %ld\n", cur.tstamp));
+
+ /* Skip the fifth column */
+
+ tok = strtok(NULL, " ");
+
+ /* Sixth column: Permissions */
+
+ tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
+ tok = strtok(NULL, ")");
+ if (tok == NULL)
+ {
+ DEBUGP(("confusing VMS permissions, skipping line\n"));
+ continue;
+ }
+ /* Permissons have the format "RWED,RWED,RE" */
+ cur.perms = vmsperms(tok);
+ DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
+
+ cur.linkto = NULL;
+
+ /* And put everything into the linked list */
+ if (!dir)
+ {
+ l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
+ memcpy (l, &cur, sizeof (cur));
+ l->prev = l->next = NULL;
+ }
+ else
+ {
+ cur.prev = l;
+ l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
+ l = l->next;
+ memcpy (l, &cur, sizeof (cur));
+ l->next = NULL;
+ }
xfree (line);
}
fclose (fp);
return dir;
}
-#endif
-/* This function switches between the correct parsing routine
- depending on the SYSTEM_TYPE. If system type is ST_UNIX, we use
- our home-grown ftp_parse_unix_ls; otherwise, we use our interface
- to ftpparse, also known as ftp_parse_nonunix_ls. The system type
- should be based on the result of the "SYST" response of the FTP
- server. */
+
+/* This function switches between the correct parsing routine depending on
+ the SYSTEM_TYPE. The system type should be based on the result of the
+ "SYST" response of the FTP server. According to this repsonse we will
+ use on of the three different listing parsers that cover the most of FTP
+ servers used nowadays. */
struct fileinfo *
ftp_parse_ls (const char *file, const enum stype system_type)
{
logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
return NULL;
- }
+ }
c = fgetc(fp);
fclose(fp);
/* If the first character of the file is '0'-'9', it's WINNT
format. */
if (c >= '0' && c <='9')
return ftp_parse_winnt_ls (file);
- else
- return ftp_parse_unix_ls (file, TRUE);
+ else
+ return ftp_parse_unix_ls (file, TRUE);
}
+ case ST_VMS:
+ return ftp_parse_vms_ls (file);
+ case ST_MACOS:
+ return ftp_parse_unix_ls (file, TRUE);
default:
-#ifdef HAVE_FTPPARSE
- return ftp_parse_nonunix_ls (file);
-#else
- /* #### Maybe log some warning here? */
- return ftp_parse_unix_ls (file);
-#endif
+ logprintf (LOG_NOTQUIET, _("\
+Usupported listing type, trying Unix listing parser.\n"));
+ return ftp_parse_unix_ls (file, FALSE);
}
}
\f
extern char ftp_last_respline[];
-/* #### Global variables?? These two should be members of struct
- ccon! */
-
-static enum stype host_type=ST_UNIX;
-static char *pwd;
-
/* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
the string S, and return the number converted to long, if found, 0
otherwise. */
connection to the server. It always closes the data connection,
and closes the control connection in case of error. */
static uerr_t
-getftp (const struct urlinfo *u, long *len, long restval, ccon *con)
+getftp (struct urlinfo *u, long *len, long restval, ccon *con)
{
int csock, dtsock, res;
uerr_t err;
/* Third: Get the system type */
if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> SYST ... ");
- err = ftp_syst (&con->rbuf, &host_type);
+ err = ftp_syst (&con->rbuf, &con->rs);
/* FTPRERR */
switch (err)
{
if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> PWD ... ");
- err = ftp_pwd(&con->rbuf, &pwd);
+ err = ftp_pwd(&con->rbuf, &con->id);
/* FTPRERR */
switch (err)
{
DEBUGP (("changing working directory\n"));
if (*(u->dir) == '/')
{
- int pwd_len = strlen (pwd);
+ int pwd_len = strlen (con->id);
char *result = (char *)alloca (strlen (u->dir) + pwd_len + 10);
*result = '\0';
- switch (host_type)
+ switch (con->rs)
{
case ST_VMS:
{
for (tmpp = tmp_dir; *tmpp; tmpp++)
if (*tmpp=='/')
*tmpp = '.';
- strcpy (result, pwd);
+ strcpy (result, con->id);
/* pwd ends with ']', we have to get rid of it */
result[pwd_len - 1]= '\0';
strcat (result, tmp_dir);
break;
case ST_UNIX:
case ST_WINNT:
+ case ST_MACOS:
/* pwd_len == 1 means pwd = "/", but u->dir begins with '/'
already */
if (pwd_len > 1)
- strcpy (result, pwd);
+ strcpy (result, con->id);
strcat (result, u->dir);
- /* These look like debugging messages to me. */
-#if 0
- logprintf (LOG_VERBOSE, "\npwd=\"%s\"", pwd);
- logprintf (LOG_VERBOSE, "\nu->dir=\"%s\"", u->dir);
-#endif
+ DEBUGP(("\npwd=\"%s\"", con->id));
+ DEBUGP(("\nu->dir=\"%s\"", u->dir));
break;
default:
abort ();
err = ftp_loop_internal (u, NULL, con);
u->local = olocal;
if (err == RETROK)
- f = ftp_parse_ls (list_filename, host_type);
+ f = ftp_parse_ls (list_filename, con->rs);
else
f = NULL;
if (opt.remove_listing)
dlthis = 1;
if (opt.timestamping && f->type == FT_PLAINFILE)
- {
+ {
struct stat st;
/* If conversion of HTML files retrieved via FTP is ever implemented,
we'll need to stat() <file>.orig here when -K has been specified.
.orig suffix. */
if (!stat (u->local, &st))
{
+ int eq_size;
+ int cor_val;
/* Else, get it from the file. */
local_size = st.st_size;
tml = st.st_mtime;
- if (local_size == f->size && tml >= f->tstamp)
+ /* Compare file sizes only for servers that tell us correct
+ values. Assumme sizes being equal for servers that lie
+ about file size. */
+ cor_val = (con->rs == ST_UNIX || con->rs == ST_WINNT);
+ eq_size = cor_val ? (local_size == f->size) : 1 ;
+ if (f->tstamp <= tml && eq_size)
{
- logprintf (LOG_VERBOSE, _("\
-Server file no newer than local file `%s' -- not retrieving.\n\n"), u->local);
+ /* Remote file is older, file sizes can be compared and
+ are both equal. */
+ logprintf (LOG_VERBOSE, _("\
+Remote file no newer than local file `%s' -- not retrieving.\n"), u->local);
dlthis = 0;
}
- else if (local_size != f->size)
- {
- if (host_type == ST_VMS)
- {
- logprintf (LOG_VERBOSE, _("\
-Cannot compare sizes, remote system is VMS.\n"));
- dlthis = 0;
- }
- else
- {
- logprintf (LOG_VERBOSE, _("\
-The sizes do not match (local %ld) -- retrieving.\n"), local_size);
- }
- }
- }
+ else if (eq_size)
+ {
+ /* Remote file is newer or sizes cannot be matched */
+ logprintf (LOG_VERBOSE, _("\
+Remote file is newer than local file `%s' -- retrieving.\n\n"),
+ u->local);
+ }
+ else
+ {
+ /* Sizes do not match */
+ logprintf (LOG_VERBOSE, _("\
+The sizes do not match (local %ld) -- retrieving.\n\n"), local_size);
+ }
+ }
} /* opt.timestamping && f->type == FT_PLAINFILE */
switch (f->type)
{
rbuf_uninitialize (&con.rbuf);
con.st = ON_YOUR_OWN;
+ con.rs = ST_UNIX;
+ con.id = NULL;
res = RETROK; /* in case it's not used */
/* If the file name is empty, the user probably wants a directory
/* If a connection was left, quench it. */
if (rbuf_initialized_p (&con.rbuf))
CLOSE (RBUF_FD (&con.rbuf));
- FREE_MAYBE (pwd);
- pwd = NULL;
+ FREE_MAYBE (con.id);
+ con.id = NULL;
return res;
}