]> sjero.net Git - wget/commitdiff
[svn] Committed Jan's ftpparse patch with Hrvoje's modifications.
authorhniksic <devnull@localhost>
Tue, 21 Nov 2000 16:48:39 +0000 (08:48 -0800)
committerhniksic <devnull@localhost>
Tue, 21 Nov 2000 16:48:39 +0000 (08:48 -0800)
<sxshf518e72.fsf@florida.arsdigita.de>.

src/ChangeLog
src/Makefile.in
src/ftp-basic.c
src/ftp-ls.c
src/ftp.c
src/ftp.h
src/ftpparse.c [new file with mode: 0644]
src/ftpparse.h [new file with mode: 0644]

index 7ec215bd7343fb6f35c6ec32cd5a0dba58dfaeae..237439baecc95ebd6069a55294f0731b56e89ad9 100644 (file)
@@ -1,3 +1,31 @@
+2000-11-21  Hrvoje Niksic  <hniksic@arsdigita.com>
+
+       * ftp.c (getftp): Reformat Jan's code according to GNU coding
+       standards; remove (debugging?) printf's; use '\0' for the ASCII
+       zero character.  Use alloca() instead of malloc() for
+       inter-function temporary allocations.
+
+2000-11-18  Jan Prikryl  <prikryl@cg.tuwien.ac.at>
+
+       * ftpparse.c, ftpparse.h: New files.
+
+       * ftp-ls.c (ftp_parse_ls): Use ftp_parse_unix_ls for UNIX servers
+       only. Use ftp_parse_nonunix_ls otherwise.
+       (ftp_parse_nonunix_ls): Stub to the ftpparse library handling all
+       exotic FTP servers.
+
+       * ftp.h (stype): New enum, distinguishes UNIX, VMS, and "other"
+       FTP servers.
+
+       * ftp.c: New static wariables host_type, pwd, and pwd_len. 
+       (getftp): Support for VMS. Support for FTP servers that do not
+       place you in the root directory after login.
+       (ftp_retrieve_list): VMS is silent about the real file size, issue
+       a more appropriate message.
+       (ftp_get_listing): Pass host_type to ftp_parse_ls.
+
+       * ftp-basic.c (ftp_pwd, ftp_syst): New functions.
+
 2000-11-21  Hrvoje Niksic  <hniksic@arsdigita.com>
 
        * hash.c (hash_table_put): Don't overwrite deleted mappings.
 2000-11-21  Hrvoje Niksic  <hniksic@arsdigita.com>
 
        * hash.c (hash_table_put): Don't overwrite deleted mappings.
index bfe9868d4c422a95a82a2458c3918ad92b7bc405..27cf75a79b6edebc93c9fd5641e7a3185efd4e3f 100644 (file)
@@ -57,10 +57,10 @@ MD5_OBJ = @MD5_OBJ@
 OPIE_OBJ = @OPIE_OBJ@
 
 OBJ = $(ALLOCA) cmpt$o connect$o fnmatch$o ftp$o ftp-basic$o  \
 OPIE_OBJ = @OPIE_OBJ@
 
 OBJ = $(ALLOCA) cmpt$o connect$o fnmatch$o ftp$o ftp-basic$o  \
-      ftp-ls$o $(OPIE_OBJ) getopt$o hash$o headers$o host$o   \
-      html-parse$o html-url$o http$o init$o log$o main$o      \
-      $(MD5_OBJ) netrc$o rbuf$o recur$o retr$o snprintf$o     \
-      url$o utils$o version$o
+      ftp-ls$o $(OPIE_OBJ) ftpparse$o getopt$o hash$o         \
+      headers$o host$o html-parse$o html-url$o http$o init$o  \
+      log$o main$o $(MD5_OBJ) netrc$o rbuf$o recur$o retr$o   \
+      snprintf$o url$o utils$o version$o
 
 .SUFFIXES:
 .SUFFIXES: .c .o ._c ._o
 
 .SUFFIXES:
 .SUFFIXES: .c .o ._c ._o
index 8d433a69fe0d79fdfc5bfdc22a3920ce2f836fa0..2de25969ee451dc343fcf60afda8d75817f55e59 100644 (file)
@@ -41,6 +41,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include "rbuf.h"
 #include "connect.h"
 #include "host.h"
 #include "rbuf.h"
 #include "connect.h"
 #include "host.h"
+#include "ftp.h"
 
 #ifndef errno
 extern int errno;
 
 #ifndef errno
 extern int errno;
@@ -537,3 +538,98 @@ ftp_list (struct rbuf *rbuf, const char *file)
   /* All OK.  */
   return FTPOK;
 }
   /* All OK.  */
   return FTPOK;
 }
+
+/* Sends the SYST command to the server. */
+uerr_t
+ftp_syst (struct rbuf *rbuf, enum stype *host_type)
+{
+  char *request, *respline;
+  int nwritten;
+  uerr_t err;
+
+  /* Send SYST request.  */
+  request = ftp_request ("SYST", NULL);
+  nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
+  if (nwritten < 0)
+    {
+      free (request);
+      return WRITEFAILED;
+    }
+  free (request);
+  /* Get appropriate response.  */
+  err = ftp_response (rbuf, &respline);
+  if (err != FTPOK)
+    {
+      free (respline);
+      return err;
+    }
+  if (*respline == '5')
+    {
+      free (respline);
+      return FTPSRVERR;
+    }
+
+  /* Skip the number (215, but 200 (!!!) in case of VMS) */
+  strtok (respline, " ");
+  
+  /* Which system type has been reported (we are interested just in the
+     first word of the server response)?  */
+  request = strtok (NULL, " ");
+
+  if (!strcasecmp (request, "VMS"))
+    *host_type = ST_VMS;
+  else
+    if (!strcasecmp (request, "UNIX"))
+      *host_type = ST_UNIX;
+    else
+      *host_type = ST_OTHER;
+
+  free (respline);
+  /* All OK.  */
+  return FTPOK;
+}
+
+/* Sends the PWD command to the server. */
+uerr_t
+ftp_pwd (struct rbuf *rbuf, char **pwd)
+{
+  char *request, *respline;
+  int nwritten;
+  uerr_t err;
+
+  /* Send PWD request.  */
+  request = ftp_request ("PWD", NULL);
+  nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
+  if (nwritten < 0)
+    {
+      free (request);
+      return WRITEFAILED;
+    }
+  free (request);
+  /* Get appropriate response.  */
+  err = ftp_response (rbuf, &respline);
+  if (err != FTPOK)
+    {
+      free (respline);
+      return err;
+    }
+  if (*respline == '5')
+    {
+      free (respline);
+      return FTPSRVERR;
+    }
+
+  /* Skip the number (257), leading citation mark, trailing citation mark
+     and everything following it. */
+  strtok (respline, "\"");
+  request = strtok (NULL, "\"");
+  
+  /* Has the `pwd' been already allocated? Free! */
+  if (*pwd) free (*pwd);
+
+  *pwd = xstrdup (request);
+
+  free (respline);
+  /* All OK.  */
+  return FTPOK;
+}
index 884cf3d870a74360d339a4c1b11d0ba3f93195a7..54e345ed07f0f52631cfce3a1d187ae7aac79175 100644 (file)
@@ -1,5 +1,5 @@
 /* Parsing FTP `ls' output.
 /* Parsing FTP `ls' output.
-   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
 
 This file is part of Wget.
 
 
 This file is part of Wget.
 
@@ -38,6 +38,15 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include "ftp.h"
 #include "url.h"
 
 #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.  */
 /* 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.  */
@@ -376,18 +385,111 @@ ftp_parse_unix_ls (const char *file)
   return dir;
 }
 
   return dir;
 }
 
-/* This function is just a stub.  It should actually accept some kind
-   of information what system it is running on -- e.g. FPL_UNIX,
-   FPL_DOS, FPL_NT, FPL_VMS, etc. and a "guess-me" value, like
-   FPL_GUESS.  Then it would call the appropriate parsers to fill up
-   fileinfos.
+#ifdef HAVE_FTPPARSE
+
+/* 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)
+{
+  FILE *fp;
+  int len;
+
+  char *line;          /* tokenizer */
+  struct fileinfo *dir, *l, cur; /* list creation */
+
+  fp = fopen (file, "rb");
+  if (!fp)
+    {
+      logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
+      return NULL;
+    }
+  dir = l = NULL;
+
+  /* Line loop to end of file: */
+  while ((line = read_whole_line (fp)))
+    {
+      struct ftpparse 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';
+
+      if (ftpparse(&fp, line, len))
+        {
+         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;
+      }
+
+      free (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.  */
 
 
-   Since we currently support only the Unix FTP servers, this function
-   simply returns the result of ftp_parse_unix_ls().  */
 struct fileinfo *
 struct fileinfo *
-ftp_parse_ls (const char *file)
+ftp_parse_ls (const char *file, const enum stype system_type)
 {
 {
-  return ftp_parse_unix_ls (file);
+  if (system_type == ST_UNIX)
+    {
+      return ftp_parse_unix_ls (file);
+    }
+  else
+    {
+#ifdef HAVE_FTPPARSE
+      return ftp_parse_nonunix_ls (file);
+#else
+      /* #### Maybe log some warning here? */ 
+      return ftp_parse_unix_ls (file);
+#endif
+    }
 }
 \f
 /* Stuff for creating FTP index. */
 }
 \f
 /* Stuff for creating FTP index. */
index aa283cf8fbbc554799f39185b164a54c38b6d327..d628ad1d7527c42e0a0c7f99e4718d0709a8f7c9 100644 (file)
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -57,6 +57,10 @@ extern int h_errno;
 
 extern char ftp_last_respline[];
 
 
 extern char ftp_last_respline[];
 
+static enum stype host_type=ST_UNIX;
+static char *pwd;
+static int  pwd_len;
+
 /* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
    the string S, and return the number converted to long, if found, 0
    otherwise.  */
 /* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
    the string S, and return the number converted to long, if found, 0
    otherwise.  */
@@ -240,7 +244,64 @@ Error in server response, closing control connection.\n"));
          exit (1);
          break;
        }
          exit (1);
          break;
        }
-      /* Third: Set type to Image (binary).  */
+      /* Third: Get the system type */
+      if (!opt.server_response)
+       logprintf (LOG_VERBOSE, "==> SYST ... ");
+      err = ftp_syst (&con->rbuf, &host_type);
+      /* FTPRERR */
+      switch (err)
+       {
+       case FTPRERR:
+         logputs (LOG_VERBOSE, "\n");
+         logputs (LOG_NOTQUIET, _("\
+Error in server response, closing control connection.\n"));
+         CLOSE (csock);
+         rbuf_uninitialize (&con->rbuf);
+         return err;
+         break;
+       case FTPSRVERR:
+         logputs (LOG_VERBOSE, "\n");
+         logputs (LOG_NOTQUIET,
+                  _("Server error, can't determine system type.\n"));
+         break;
+       case FTPOK:
+         /* Everything is OK.  */
+         break;
+       default:
+         abort ();
+         break;
+       }
+      if (!opt.server_response)
+       logputs (LOG_VERBOSE, _("done.    "));
+
+      /* Fourth: Find the initial ftp directory */
+
+      if (!opt.server_response)
+       logprintf (LOG_VERBOSE, "==> PWD ... ");
+      err = ftp_pwd(&con->rbuf, &pwd);
+      pwd_len = strlen(pwd);
+      /* FTPRERR */
+      switch (err)
+      {
+       case FTPRERR || FTPSRVERR :
+         logputs (LOG_VERBOSE, "\n");
+         logputs (LOG_NOTQUIET, _("\
+Error in server response, closing control connection.\n"));
+         CLOSE (csock);
+         rbuf_uninitialize (&con->rbuf);
+         return err;
+         break;
+       case FTPOK:
+         /* Everything is OK.  */
+         break;
+       default:
+         abort ();
+         break;
+      }
+      if (!opt.server_response)
+       logputs (LOG_VERBOSE, _("done.\n"));
+
+      /* Fifth: Set the FTP type.  */
       if (!opt.server_response)
        logprintf (LOG_VERBOSE, "==> TYPE %c ... ", TOUPPER (u->ftp_type));
       err = ftp_type (&con->rbuf, TOUPPER (u->ftp_type));
       if (!opt.server_response)
        logprintf (LOG_VERBOSE, "==> TYPE %c ... ", TOUPPER (u->ftp_type));
       err = ftp_type (&con->rbuf, TOUPPER (u->ftp_type));
@@ -288,10 +349,55 @@ Error in server response, closing control connection.\n"));
        logputs (LOG_VERBOSE, _("==> CWD not needed.\n"));
       else
        {
        logputs (LOG_VERBOSE, _("==> CWD not needed.\n"));
       else
        {
-         /* Change working directory.  */
-         if (!opt.server_response)
-           logprintf (LOG_VERBOSE, "==> CWD %s ... ", u->dir);
-         err = ftp_cwd (&con->rbuf, u->dir);
+         /* Change working directory. If the FTP host runs VMS and
+             the path specified is absolute, we will have to convert
+             it to VMS style as VMS does not like leading slashes */
+         if (*(u->dir) == '/')
+           {
+             char *result = (char *)alloca (strlen (u->dir) + pwd_len + 10);
+             *result = '\0';
+             switch (host_type)
+               {
+               case ST_VMS:
+                 {
+                   char *tmp_dir, *tmpp;
+                   STRDUP_ALLOCA (tmp_dir, u->dir);
+                   for (tmpp = tmp_dir; *tmpp; tmpp++)
+                     if (*tmpp=='/')
+                       *tmpp = '.';
+                   strcpy (result, pwd);
+                   /* pwd ends with ']', we have to get rid of it */
+                   result[pwd_len - 1]= '\0';
+                   strcat (result, tmp_dir);
+                   strcat (result, "]");
+                 }
+                 break;
+               case ST_UNIX:
+                 /* pwd_len == 1 means pwd = "/", but u->dir begins with '/'
+                    already */
+                 if (pwd_len > 1)
+                   strcpy (result, pwd);
+                 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
+                 break;
+               default:
+                 abort ();
+                 break;
+               }
+             if (!opt.server_response)
+               logprintf (LOG_VERBOSE, "==> CWD %s ... ", result);
+             err = ftp_cwd (&con->rbuf, result);
+           }
+         else
+           {
+             if (!opt.server_response)
+               logprintf (LOG_VERBOSE, "==> CWD %s ... ", u->dir);
+             err = ftp_cwd (&con->rbuf, u->dir);
+           }
          /* FTPRERR, WRITEFAILED, FTPNSFOD */
          switch (err)
            {
          /* FTPRERR, WRITEFAILED, FTPNSFOD */
          switch (err)
            {
@@ -1080,7 +1186,7 @@ ftp_get_listing (struct urlinfo *u, ccon *con)
   err = ftp_loop_internal (u, NULL, con);
   u->local = olocal;
   if (err == RETROK)
   err = ftp_loop_internal (u, NULL, con);
   u->local = olocal;
   if (err == RETROK)
-    f = ftp_parse_ls (list_filename);
+    f = ftp_parse_ls (list_filename, host_type);
   else
     f = NULL;
   if (opt.remove_listing)
   else
     f = NULL;
   if (opt.remove_listing)
@@ -1177,13 +1283,22 @@ ftp_retrieve_list (struct urlinfo *u, struct fileinfo *f, ccon *con)
              if (local_size == f->size && tml >= f->tstamp)
                {
                  logprintf (LOG_VERBOSE, _("\
              if (local_size == f->size && tml >= f->tstamp)
                {
                  logprintf (LOG_VERBOSE, _("\
-Server file no newer than local file `%s' -- not retrieving.\n\n"), u->local);
+Server file not newer than local file `%s' -- not retrieving.\n\n"), u->local);
                  dlthis = 0;
                }
              else if (local_size != f->size)
                {
                  dlthis = 0;
                }
              else if (local_size != f->size)
                {
-                 logprintf (LOG_VERBOSE, _("\
+                 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);
 The sizes do not match (local %ld) -- retrieving.\n"), local_size);
+                   }
                }
            }
        }       /* opt.timestamping && f->type == FT_PLAINFILE */
                }
            }
        }       /* opt.timestamping && f->type == FT_PLAINFILE */
index 064e6354fdc8940aa14f16d9b97c890d311d46e2..61c7240006545f3b67588b5301ccb60d80cc426b 100644 (file)
--- a/src/ftp.h
+++ b/src/ftp.h
@@ -1,5 +1,5 @@
 /* Declarations for FTP support.
 /* Declarations for FTP support.
-   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 /* Need it for struct rbuf.  */
 #include "rbuf.h"
 
 /* Need it for struct rbuf.  */
 #include "rbuf.h"
 
+/* System types. */
+enum stype
+{
+  ST_UNIX,
+  ST_VMS,
+  ST_OTHER
+};
+  
 uerr_t ftp_response PARAMS ((struct rbuf *, char **));
 uerr_t ftp_login PARAMS ((struct rbuf *, const char *, const char *));
 uerr_t ftp_port PARAMS ((struct rbuf *));
 uerr_t ftp_response PARAMS ((struct rbuf *, char **));
 uerr_t ftp_login PARAMS ((struct rbuf *, const char *, const char *));
 uerr_t ftp_port PARAMS ((struct rbuf *));
@@ -30,6 +38,8 @@ uerr_t ftp_cwd PARAMS ((struct rbuf *, const char *));
 uerr_t ftp_retr PARAMS ((struct rbuf *, const char *));
 uerr_t ftp_rest PARAMS ((struct rbuf *, long));
 uerr_t ftp_list PARAMS ((struct rbuf *, const char *));
 uerr_t ftp_retr PARAMS ((struct rbuf *, const char *));
 uerr_t ftp_rest PARAMS ((struct rbuf *, long));
 uerr_t ftp_list PARAMS ((struct rbuf *, const char *));
+uerr_t ftp_syst PARAMS ((struct rbuf *, enum stype *));
+uerr_t ftp_pwd PARAMS ((struct rbuf *, char **));
 
 struct urlinfo;
 
 
 struct urlinfo;
 
@@ -89,7 +99,7 @@ typedef struct
   long dltime;                 /* time of the download */
 } ccon;
 
   long dltime;                 /* time of the download */
 } ccon;
 
-struct fileinfo *ftp_parse_ls PARAMS ((const char *));
+struct fileinfo *ftp_parse_ls PARAMS ((const char *, enum stype));
 uerr_t ftp_loop PARAMS ((struct urlinfo *, int *));
 
 uerr_t ftp_index (const char *, struct urlinfo *, struct fileinfo *);
 uerr_t ftp_loop PARAMS ((struct urlinfo *, int *));
 
 uerr_t ftp_index (const char *, struct urlinfo *, struct fileinfo *);
diff --git a/src/ftpparse.c b/src/ftpparse.c
new file mode 100644 (file)
index 0000000..ead436c
--- /dev/null
@@ -0,0 +1,411 @@
+/* This file is not part of Wget proper, but is included here with the
+   permission of the author.  If you wish to use Wget without
+   ftpparse, undefine HAVE_FTPPARSE in ftp-ls.c.  */
+
+/* ftpparse.c, ftpparse.h: library for parsing FTP LIST responses
+D. J. Bernstein, djb@pobox.com
+19970712 (doc updated 19970810)
+Commercial use is fine, if you let me know what programs you're using this in.
+
+Currently covered:
+EPLF.
+UNIX ls, with or without gid.
+Microsoft FTP Service.
+Windows NT FTP Server.
+VMS.
+WFTPD (DOS).
+NetPresenz (Mac).
+NetWare.
+
+Definitely not covered: 
+Long VMS filenames, with information split across two lines.
+NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories).
+
+Written for maximum portability, but tested only under UNIX so far.  */
+
+#include <time.h>
+#include "ftpparse.h"
+
+static long totai(year,month,mday)
+long year;
+long month;
+long mday;
+{
+  /* adapted from datetime_untai() */
+  /* about 100x faster than typical mktime() */
+  long result;
+  if (month >= 2) month -= 2;
+  else { month += 10; --year; }
+  result = (mday - 1) * 10 + 5 + 306 * month;
+  result /= 10;
+  if (result == 365) { year -= 3; result = 1460; }
+  else result += 365 * (year % 4);
+  year /= 4;
+  result += 1461 * (year % 25);
+  year /= 25;
+  if (result == 36524) { year -= 3; result = 146096; }
+  else { result += 36524 * (year % 4); }
+  year /= 4;
+  result += 146097 * (year - 5);
+  result += 11017;
+  return result * 86400;
+}
+
+static int flagneedbase = 1;
+static time_t base; /* time() value on this OS at the beginning of 1970 TAI */
+static long now; /* current time */
+static int flagneedcurrentyear = 1;
+static long currentyear; /* approximation to current year */
+
+static void initbase()
+{
+  struct tm *t;
+  if (!flagneedbase) return;
+
+  base = 0;
+  t = gmtime(&base);
+  base = -(totai(t->tm_year + 1900,t->tm_mon,t->tm_mday) + t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec);
+  /* time_t is assumed to be measured in TAI seconds. */
+  /* base may be slightly off if time_t is measured in UTC seconds. */
+  /* Typical software naively claims to use UTC but actually uses TAI. */
+  flagneedbase = 0;
+}
+
+static void initnow()
+{
+  long day;
+  long year;
+
+  initbase();
+  now = time((time_t *) 0) - base;
+
+  if (flagneedcurrentyear) {
+    /* adapted from datetime_tai() */
+    day = now / 86400;
+    if ((now % 86400) < 0) --day;
+    day -= 11017;
+    year = 5 + day / 146097;
+    day = day % 146097;
+    if (day < 0) { day += 146097; --year; }
+    year *= 4;
+    if (day == 146096) { year += 3; day = 36524; }
+    else { year += day / 36524; day %= 36524; }
+    year *= 25;
+    year += day / 1461;
+    day %= 1461;
+    year *= 4;
+    if (day == 1460) { year += 3; day = 365; }
+    else { year += day / 365; day %= 365; }
+    day *= 10;
+    if ((day + 5) / 306 >= 10) ++year;
+    currentyear = year;
+    flagneedcurrentyear = 0;
+  }
+}
+
+/* UNIX ls does not show the year for dates in the last six months. */
+/* So we have to guess the year. */
+/* Apparently NetWare uses ``twelve months'' instead of ``six months''; ugh. */
+/* Some versions of ls also fail to show the year for future dates. */
+static long guesstai(month,mday)
+long month;
+long mday;
+{
+  long year;
+  long t;
+
+  initnow();
+
+  for (year = currentyear - 1;year < currentyear + 100;++year) {
+    t = totai(year,month,mday);
+    if (now - t < 350 * 86400)
+      return t;
+  }
+}
+
+static int check(buf,monthname)
+char *buf;
+char *monthname;
+{
+  if ((buf[0] != monthname[0]) && (buf[0] != monthname[0] - 32)) return 0;
+  if ((buf[1] != monthname[1]) && (buf[1] != monthname[1] - 32)) return 0;
+  if ((buf[2] != monthname[2]) && (buf[2] != monthname[2] - 32)) return 0;
+  return 1;
+}
+
+static char *months[12] = {
+  "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"
+} ;
+
+static int getmonth(buf,len)
+char *buf;
+int len;
+{
+  int i;
+  if (len == 3)
+    for (i = 0;i < 12;++i)
+      if (check(buf,months[i])) return i;
+  return -1;
+}
+
+static long getlong(buf,len)
+char *buf;
+int len;
+{
+  long u = 0;
+  while (len-- > 0)
+    u = u * 10 + (*buf++ - '0');
+  return u;
+}
+
+int ftpparse(fp,buf,len)
+struct ftpparse *fp;
+char *buf;
+int len;
+{
+  int i;
+  int j;
+  int state;
+  long size;
+  long year;
+  long month;
+  long mday;
+  long hour;
+  long minute;
+
+  fp->name = 0;
+  fp->namelen = 0;
+  fp->flagtrycwd = 0;
+  fp->flagtryretr = 0;
+  fp->sizetype = FTPPARSE_SIZE_UNKNOWN;
+  fp->size = 0;
+  fp->mtimetype = FTPPARSE_MTIME_UNKNOWN;
+  fp->mtime = 0;
+  fp->idtype = FTPPARSE_ID_UNKNOWN;
+  fp->id = 0;
+  fp->idlen = 0;
+
+  if (len < 2) /* an empty name in EPLF, with no info, could be 2 chars */
+    return 0;
+
+  switch(*buf) {
+    /* see http://pobox.com/~djb/proto/eplf.txt */
+    /* "+i8388621.29609,m824255902,/,\tdev" */
+    /* "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF" */
+    case '+':
+      i = 1;
+      for (j = 1;j < len;++j) {
+        if (buf[j] == 9) {
+         fp->name = buf + j + 1;
+         fp->namelen = len - j - 1;
+         return 1;
+        }
+        if (buf[j] == ',') {
+         switch(buf[i]) {
+           case '/':
+             fp->flagtrycwd = 1;
+             break;
+           case 'r':
+             fp->flagtryretr = 1;
+             break;
+           case 's':
+             fp->sizetype = FTPPARSE_SIZE_BINARY;
+             fp->size = getlong(buf + i + 1,j - i - 1);
+             break;
+           case 'm':
+             fp->mtimetype = FTPPARSE_MTIME_LOCAL;
+             initbase();
+             fp->mtime = base + getlong(buf + i + 1,j - i - 1);
+             break;
+           case 'i':
+             fp->idtype = FTPPARSE_ID_FULL;
+             fp->id = buf + i + 1;
+             fp->idlen = j - i - 1;
+         }
+         i = j + 1;
+        }
+      }
+      return 0;
+    
+    /* UNIX-style listing, without inum and without blocks */
+    /* "-rw-r--r--   1 root     other        531 Jan 29 03:26 README" */
+    /* "dr-xr-xr-x   2 root     other        512 Apr  8  1994 etc" */
+    /* "dr-xr-xr-x   2 root     512 Apr  8  1994 etc" */
+    /* "lrwxrwxrwx   1 root     other          7 Jan 25 00:17 bin -> usr/bin" */
+    /* Also produced by Microsoft's FTP servers for Windows: */
+    /* "----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z" */
+    /* "d---------   1 owner    group               0 May  9 19:45 Softlib" */
+    /* Also WFTPD for MSDOS: */
+    /* "-rwxrwxrwx   1 noone    nogroup      322 Aug 19  1996 message.ftp" */
+    /* Also NetWare: */
+    /* "d [R----F--] supervisor            512       Jan 16 18:53    login" */
+    /* "- [R----F--] rhesus             214059       Oct 20 15:27    cx.exe" */
+    /* Also NetPresenz for the Mac: */
+    /* "-------r--         326  1391972  1392298 Nov 22  1995 MegaPhone.sit" */
+    /* "drwxrwxr-x               folder        2 May 10  1996 network" */
+    case 'b':
+    case 'c':
+    case 'd':
+    case 'l':
+    case 'p':
+    case 's':
+    case '-':
+
+      if (*buf == 'd') fp->flagtrycwd = 1;
+      if (*buf == '-') fp->flagtryretr = 1;
+      if (*buf == 'l') fp->flagtrycwd = fp->flagtryretr = 1;
+
+      state = 1;
+      i = 0;
+      for (j = 1;j < len;++j)
+       if ((buf[j] == ' ') && (buf[j - 1] != ' ')) {
+         switch(state) {
+           case 1: /* skipping perm */
+             state = 2;
+             break;
+           case 2: /* skipping nlink */
+             state = 3;
+             if ((j - i == 6) && (buf[i] == 'f')) /* for NetPresenz */
+               state = 4;
+             break;
+           case 3: /* skipping uid */
+             state = 4;
+             break;
+           case 4: /* getting tentative size */
+             size = getlong(buf + i,j - i);
+             state = 5;
+             break;
+           case 5: /* searching for month, otherwise getting tentative size */
+             month = getmonth(buf + i,j - i);
+             if (month >= 0)
+               state = 6;
+             else
+               size = getlong(buf + i,j - i);
+             break;
+           case 6: /* have size and month */
+             mday = getlong(buf + i,j - i);
+             state = 7;
+             break;
+           case 7: /* have size, month, mday */
+             if ((j - i == 4) && (buf[i + 1] == ':')) {
+               hour = getlong(buf + i,1);
+               minute = getlong(buf + i + 2,2);
+               fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
+               initbase();
+               fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
+             } else if ((j - i == 5) && (buf[i + 2] == ':')) {
+               hour = getlong(buf + i,2);
+               minute = getlong(buf + i + 3,2);
+               fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
+               initbase();
+               fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
+             }
+             else if (j - i >= 4) {
+               year = getlong(buf + i,j - i);
+               fp->mtimetype = FTPPARSE_MTIME_REMOTEDAY;
+               initbase();
+               fp->mtime = base + totai(year,month,mday);
+             }
+             else
+               return 0;
+             fp->name = buf + j + 1;
+             fp->namelen = len - j - 1;
+             state = 8;
+             break;
+           case 8: /* twiddling thumbs */
+             break;
+         }
+         i = j + 1;
+         while ((i < len) && (buf[i] == ' ')) ++i;
+       }
+
+      if (state != 8)
+       return 0;
+
+      fp->size = size;
+      fp->sizetype = FTPPARSE_SIZE_ASCII;
+
+      if (*buf == 'l')
+       for (i = 0;i + 3 < fp->namelen;++i)
+         if (fp->name[i] == ' ')
+           if (fp->name[i + 1] == '-')
+             if (fp->name[i + 2] == '>')
+               if (fp->name[i + 3] == ' ') {
+                 fp->namelen = i;
+                 break;
+               }
+
+      /* eliminate extra NetWare spaces */
+      if ((buf[1] == ' ') || (buf[1] == '['))
+       if (fp->namelen > 3)
+         if (fp->name[0] == ' ')
+           if (fp->name[1] == ' ')
+             if (fp->name[2] == ' ') {
+               fp->name += 3;
+               fp->namelen -= 3;
+             }
+
+      return 1;
+  }
+
+  /* MultiNet (some spaces removed from examples) */
+  /* "00README.TXT;1      2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)" */
+  /* "CORE.DIR;1          1  8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)" */
+  /* and non-MutliNet VMS: */
+  /* "CII-MANUAL.TEX;1  213/216  29-JAN-1996 03:33:12  [ANONYMOU,ANONYMOUS]   (RWED,RWED,,)" */
+  for (i = 0;i < len;++i)
+    if (buf[i] == ';')
+      break;
+  if (i < len) {
+    fp->name = buf;
+    fp->namelen = i;
+    if (i > 4)
+      if (buf[i - 4] == '.')
+        if (buf[i - 3] == 'D')
+          if (buf[i - 2] == 'I')
+            if (buf[i - 1] == 'R') {
+             fp->namelen -= 4;
+             fp->flagtrycwd = 1;
+           }
+    if (!fp->flagtrycwd)
+      fp->flagtryretr = 1;
+    while (buf[i] != ' ') if (++i == len) return 0;
+    while (buf[i] == ' ') if (++i == len) return 0;
+    while (buf[i] != ' ') if (++i == len) return 0;
+    while (buf[i] == ' ') if (++i == len) return 0;
+    j = i;
+    while (buf[j] != '-') if (++j == len) return 0;
+    mday = getlong(buf + i,j - i);
+    while (buf[j] == '-') if (++j == len) return 0;
+    i = j;
+    while (buf[j] != '-') if (++j == len) return 0;
+    month = getmonth(buf + i,j - i);
+    if (month < 0) return 0;
+    while (buf[j] == '-') if (++j == len) return 0;
+    i = j;
+    while (buf[j] != ' ') if (++j == len) return 0;
+    year = getlong(buf + i,j - i);
+    while (buf[j] == ' ') if (++j == len) return 0;
+    i = j;
+    while (buf[j] != ':') if (++j == len) return 0;
+    hour = getlong(buf + i,j - i);
+    while (buf[j] == ':') if (++j == len) return 0;
+    i = j;
+    while ((buf[j] != ':') && (buf[j] != ' ')) if (++j == len) return 0;
+    minute = getlong(buf + i,j - i);
+
+    fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
+    initbase();
+    fp->mtime = base + totai(year,month,mday) + hour * 3600 + minute * 60;
+
+    return 1;
+  }
+
+  /* Some useless lines, safely ignored: */
+  /* "Total of 11 Files, 10966 Blocks." (VMS) */
+  /* "total 14786" (UNIX) */
+  /* "DISK$ANONFTP:[ANONYMOUS]" (VMS) */
+  /* "Directory DISK$PCSA:[ANONYM]" (VMS) */
+
+  return 0;
+}
diff --git a/src/ftpparse.h b/src/ftpparse.h
new file mode 100644 (file)
index 0000000..9d62605
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef FTPPARSE_H
+#define FTPPARSE_H
+
+/*
+ftpparse(&fp,buf,len) tries to parse one line of LIST output.
+
+The line is an array of len characters stored in buf.
+It should not include the terminating CR LF; so buf[len] is typically CR.
+
+If ftpparse() can't find a filename, it returns 0.
+
+If ftpparse() can find a filename, it fills in fp and returns 1.
+fp is a struct ftpparse, defined below.
+The name is an array of fp.namelen characters stored in fp.name;
+fp.name points somewhere within buf.
+*/
+
+struct ftpparse {
+  char *name; /* not necessarily 0-terminated */
+  int namelen;
+  int flagtrycwd; /* 0 if cwd is definitely pointless, 1 otherwise */
+  int flagtryretr; /* 0 if retr is definitely pointless, 1 otherwise */
+  int sizetype;
+  long size; /* number of octets */
+  int mtimetype;
+  time_t mtime; /* modification time */
+  int idtype;
+  char *id; /* not necessarily 0-terminated */
+  int idlen;
+} ;
+
+#define FTPPARSE_SIZE_UNKNOWN 0
+#define FTPPARSE_SIZE_BINARY 1 /* size is the number of octets in TYPE I */
+#define FTPPARSE_SIZE_ASCII 2 /* size is the number of octets in TYPE A */
+
+#define FTPPARSE_MTIME_UNKNOWN 0
+#define FTPPARSE_MTIME_LOCAL 1 /* time is correct */
+#define FTPPARSE_MTIME_REMOTEMINUTE 2 /* time zone and secs are unknown */
+#define FTPPARSE_MTIME_REMOTEDAY 3 /* time zone and time of day are unknown */
+/*
+When a time zone is unknown, it is assumed to be GMT. You may want
+to use localtime() for LOCAL times, along with an indication that the
+time is correct in the local time zone, and gmtime() for REMOTE* times.
+*/
+
+#define FTPPARSE_ID_UNKNOWN 0
+#define FTPPARSE_ID_FULL 1 /* unique identifier for files on this FTP server */
+
+extern int ftpparse();
+
+#endif