]> sjero.net Git - wget/blob - src/ftp-ls.c
1039a43d88d144642ab94cd455fcbd1c48dc8293
[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               for (i = 0; i < strlen(tok); i++)
899                 {
900                   switch (tok[ i])
901                     {
902                     case '(':
903                       break;
904                     case ')':
905                       break;
906                     case ',':
907                       if (j == 0)
908                         {
909                           perms = 0;
910                           j = 1;
911                         }
912                       else
913                         {
914                           perms <<= 3;
915                         }
916                       break;
917                     case 'R':
918                       perms |= 4;
919                       break;
920                     case 'W':
921                       perms |= 2;
922                       break;
923                     case 'E':
924                       perms |= 1;
925                       break;
926                     case 'D':
927                       perms |= 2;
928                       break;
929                     }
930                 }
931               cur.perms = perms;
932               DEBUGP (("Prot.  perms = %0o.\n", cur.perms));
933             }
934           else
935             {
936               /* Nondescript.  Probably size(s), probably in blocks.
937                  Could be "No privilege ..." message.  (Ignore.)
938               */
939               DEBUGP (("Ignored (size?).\n"));
940             }
941
942           tok = strtok (NULL, " ");
943         }
944
945       /* Tokens exhausted.  Interpret the data, and fill in the
946          structure.
947       */
948       /* Fill tm timestruct according to date-time string.  Fractional
949          seconds are ignored.  Default to current time, if conversion
950          fails.
951       */
952       timenow = time( NULL);
953       timestruct = localtime( &timenow );
954       strptime( date_str, "%d-%b-%Y %H:%M:%S", timestruct);
955
956       /* Convert struct tm local time to time_t local time. */
957       timenow = mktime (timestruct);
958       /* Offset local time according to environment variable (seconds). */
959       if ((tok = getenv ( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
960         {
961           dt = atoi (tok);
962           DEBUGP (("Time differential = %d.\n", dt));
963         }
964       else
965         dt = 0;
966
967       if (dt >= 0)
968         timenow += dt;
969       else
970         timenow -= (-dt);
971
972       cur.tstamp = timenow; /* Store the time-stamp. */
973       DEBUGP (("Timestamp: %ld\n", cur.tstamp));
974       cur.ptype = TT_HOUR_MIN;
975
976       /* Add the data for this item to the linked list, */
977       if (!dir)
978         {
979           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
980           memcpy (l, &cur, sizeof (cur));
981           l->prev = l->next = NULL;
982         }
983       else
984         {
985           cur.prev = l;
986           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
987           l = l->next;
988           memcpy (l, &cur, sizeof (cur));
989           l->next = NULL;
990         }
991
992       i = getline (&line, &bufsize, fp);
993       if (i > 0)
994         {
995           i = clean_line (line, i);
996           if (i <= 0)
997             {
998               /* Blank line.  End of significant file listing. */
999               break;
1000             }
1001         }
1002     }
1003
1004   xfree (line);
1005   fclose (fp);
1006   return dir;
1007 }
1008
1009
1010 /* This function switches between the correct parsing routine depending on
1011    the SYSTEM_TYPE. The system type should be based on the result of the
1012    "SYST" response of the FTP server. According to this repsonse we will
1013    use on of the three different listing parsers that cover the most of FTP
1014    servers used nowadays.  */
1015
1016 struct fileinfo *
1017 ftp_parse_ls (const char *file, const enum stype system_type)
1018 {
1019   switch (system_type)
1020     {
1021     case ST_UNIX:
1022       return ftp_parse_unix_ls (file, 0);
1023     case ST_WINNT:
1024       {
1025         /* Detect whether the listing is simulating the UNIX format */
1026         FILE *fp;
1027         int   c;
1028         fp = fopen (file, "rb");
1029         if (!fp)
1030         {
1031           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1032           return NULL;
1033         }
1034         c = fgetc(fp);
1035         fclose(fp);
1036         /* If the first character of the file is '0'-'9', it's WINNT
1037            format. */
1038         if (c >= '0' && c <='9')
1039           return ftp_parse_winnt_ls (file);
1040         else
1041           return ftp_parse_unix_ls (file, 1);
1042       }
1043     case ST_VMS:
1044       return ftp_parse_vms_ls (file);
1045     case ST_MACOS:
1046       return ftp_parse_unix_ls (file, 1);
1047     default:
1048       logprintf (LOG_NOTQUIET, _("\
1049 Unsupported listing type, trying Unix listing parser.\n"));
1050       return ftp_parse_unix_ls (file, 0);
1051     }
1052 }
1053 \f
1054 /* Stuff for creating FTP index. */
1055
1056 /* The function creates an HTML index containing references to given
1057    directories and files on the appropriate host.  The references are
1058    FTP.  */
1059 uerr_t
1060 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1061 {
1062   FILE *fp;
1063   char *upwd;
1064   char *htcldir;                /* HTML-clean dir name */
1065   char *htclfile;               /* HTML-clean file name */
1066   char *urlclfile;              /* URL-clean file name */
1067
1068   if (!output_stream)
1069     {
1070       fp = fopen (file, "wb");
1071       if (!fp)
1072         {
1073           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1074           return FOPENERR;
1075         }
1076     }
1077   else
1078     fp = output_stream;
1079   if (u->user)
1080     {
1081       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
1082
1083       tmpu = url_escape (u->user);
1084       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1085       if (tmpp)
1086         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1087       else
1088         upwd = concat_strings (tmpu, "@", (char *) 0);
1089       xfree (tmpu);
1090       xfree_null (tmpp);
1091     }
1092   else
1093     upwd = xstrdup ("");
1094
1095   htcldir = html_quote_string (u->dir);
1096
1097   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1098   fprintf (fp, "<html>\n<head>\n<title>");
1099   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1100   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1101   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1102   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1103
1104   while (f)
1105     {
1106       fprintf (fp, "  ");
1107       if (f->tstamp != -1)
1108         {
1109           /* #### Should we translate the months?  Or, even better, use
1110              ISO 8601 dates?  */
1111           static const char *months[] = {
1112             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1113             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1114           };
1115           time_t tstamp = f->tstamp;
1116           struct tm *ptm = localtime (&tstamp);
1117
1118           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1119                   ptm->tm_mday);
1120           if (f->ptype == TT_HOUR_MIN)
1121             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
1122           else
1123             fprintf (fp, "       ");
1124         }
1125       else
1126         fprintf (fp, _("time unknown       "));
1127       switch (f->type)
1128         {
1129         case FT_PLAINFILE:
1130           fprintf (fp, _("File        "));
1131           break;
1132         case FT_DIRECTORY:
1133           fprintf (fp, _("Directory   "));
1134           break;
1135         case FT_SYMLINK:
1136           fprintf (fp, _("Link        "));
1137           break;
1138         default:
1139           fprintf (fp, _("Not sure    "));
1140           break;
1141         }
1142       htclfile = html_quote_string (f->name);
1143       urlclfile = url_escape_unsafe_and_reserved (f->name);
1144       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1145       if (*u->dir != '/')
1146         putc ('/', fp);
1147       /* XXX: Should probably URL-escape dir components here, rather
1148        * than just HTML-escape, for consistency with the next bit where
1149        * we use urlclfile for the file component. Anyway, this is safer
1150        * than what we had... */
1151       fprintf (fp, "%s", htcldir);
1152       if (*u->dir)
1153         putc ('/', fp);
1154       fprintf (fp, "%s", urlclfile);
1155       if (f->type == FT_DIRECTORY)
1156         putc ('/', fp);
1157       fprintf (fp, "\">%s", htclfile);
1158       if (f->type == FT_DIRECTORY)
1159         putc ('/', fp);
1160       fprintf (fp, "</a> ");
1161       if (f->type == FT_PLAINFILE)
1162         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1163       else if (f->type == FT_SYMLINK)
1164         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1165       putc ('\n', fp);
1166       xfree (htclfile);
1167       xfree (urlclfile);
1168       f = f->next;
1169     }
1170   fprintf (fp, "</pre>\n</body>\n</html>\n");
1171   xfree (htcldir);
1172   xfree (upwd);
1173   if (!output_stream)
1174     fclose (fp);
1175   else
1176     fflush (fp);
1177   return FTPOK;
1178 }