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