]> sjero.net Git - wget/blob - src/ftp-ls.c
d9077a375e55541d86bd142eb982d4da4d8060cf
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3    2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
4    Inc.
5
6 This file is part of GNU Wget.
7
8 GNU Wget is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
12
13 GNU Wget is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
20
21 Additional permission under GNU GPL version 3 section 7
22
23 If you modify this program, or any covered work, by linking or
24 combining it with the OpenSSL project's OpenSSL library (or a
25 modified version of that library), containing parts covered by the
26 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
27 grants you additional permission to convey the resulting work.
28 Corresponding Source for a non-source form of such a combination
29 shall include the source code for the parts of OpenSSL used as well
30 as that of the covered work.  */
31
32 #include "wget.h"
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <time.h>
40 #include "utils.h"
41 #include "ftp.h"
42 #include "url.h"
43 #include "convert.h"            /* for html_quote_string prototype */
44 #include "retr.h"               /* for output_stream */
45
46 /* Converts symbolic permissions to number-style ones, e.g. string
47    rwxr-xr-x to 755.  For now, it knows nothing of
48    setuid/setgid/sticky.  ACLs are ignored.  */
49 static int
50 symperms (const char *s)
51 {
52   int perms = 0, i;
53
54   if (strlen (s) < 9)
55     return 0;
56   for (i = 0; i < 3; i++, s += 3)
57     {
58       perms <<= 3;
59       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
60                 (s[2] == 'x' || s[2] == 's'));
61     }
62   return perms;
63 }
64
65
66 /* Cleans a line of text so that it can be consistently parsed. Destroys
67    <CR> and <LF> in case that thay occur at the end of the line and
68    replaces all <TAB> character with <SPACE>. Returns the length of the
69    modified line. */
70 static int
71 clean_line (char *line, int len)
72 {
73   if (len <= 0) return 0;
74
75   while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
76     line[--len] = '\0';
77
78   if (!len) return 0;
79
80   for ( ; *line ; line++ ) if (*line == '\t') *line = ' ';
81
82   return len;
83 }
84
85 /* Convert the Un*x-ish style directory listing stored in FILE to a
86    linked list of fileinfo (system-independent) entries.  The contents
87    of FILE are considered to be produced by the standard Unix `ls -la'
88    output (whatever that might be).  BSD (no group) and SYSV (with
89    group) listings are handled.
90
91    The time stamps are stored in a separate variable, time_t
92    compatible (I hope).  The timezones are ignored.  */
93 static struct fileinfo *
94 ftp_parse_unix_ls (const char *file, int ignore_perms)
95 {
96   FILE *fp;
97   static const char *months[] = {
98     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
99     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
100   };
101   int next, len, i, error, ignore;
102   int year, month, day;         /* for time analysis */
103   int hour, min, sec, ptype;
104   struct tm timestruct, *tnow;
105   time_t timenow;
106   size_t bufsize = 0;
107
108   char *line = NULL, *tok, *ptok;      /* tokenizer */
109   struct fileinfo *dir, *l, cur; /* list creation */
110
111   fp = fopen (file, "rb");
112   if (!fp)
113     {
114       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
115       return NULL;
116     }
117   dir = l = NULL;
118
119   /* Line loop to end of file: */
120   while ((len = getline (&line, &bufsize, fp)) > 0)
121     {
122       len = clean_line (line, len);
123       /* Skip if total...  */
124       if (!strncasecmp (line, "total", 5))
125         continue;
126       /* Get the first token (permissions).  */
127       tok = strtok (line, " ");
128       if (!tok)
129         continue;
130
131       cur.name = NULL;
132       cur.linkto = NULL;
133
134       /* Decide whether we deal with a file or a directory.  */
135       switch (*tok)
136         {
137         case '-':
138           cur.type = FT_PLAINFILE;
139           DEBUGP (("PLAINFILE; "));
140           break;
141         case 'd':
142           cur.type = FT_DIRECTORY;
143           DEBUGP (("DIRECTORY; "));
144           break;
145         case 'l':
146           cur.type = FT_SYMLINK;
147           DEBUGP (("SYMLINK; "));
148           break;
149         default:
150           cur.type = FT_UNKNOWN;
151           DEBUGP (("UNKNOWN; "));
152           break;
153         }
154
155       if (ignore_perms)
156         {
157           switch (cur.type)
158             {
159             case FT_PLAINFILE:
160               cur.perms = 0644;
161               break;
162             case FT_DIRECTORY:
163               cur.perms = 0755;
164               break;
165             default:
166               /*cur.perms = 1023;*/     /* #### What is this?  --hniksic */
167               cur.perms = 0644;
168             }
169           DEBUGP (("implicit perms %0o; ", cur.perms));
170         }
171        else
172          {
173            cur.perms = symperms (tok + 1);
174            DEBUGP (("perms %0o; ", cur.perms));
175          }
176
177       error = ignore = 0;       /* Erroneous and ignoring entries are
178                                    treated equally for now.  */
179       year = hour = min = sec = 0; /* Silence the compiler.  */
180       month = day = 0;
181       ptype = TT_DAY;
182       next = -1;
183       /* While there are tokens on the line, parse them.  Next is the
184          number of tokens left until the filename.
185
186          Use the month-name token as the "anchor" (the place where the
187          position wrt the file name is "known").  When a month name is
188          encountered, `next' is set to 5.  Also, the preceding
189          characters are parsed to get the file size.
190
191          This tactic is quite dubious when it comes to
192          internationalization issues (non-English month names), but it
193          works for now.  */
194       tok = line;
195       while (ptok = tok,
196              (tok = strtok (NULL, " ")) != NULL)
197         {
198           --next;
199           if (next < 0)         /* a month name was not encountered */
200             {
201               for (i = 0; i < 12; i++)
202                 if (!strcasecmp (tok, months[i]))
203                   break;
204               /* If we got a month, it means the token before it is the
205                  size, and the filename is three tokens away.  */
206               if (i != 12)
207                 {
208                   wgint size;
209
210                   /* Parse the previous token with str_to_wgint.  */
211                   if (ptok == line)
212                     {
213                       /* Something has gone wrong during parsing. */
214                       error = 1;
215                       break;
216                     }
217                   errno = 0;
218                   size = str_to_wgint (ptok, NULL, 10);
219                   if (size == WGINT_MAX && errno == ERANGE)
220                     /* Out of range -- ignore the size.  #### Should
221                        we refuse to start the download.  */
222                     cur.size = 0;
223                   else
224                     cur.size = size;
225                   DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
226
227                   month = i;
228                   next = 5;
229                   DEBUGP (("month: %s; ", months[month]));
230                 }
231             }
232           else if (next == 4)   /* days */
233             {
234               if (tok[1])       /* two-digit... */
235                 day = 10 * (*tok - '0') + tok[1] - '0';
236               else              /* ...or one-digit */
237                 day = *tok - '0';
238               DEBUGP (("day: %d; ", day));
239             }
240           else if (next == 3)
241             {
242               /* This ought to be either the time, or the year.  Let's
243                  be flexible!
244
245                  If we have a number x, it's a year.  If we have x:y,
246                  it's hours and minutes.  If we have x:y:z, z are
247                  seconds.  */
248               year = 0;
249               min = hour = sec = 0;
250               /* We must deal with digits.  */
251               if (c_isdigit (*tok))
252                 {
253                   /* Suppose it's year.  */
254                   for (; c_isdigit (*tok); tok++)
255                     year = (*tok - '0') + 10 * year;
256                   if (*tok == ':')
257                     {
258                       /* This means these were hours!  */
259                       hour = year;
260                       year = 0;
261                       ptype = TT_HOUR_MIN;
262                       ++tok;
263                       /* Get the minutes...  */
264                       for (; c_isdigit (*tok); tok++)
265                         min = (*tok - '0') + 10 * min;
266                       if (*tok == ':')
267                         {
268                           /* ...and the seconds.  */
269                           ++tok;
270                           for (; c_isdigit (*tok); tok++)
271                             sec = (*tok - '0') + 10 * sec;
272                         }
273                     }
274                 }
275               if (year)
276                 DEBUGP (("year: %d (no tm); ", year));
277               else
278                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
279             }
280           else if (next == 2)    /* The file name */
281             {
282               int fnlen;
283               char *p;
284
285               /* Since the file name may contain a SPC, it is possible
286                  for strtok to handle it wrong.  */
287               fnlen = strlen (tok);
288               if (fnlen < len - (tok - line))
289                 {
290                   /* So we have a SPC in the file name.  Restore the
291                      original.  */
292                   tok[fnlen] = ' ';
293                   /* If the file is a symbolic link, it should have a
294                      ` -> ' somewhere.  */
295                   if (cur.type == FT_SYMLINK)
296                     {
297                       p = strstr (tok, " -> ");
298                       if (!p)
299                         {
300                           error = 1;
301                           break;
302                         }
303                       cur.linkto = xstrdup (p + 4);
304                       DEBUGP (("link to: %s\n", cur.linkto));
305                       /* And separate it from the file name.  */
306                       *p = '\0';
307                     }
308                 }
309               /* If we have the filename, add it to the list of files or
310                  directories.  */
311               /* "." and ".." are an exception!  */
312               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
313                 {
314                   DEBUGP (("\nIgnoring `.' and `..'; "));
315                   ignore = 1;
316                   break;
317                 }
318               /* Some FTP sites choose to have ls -F as their default
319                  LIST output, which marks the symlinks with a trailing
320                  `@', directory names with a trailing `/' and
321                  executables with a trailing `*'.  This is no problem
322                  unless encountering a symbolic link ending with `@',
323                  or an executable ending with `*' on a server without
324                  default -F output.  I believe these cases are very
325                  rare.  */
326               fnlen = strlen (tok); /* re-calculate `fnlen' */
327               cur.name = xmalloc (fnlen + 1);
328               memcpy (cur.name, tok, fnlen + 1);
329               if (fnlen)
330                 {
331                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
332                     {
333                       cur.name[fnlen - 1] = '\0';
334                       DEBUGP (("trailing `/' on dir.\n"));
335                     }
336                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
337                     {
338                       cur.name[fnlen - 1] = '\0';
339                       DEBUGP (("trailing `@' on link.\n"));
340                     }
341                   else if (cur.type == FT_PLAINFILE
342                            && (cur.perms & 0111)
343                            && cur.name[fnlen - 1] == '*')
344                     {
345                       cur.name[fnlen - 1] = '\0';
346                       DEBUGP (("trailing `*' on exec.\n"));
347                     }
348                 } /* if (fnlen) */
349               else
350                 error = 1;
351               break;
352             }
353           else
354             abort ();
355         } /* while */
356
357       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
358         error = 1;
359
360       DEBUGP (("%s\n", cur.name ? cur.name : ""));
361
362       if (error || ignore)
363         {
364           DEBUGP (("Skipping.\n"));
365           xfree_null (cur.name);
366           xfree_null (cur.linkto);
367           continue;
368         }
369
370       if (!dir)
371         {
372           l = dir = xnew (struct fileinfo);
373           memcpy (l, &cur, sizeof (cur));
374           l->prev = l->next = NULL;
375         }
376       else
377         {
378           cur.prev = l;
379           l->next = xnew (struct fileinfo);
380           l = l->next;
381           memcpy (l, &cur, sizeof (cur));
382           l->next = NULL;
383         }
384       /* Get the current time.  */
385       timenow = time (NULL);
386       tnow = localtime (&timenow);
387       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
388       timestruct.tm_sec   = sec;
389       timestruct.tm_min   = min;
390       timestruct.tm_hour  = hour;
391       timestruct.tm_mday  = day;
392       timestruct.tm_mon   = month;
393       if (year == 0)
394         {
395           /* Some listings will not specify the year if it is "obvious"
396              that the file was from the previous year.  E.g. if today
397              is 97-01-12, and you see a file of Dec 15th, its year is
398              1996, not 1997.  Thanks to Vladimir Volovich for
399              mentioning this!  */
400           if (month > tnow->tm_mon)
401             timestruct.tm_year = tnow->tm_year - 1;
402           else
403             timestruct.tm_year = tnow->tm_year;
404         }
405       else
406         timestruct.tm_year = year;
407       if (timestruct.tm_year >= 1900)
408         timestruct.tm_year -= 1900;
409       timestruct.tm_wday  = 0;
410       timestruct.tm_yday  = 0;
411       timestruct.tm_isdst = -1;
412       l->tstamp = mktime (&timestruct); /* store the time-stamp */
413       l->ptype = ptype;
414     }
415
416   xfree (line);
417   fclose (fp);
418   return dir;
419 }
420
421 static struct fileinfo *
422 ftp_parse_winnt_ls (const char *file)
423 {
424   FILE *fp;
425   int len;
426   int year, month, day;         /* for time analysis */
427   int hour, min;
428   size_t bufsize = 0;
429   struct tm timestruct;
430
431   char *line = NULL, *tok;             /* tokenizer */
432   char *filename;
433   struct fileinfo *dir, *l, cur; /* list creation */
434
435   fp = fopen (file, "rb");
436   if (!fp)
437     {
438       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
439       return NULL;
440     }
441   dir = l = NULL;
442
443   /* Line loop to end of file: */
444   while ((len = getline (&line, &bufsize, fp)) > 0)
445     {
446       len = clean_line (line, len);
447
448       /* Name begins at 39 column of the listing if date presented in `mm-dd-yy'
449          format or at 41 column if date presented in `mm-dd-yyyy' format. Thus,
450          we cannot extract name before we parse date. Using this information we
451          also can recognize filenames that begin with a series of space
452          characters (but who really wants to use such filenames anyway?). */
453       if (len < 40) continue;
454       filename = line + 39;
455
456       /* First column: mm-dd-yy or mm-dd-yyyy. Should atoi() on the month fail,
457          january will be assumed.  */
458       tok = strtok(line, "-");
459       if (tok == NULL) continue;
460       month = atoi(tok) - 1;
461       if (month < 0) month = 0;
462       tok = strtok(NULL, "-");
463       if (tok == NULL) continue;
464       day = atoi(tok);
465       tok = strtok(NULL, " ");
466       if (tok == NULL) continue;
467       year = atoi(tok);
468       /* Assuming the epoch starting at 1.1.1970 */
469       if (year <= 70)
470         {
471           year += 100;
472         }
473       else if (year >= 1900)
474         {
475           year -= 1900;
476           filename += 2;
477         }
478       /* Now it is possible to determine the position of the first symbol in
479          filename. */
480       cur.name = xstrdup(filename);
481       DEBUGP (("Name: '%s'\n", cur.name));
482
483
484       /* Second column: hh:mm[AP]M, listing does not contain value for
485          seconds */
486       tok = strtok(NULL,  ":");
487       if (tok == NULL) continue;
488       hour = atoi(tok);
489       tok = strtok(NULL,  "M");
490       if (tok == NULL) continue;
491       min = atoi(tok);
492       /* Adjust hour from AM/PM. Just for the record, the sequence goes
493          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
494       tok+=2;
495       if (hour == 12)  hour  = 0;
496       if (*tok == 'P') hour += 12;
497
498       DEBUGP (("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n",
499               year+1900, month, day, hour, min));
500
501       /* Build the time-stamp (copy & paste from above) */
502       timestruct.tm_sec   = 0;
503       timestruct.tm_min   = min;
504       timestruct.tm_hour  = hour;
505       timestruct.tm_mday  = day;
506       timestruct.tm_mon   = month;
507       timestruct.tm_year  = year;
508       timestruct.tm_wday  = 0;
509       timestruct.tm_yday  = 0;
510       timestruct.tm_isdst = -1;
511       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
512       cur.ptype = TT_HOUR_MIN;
513
514       DEBUGP (("Timestamp: %ld\n", cur.tstamp));
515
516       /* Third column: Either file length, or <DIR>. We also set the
517          permissions (guessed as 0644 for plain files and 0755 for
518          directories as the listing does not give us a clue) and filetype
519          here. */
520       tok = strtok(NULL, " ");
521       if (tok == NULL) continue;
522       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
523       if (tok == NULL) continue;
524       if (*tok == '<')
525         {
526           cur.type  = FT_DIRECTORY;
527           cur.size  = 0;
528           cur.perms = 0755;
529           DEBUGP (("Directory\n"));
530         }
531       else
532         {
533           wgint size;
534           cur.type  = FT_PLAINFILE;
535           errno = 0;
536           size = str_to_wgint (tok, NULL, 10);
537           if (size == WGINT_MAX && errno == ERANGE)
538             cur.size = 0;       /* overflow */
539           else
540             cur.size = size;
541           cur.perms = 0644;
542           DEBUGP (("File, size %s bytes\n", number_to_static_string (cur.size)));
543         }
544
545       cur.linkto = NULL;
546
547       /* And put everything into the linked list */
548       if (!dir)
549         {
550           l = dir = xnew (struct fileinfo);
551           memcpy (l, &cur, sizeof (cur));
552           l->prev = l->next = NULL;
553         }
554       else
555         {
556           cur.prev = l;
557           l->next = xnew (struct fileinfo);
558           l = l->next;
559           memcpy (l, &cur, sizeof (cur));
560           l->next = NULL;
561         }
562     }
563
564   xfree (line);
565   fclose(fp);
566   return dir;
567 }
568
569
570
571 /* Convert the VMS-style directory listing stored in "file" to a
572    linked list of fileinfo (system-independent) entries.  The contents
573    of FILE are considered to be produced by the standard VMS
574    "DIRECTORY [/SIZE [= ALL]] /DATE [/OWNER] [/PROTECTION]" command,
575    more or less.  (Different VMS FTP servers may have different headers,
576    and may not supply the same data, but all should be subsets of this.)
577
578    VMS normally provides local (server) time and date information.
579    Define the logical name or environment variable
580    "WGET_TIMEZONE_DIFFERENTIAL" (seconds) to adjust the receiving local
581    times if different from the remote local times.
582
583    2005-02-23 SMS.
584    Added code to eliminate "^" escape characters from ODS5 extended file
585    names.  The TCPIP FTP server (V5.4) seems to prefer requests which do
586    not use the escaped names which it provides.
587 */
588
589 #define VMS_DEFAULT_PROT_FILE 0644
590 #define VMS_DEFAULT_PROT_DIR 0755
591
592 /* 2005-02-23 SMS.
593    eat_carets().
594
595    Delete ODS5 extended file name escape characters ("^") in the
596    original buffer.
597    Note that the current scheme does not handle all EFN cases, but it
598    could be made more complicated.
599 */
600
601 static void eat_carets( char *str)
602 /* char *str;      Source pointer. */
603 {
604   char *strd;   /* Destination pointer. */
605   char hdgt;
606   unsigned char uchr;
607   unsigned char prop;
608
609   /* Skip ahead to the first "^", if any. */
610   while ((*str != '\0') && (*str != '^'))
611      str++;
612
613   /* If no caret was found, quit early. */
614   if (*str != '\0')
615   {
616     /* Shift characters leftward as carets are found. */
617     strd = str;
618     while (*str != '\0')
619     {
620       uchr = *str;
621       if (uchr == '^')
622       {
623         /* Found a caret.  Skip it, and check the next character. */
624         uchr = *(++str);
625         prop = char_prop[ uchr];
626         if (prop& 64)
627         {
628           /* Hex digit.  Get char code from this and next hex digit. */
629           if (uchr <= '9')
630           {
631             hdgt = uchr- '0';           /* '0' - '9' -> 0 - 9. */
632           }
633           else
634           {
635             hdgt = ((uchr- 'A')& 7)+ 10;    /* [Aa] - [Ff] -> 10 - 15. */
636           }
637           hdgt <<= 4;                   /* X16. */
638           uchr = *(++str);              /* Next char must be hex digit. */
639           if (uchr <= '9')
640           {
641             uchr = hdgt+ uchr- '0';
642           }
643           else
644           {
645             uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
646           }
647         }
648         else if (uchr == '_')
649         {
650           /* Convert escaped "_" to " ". */
651           uchr = ' ';
652         }
653         else if (uchr == '/')
654         {
655           /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
656           /* Note that this is a left-over from Info-ZIP code, and is
657              probably of little value here, except perhaps to avoid
658              directory confusion which an unconverted slash might cause.
659           */
660           uchr = '?';
661         }
662         /* Else, not a hex digit.  Must be a simple escaped character
663            (or Unicode, which is not yet handled here).
664         */
665       }
666       /* Else, not a caret.  Use as-is. */
667       *strd = uchr;
668
669       /* Advance destination and source pointers. */
670       strd++;
671       str++;
672     }
673     /* Terminate the destination string. */
674     *strd = '\0';
675   }
676 }
677
678
679 static struct fileinfo *
680 ftp_parse_vms_ls (const char *file)
681 {
682   FILE *fp;
683   int dt, i, j, len;
684   int perms;
685   size_t bufsize = 0;
686   time_t timenow;
687   struct tm *timestruct;
688   char date_str[ 32];
689
690   char *line = NULL, *tok; /* tokenizer */
691   struct fileinfo *dir, *l, cur; /* list creation */
692
693   fp = fopen (file, "r");
694   if (!fp)
695     {
696       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
697       return NULL;
698     }
699   dir = l = NULL;
700
701   /* Skip blank lines, Directory heading, and more blank lines. */
702
703   for (j = 0; (i = getline (&line, &bufsize, fp)) > 0; )
704     {
705       i = clean_line (line, i);
706       if (i <= 0)
707         continue; /* Ignore blank line. */
708
709       if ((j == 0) && (line[i - 1] == ']'))
710         {
711           /* Found Directory heading line.  Next non-blank line
712           is significant. */
713           j = 1;
714         }
715       else if (!strncmp (line, "Total of ", 9))
716         {
717           /* Found "Total of ..." footing line.  No valid data
718              will follow (empty directory). */
719           i = 0; /* Arrange for early exit. */
720           break;
721         }
722       else
723         {
724           break; /* Must be significant data. */
725         }
726     }
727
728   /* Read remainder of file until the next blank line or EOF. */
729
730   while (i > 0)
731     {
732       char *p;
733
734       /* The first token is the file name.  After a long name, other
735          data may be on the following line.  A valid directory name ends
736          in ".DIR;1" (any case), although some VMS FTP servers may omit
737          the version number (";1").
738       */
739
740       tok = strtok(line, " ");
741       if (tok == NULL) tok = line;
742       DEBUGP (("file name:   '%s'\n", tok));
743
744       /* Stripping the version number on a VMS system would be wrong.
745          It may be foolish on a non-VMS system, too, but that's someone
746          else's problem.  (Define PRESERVE_VMS_VERSIONS for proper
747          operation on other operating systems.)
748
749          2005-02-23 SMS.
750          ODS5 extended file names may contain escaped semi-colons, so
751          the version number is identified as right-side decimal digits
752          led by a non-escaped semi-colon.  It may be absent.
753       */
754
755 #if (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS))
756       for (p = tok + strlen (tok); (--p > tok) && c_isdigit( *p); );
757       if ((*p == ';') && (*(p- 1) != '^'))
758         {
759           *p = '\0';
760         }
761 #endif /* (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS)) */
762
763       /* 2005-02-23 SMS.
764          Eliminate "^" escape characters from ODS5 extended file name.
765          (A caret is invalid in an ODS2 name, so this is always safe.)
766       */
767       eat_carets (tok);
768       DEBUGP (("file name-^: '%s'\n", tok));
769
770       /* Differentiate between a directory and any other file.  A VMS
771          listing may not include file protections (permissions).  Set a
772          default permissions value (according to the file type), which
773          may be overwritten later.  Store directory names without the
774          ".DIR;1" file type and version number, as the plain name is
775          what will work in a CWD command.
776       */
777       len = strlen (tok);
778       if (!strncasecmp((tok + (len - 4)), ".DIR", 4))
779         {
780           *(tok+ (len - 4)) = '\0'; /* Discard ".DIR". */
781           cur.type  = FT_DIRECTORY;
782           cur.perms = VMS_DEFAULT_PROT_DIR;
783           DEBUGP (("Directory (nv)\n"));
784         }
785       else if (!strncasecmp ((tok + (len - 6)), ".DIR;1", 6))
786         {
787           *(tok+ (len - 6)) = '\0'; /* Discard ".DIR;1". */
788           cur.type  = FT_DIRECTORY;
789           cur.perms = VMS_DEFAULT_PROT_DIR;
790           DEBUGP (("Directory (v)\n"));
791         }
792       else
793         {
794           cur.type  = FT_PLAINFILE;
795           cur.perms = VMS_DEFAULT_PROT_FILE;
796           DEBUGP (("File\n"));
797         }
798       cur.name = xstrdup (tok);
799       DEBUGP (("Name: '%s'\n", cur.name));
800
801       /* Null the date and time string. */
802       *date_str = '\0';
803
804       /* VMS lacks symbolic links. */
805       cur.linkto = NULL;
806
807       /* VMS reports file sizes in (512-byte) disk blocks, not bytes,
808          hence useless for an integrity check based on byte-count.
809          Set size to unknown.
810       */
811       cur.size = 0;
812
813       /* Get token 2, if any.  A long name may force all other data onto
814          a second line.  If needed, read the second line.
815       */
816
817       tok = strtok (NULL, " ");
818       if (tok == NULL)
819         {
820           DEBUGP (("Getting additional line.\n"));
821           i = getline (&line, &bufsize, fp);
822           if (i <= 0)
823             {
824               DEBUGP (("EOF.  Leaving listing parser.\n"));
825               break;
826             }
827
828           /* Second line must begin with " ".  Otherwise, it's a first
829              line (and we may be confused).
830           */
831           i = clean_line (line, i);
832           if (i <= 0)
833             {
834               /* Blank line.  End of significant file listing. */
835               DEBUGP (("Blank line.  Leaving listing parser.\n"));
836               break;
837             }
838           else if (line[0] != ' ')
839             {
840               DEBUGP (("Non-blank in column 1.  Must be a new file name?\n"));
841               continue;
842             }
843           else
844             {
845               tok = strtok (line, " ");
846               if (tok == NULL)
847                 {
848                   /* Unexpected non-empty but apparently blank line. */
849                   DEBUGP (("Null token.  Leaving listing parser.\n"));
850                   break;
851                 }
852             }
853         }
854
855       /* Analyze tokens.  (Order is not significant, except date must
856          precede time.)
857
858          Size:       ddd or ddd/ddd (where "ddd" is a decimal number)
859          Date:       DD-MMM-YYYY
860          Time:       HH:MM or HH:MM:SS or HH:MM:SS.CC
861          Owner:      [user] or [user,group]
862          Protection: (ppp,ppp,ppp,ppp) (where "ppp" is "RWED" or some
863          subset thereof, for System, Owner, Group, World.
864
865          If permission is lacking, info may be replaced by the string:
866          "No privilege for attempted operation".
867       */
868       while (tok != NULL)
869         {
870           DEBUGP (("Token: >%s<: ", tok));
871
872           if ((strlen (tok) < 12) && (strchr( tok, '-') != NULL))
873             {
874               /* Date. */
875               DEBUGP (("Date.\n"));
876               strcpy( date_str, tok);
877               strcat( date_str, " ");
878             }
879           else if ((strlen (tok) < 12) && (strchr( tok, ':') != NULL))
880             {
881               /* Time. */
882               DEBUGP (("Time. "));
883               strncat( date_str,
884                        tok,
885                        (sizeof( date_str)- strlen (date_str) - 1));
886               DEBUGP (("Date time: >%s<\n", date_str));
887             }
888           else if (strchr (tok, '[') != NULL)
889             {
890               /* Owner.  (Ignore.) */
891               DEBUGP (("Owner.\n"));
892             }
893           else if (strchr (tok, '(') != NULL)
894             {
895               /* Protections (permissions). */
896               perms = 0;
897               j = 0;
898               /*FIXME: Should not be using the variable like this. */
899               for (i = 0; i < (int) strlen(tok); i++)
900                 {
901                   switch (tok[ i])
902                     {
903                     case '(':
904                       break;
905                     case ')':
906                       break;
907                     case ',':
908                       if (j == 0)
909                         {
910                           perms = 0;
911                           j = 1;
912                         }
913                       else
914                         {
915                           perms <<= 3;
916                         }
917                       break;
918                     case 'R':
919                       perms |= 4;
920                       break;
921                     case 'W':
922                       perms |= 2;
923                       break;
924                     case 'E':
925                       perms |= 1;
926                       break;
927                     case 'D':
928                       perms |= 2;
929                       break;
930                     }
931                 }
932               cur.perms = perms;
933               DEBUGP (("Prot.  perms = %0o.\n", cur.perms));
934             }
935           else
936             {
937               /* Nondescript.  Probably size(s), probably in blocks.
938                  Could be "No privilege ..." message.  (Ignore.)
939               */
940               DEBUGP (("Ignored (size?).\n"));
941             }
942
943           tok = strtok (NULL, " ");
944         }
945
946       /* Tokens exhausted.  Interpret the data, and fill in the
947          structure.
948       */
949       /* Fill tm timestruct according to date-time string.  Fractional
950          seconds are ignored.  Default to current time, if conversion
951          fails.
952       */
953       timenow = time( NULL);
954       timestruct = localtime( &timenow );
955       strptime( date_str, "%d-%b-%Y %H:%M:%S", timestruct);
956
957       /* Convert struct tm local time to time_t local time. */
958       timenow = mktime (timestruct);
959       /* Offset local time according to environment variable (seconds). */
960       if ((tok = getenv ( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
961         {
962           dt = atoi (tok);
963           DEBUGP (("Time differential = %d.\n", dt));
964         }
965       else
966         dt = 0;
967
968       if (dt >= 0)
969         timenow += dt;
970       else
971         timenow -= (-dt);
972
973       cur.tstamp = timenow; /* Store the time-stamp. */
974       DEBUGP (("Timestamp: %ld\n", cur.tstamp));
975       cur.ptype = TT_HOUR_MIN;
976
977       /* Add the data for this item to the linked list, */
978       if (!dir)
979         {
980           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
981           memcpy (l, &cur, sizeof (cur));
982           l->prev = l->next = NULL;
983         }
984       else
985         {
986           cur.prev = l;
987           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
988           l = l->next;
989           memcpy (l, &cur, sizeof (cur));
990           l->next = NULL;
991         }
992
993       i = getline (&line, &bufsize, fp);
994       if (i > 0)
995         {
996           i = clean_line (line, i);
997           if (i <= 0)
998             {
999               /* Blank line.  End of significant file listing. */
1000               break;
1001             }
1002         }
1003     }
1004
1005   xfree (line);
1006   fclose (fp);
1007   return dir;
1008 }
1009
1010
1011 /* This function switches between the correct parsing routine depending on
1012    the SYSTEM_TYPE. The system type should be based on the result of the
1013    "SYST" response of the FTP server. According to this repsonse we will
1014    use on of the three different listing parsers that cover the most of FTP
1015    servers used nowadays.  */
1016
1017 struct fileinfo *
1018 ftp_parse_ls (const char *file, const enum stype system_type)
1019 {
1020   switch (system_type)
1021     {
1022     case ST_UNIX:
1023       return ftp_parse_unix_ls (file, 0);
1024     case ST_WINNT:
1025       {
1026         /* Detect whether the listing is simulating the UNIX format */
1027         FILE *fp;
1028         int   c;
1029         fp = fopen (file, "rb");
1030         if (!fp)
1031         {
1032           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1033           return NULL;
1034         }
1035         c = fgetc(fp);
1036         fclose(fp);
1037         /* If the first character of the file is '0'-'9', it's WINNT
1038            format. */
1039         if (c >= '0' && c <='9')
1040           return ftp_parse_winnt_ls (file);
1041         else
1042           return ftp_parse_unix_ls (file, 1);
1043       }
1044     case ST_VMS:
1045       return ftp_parse_vms_ls (file);
1046     case ST_MACOS:
1047       return ftp_parse_unix_ls (file, 1);
1048     default:
1049       logprintf (LOG_NOTQUIET, _("\
1050 Unsupported listing type, trying Unix listing parser.\n"));
1051       return ftp_parse_unix_ls (file, 0);
1052     }
1053 }
1054 \f
1055 /* Stuff for creating FTP index. */
1056
1057 /* The function creates an HTML index containing references to given
1058    directories and files on the appropriate host.  The references are
1059    FTP.  */
1060 uerr_t
1061 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1062 {
1063   FILE *fp;
1064   char *upwd;
1065   char *htcldir;                /* HTML-clean dir name */
1066   char *htclfile;               /* HTML-clean file name */
1067   char *urlclfile;              /* URL-clean file name */
1068
1069   if (!output_stream)
1070     {
1071       fp = fopen (file, "wb");
1072       if (!fp)
1073         {
1074           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1075           return FOPENERR;
1076         }
1077     }
1078   else
1079     fp = output_stream;
1080   if (u->user)
1081     {
1082       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
1083
1084       tmpu = url_escape (u->user);
1085       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1086       if (tmpp)
1087         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1088       else
1089         upwd = concat_strings (tmpu, "@", (char *) 0);
1090       xfree (tmpu);
1091       xfree_null (tmpp);
1092     }
1093   else
1094     upwd = xstrdup ("");
1095
1096   htcldir = html_quote_string (u->dir);
1097
1098   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1099   fprintf (fp, "<html>\n<head>\n<title>");
1100   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1101   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1102   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1103   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1104
1105   while (f)
1106     {
1107       fprintf (fp, "  ");
1108       if (f->tstamp != -1)
1109         {
1110           /* #### Should we translate the months?  Or, even better, use
1111              ISO 8601 dates?  */
1112           static const char *months[] = {
1113             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1114             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1115           };
1116           time_t tstamp = f->tstamp;
1117           struct tm *ptm = localtime (&tstamp);
1118
1119           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1120                   ptm->tm_mday);
1121           if (f->ptype == TT_HOUR_MIN)
1122             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
1123           else
1124             fprintf (fp, "       ");
1125         }
1126       else
1127         fprintf (fp, _("time unknown       "));
1128       switch (f->type)
1129         {
1130         case FT_PLAINFILE:
1131           fprintf (fp, _("File        "));
1132           break;
1133         case FT_DIRECTORY:
1134           fprintf (fp, _("Directory   "));
1135           break;
1136         case FT_SYMLINK:
1137           fprintf (fp, _("Link        "));
1138           break;
1139         default:
1140           fprintf (fp, _("Not sure    "));
1141           break;
1142         }
1143       htclfile = html_quote_string (f->name);
1144       urlclfile = url_escape_unsafe_and_reserved (f->name);
1145       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1146       if (*u->dir != '/')
1147         putc ('/', fp);
1148       /* XXX: Should probably URL-escape dir components here, rather
1149        * than just HTML-escape, for consistency with the next bit where
1150        * we use urlclfile for the file component. Anyway, this is safer
1151        * than what we had... */
1152       fprintf (fp, "%s", htcldir);
1153       if (*u->dir)
1154         putc ('/', fp);
1155       fprintf (fp, "%s", urlclfile);
1156       if (f->type == FT_DIRECTORY)
1157         putc ('/', fp);
1158       fprintf (fp, "\">%s", htclfile);
1159       if (f->type == FT_DIRECTORY)
1160         putc ('/', fp);
1161       fprintf (fp, "</a> ");
1162       if (f->type == FT_PLAINFILE)
1163         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1164       else if (f->type == FT_SYMLINK)
1165         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1166       putc ('\n', fp);
1167       xfree (htclfile);
1168       xfree (urlclfile);
1169       f = f->next;
1170     }
1171   fprintf (fp, "</pre>\n</body>\n</html>\n");
1172   xfree (htcldir);
1173   xfree (upwd);
1174   if (!output_stream)
1175     fclose (fp);
1176   else
1177     fflush (fp);
1178   return FTPOK;
1179 }