]> sjero.net Git - wget/blob - src/ftp-ls.c
3056651bf2ebcbce48e6254099835606fd76fc74
[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   char *filename;
438   struct fileinfo *dir, *l, cur; /* list creation */
439
440   fp = fopen (file, "rb");
441   if (!fp)
442     {
443       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
444       return NULL;
445     }
446   dir = l = NULL;
447
448   /* Line loop to end of file: */
449   while ((line = read_whole_line (fp)) != NULL)
450     {
451       len = clean_line (line);
452
453       /* Name begins at 39 column of the listing if date presented in `mm-dd-yy'
454          format or at 41 column if date presented in `mm-dd-yyyy' format. Thus,
455          we cannot extract name before we parse date. Using this information we
456          also can recognize filenames that begin with a series of space
457          characters (but who really wants to use such filenames anyway?). */
458       if (len < 40) goto continue_loop;
459       filename = line + 39;
460
461       /* First column: mm-dd-yy or mm-dd-yyyy. Should atoi() on the month fail,
462          january will be assumed.  */
463       tok = strtok(line, "-");
464       if (tok == NULL) goto continue_loop;
465       month = atoi(tok) - 1;
466       if (month < 0) month = 0;
467       tok = strtok(NULL, "-");
468       if (tok == NULL) goto continue_loop;
469       day = atoi(tok);
470       tok = strtok(NULL, " ");
471       if (tok == NULL) goto continue_loop;
472       year = atoi(tok);
473       /* Assuming the epoch starting at 1.1.1970 */
474       if (year <= 70)
475         {
476           year += 100;
477         }
478       else if (year >= 1900)
479         {
480           year -= 1900;
481           filename += 2;
482         }
483       /* Now it is possible to determine the position of the first symbol in
484          filename. */
485       cur.name = xstrdup(filename);
486       DEBUGP (("Name: '%s'\n", cur.name));
487
488
489       /* Second column: hh:mm[AP]M, listing does not contain value for
490          seconds */
491       tok = strtok(NULL,  ":");
492       if (tok == NULL) goto continue_loop;
493       hour = atoi(tok);
494       tok = strtok(NULL,  "M");
495       if (tok == NULL) goto continue_loop;
496       min = atoi(tok);
497       /* Adjust hour from AM/PM. Just for the record, the sequence goes
498          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
499       tok+=2;
500       if (hour == 12)  hour  = 0;
501       if (*tok == 'P') hour += 12;
502
503       DEBUGP (("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n",
504               year+1900, month, day, hour, min));
505
506       /* Build the time-stamp (copy & paste from above) */
507       timestruct.tm_sec   = 0;
508       timestruct.tm_min   = min;
509       timestruct.tm_hour  = hour;
510       timestruct.tm_mday  = day;
511       timestruct.tm_mon   = month;
512       timestruct.tm_year  = year;
513       timestruct.tm_wday  = 0;
514       timestruct.tm_yday  = 0;
515       timestruct.tm_isdst = -1;
516       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
517       cur.ptype = TT_HOUR_MIN;
518
519       DEBUGP (("Timestamp: %ld\n", cur.tstamp));
520
521       /* Third column: Either file length, or <DIR>. We also set the
522          permissions (guessed as 0644 for plain files and 0755 for
523          directories as the listing does not give us a clue) and filetype
524          here. */
525       tok = strtok(NULL, " ");
526       if (tok == NULL) goto continue_loop;
527       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
528       if (tok == NULL) goto continue_loop;
529       if (*tok == '<')
530         {
531           cur.type  = FT_DIRECTORY;
532           cur.size  = 0;
533           cur.perms = 0755;
534           DEBUGP (("Directory\n"));
535         }
536       else
537         {
538           wgint size;
539           cur.type  = FT_PLAINFILE;
540           errno = 0;
541           size = str_to_wgint (tok, NULL, 10);
542           if (size == WGINT_MAX && errno == ERANGE)
543             cur.size = 0;       /* overflow */
544           else
545             cur.size = size;
546           cur.perms = 0644;
547           DEBUGP (("File, size %s bytes\n", number_to_static_string (cur.size)));
548         }
549
550       cur.linkto = NULL;
551
552       /* And put everything into the linked list */
553       if (!dir)
554         {
555           l = dir = xnew (struct fileinfo);
556           memcpy (l, &cur, sizeof (cur));
557           l->prev = l->next = NULL;
558         }
559       else
560         {
561           cur.prev = l;
562           l->next = xnew (struct fileinfo);
563           l = l->next;
564           memcpy (l, &cur, sizeof (cur));
565           l->next = NULL;
566         }
567
568 continue_loop:
569       xfree (line);
570     }
571
572   fclose(fp);
573   return dir;
574 }
575
576
577
578 /* Convert the VMS-style directory listing stored in "file" to a
579    linked list of fileinfo (system-independent) entries.  The contents
580    of FILE are considered to be produced by the standard VMS
581    "DIRECTORY [/SIZE [= ALL]] /DATE [/OWNER] [/PROTECTION]" command,
582    more or less.  (Different VMS FTP servers may have different headers,
583    and may not supply the same data, but all should be subsets of this.)
584
585    VMS normally provides local (server) time and date information.
586    Define the logical name or environment variable
587    "WGET_TIMEZONE_DIFFERENTIAL" (seconds) to adjust the receiving local
588    times if different from the remote local times.
589
590    2005-02-23 SMS.
591    Added code to eliminate "^" escape characters from ODS5 extended file
592    names.  The TCPIP FTP server (V5.4) seems to prefer requests which do
593    not use the escaped names which it provides.
594 */
595
596 #define VMS_DEFAULT_PROT_FILE 0644
597 #define VMS_DEFAULT_PROT_DIR 0755
598
599 /* 2005-02-23 SMS.
600    eat_carets().
601
602    Delete ODS5 extended file name escape characters ("^") in the
603    original buffer.
604    Note that the current scheme does not handle all EFN cases, but it
605    could be made more complicated.
606 */
607
608 static void eat_carets( char *str)
609 /* char *str;      Source pointer. */
610 {
611   char *strd;   /* Destination pointer. */
612   char hdgt;
613   unsigned char uchr;
614   unsigned char prop;
615
616   /* Skip ahead to the first "^", if any. */
617   while ((*str != '\0') && (*str != '^'))
618      str++;
619
620   /* If no caret was found, quit early. */
621   if (*str != '\0')
622   {
623     /* Shift characters leftward as carets are found. */
624     strd = str;
625     while (*str != '\0')
626     {
627       uchr = *str;
628       if (uchr == '^')
629       {
630         /* Found a caret.  Skip it, and check the next character. */
631         uchr = *(++str);
632         prop = char_prop[ uchr];
633         if (prop& 64)
634         {
635           /* Hex digit.  Get char code from this and next hex digit. */
636           if (uchr <= '9')
637           {
638             hdgt = uchr- '0';           /* '0' - '9' -> 0 - 9. */
639           }
640           else
641           {
642             hdgt = ((uchr- 'A')& 7)+ 10;    /* [Aa] - [Ff] -> 10 - 15. */
643           }
644           hdgt <<= 4;                   /* X16. */
645           uchr = *(++str);              /* Next char must be hex digit. */
646           if (uchr <= '9')
647           {
648             uchr = hdgt+ uchr- '0';
649           }
650           else
651           {
652             uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
653           }
654         }
655         else if (uchr == '_')
656         {
657           /* Convert escaped "_" to " ". */
658           uchr = ' ';
659         }
660         else if (uchr == '/')
661         {
662           /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
663           /* Note that this is a left-over from Info-ZIP code, and is
664              probably of little value here, except perhaps to avoid
665              directory confusion which an unconverted slash might cause.
666           */
667           uchr = '?';
668         }
669         /* Else, not a hex digit.  Must be a simple escaped character
670            (or Unicode, which is not yet handled here).
671         */
672       }
673       /* Else, not a caret.  Use as-is. */
674       *strd = uchr;
675
676       /* Advance destination and source pointers. */
677       strd++;
678       str++;
679     }
680     /* Terminate the destination string. */
681     *strd = '\0';
682   }
683 }
684
685
686 static struct fileinfo *
687 ftp_parse_vms_ls (const char *file)
688 {
689   FILE *fp;
690   int dt, i, j, len;
691   int perms;
692   time_t timenow;
693   struct tm *timestruct;
694   char date_str[ 32];
695
696   char *line, *tok;              /* tokenizer */
697   struct fileinfo *dir, *l, cur; /* list creation */
698
699   fp = fopen (file, "r");
700   if (!fp)
701     {
702       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
703       return NULL;
704     }
705   dir = l = NULL;
706
707   /* Skip blank lines, Directory heading, and more blank lines. */
708
709   j = 0; /* Expecting initial blank line(s). */
710   while (1)
711     {
712       line = read_whole_line (fp);
713       if (line == NULL)
714         {
715         break;
716         }
717       else
718         {
719           i = clean_line (line);
720           if (i <= 0)
721             {
722               xfree (line); /* Free useless line storage. */
723               continue; /* Blank line.  Keep looking. */
724             }
725           else
726             {
727               if ((j == 0) && (line[ i- 1] == ']'))
728                 {
729                   /* Found Directory heading line.  Next non-blank line
730                   is significant.
731                   */
732                   j = 1;
733                 }
734               else if (!strncmp (line, "Total of ", 9))
735                 {
736                   /* Found "Total of ..." footing line.  No valid data
737                      will follow (empty directory).
738                   */
739                   xfree (line); /* Free useless line storage. */
740                   line = NULL; /* Arrange for early exit. */
741                   break;
742                 }
743               else
744                 {
745                   break; /* Must be significant data. */
746                 }
747             }
748           xfree (line); /* Free useless line storage. */
749         }
750     }
751
752   /* Read remainder of file until the next blank line or EOF. */
753
754   while (line != NULL)
755     {
756       char *p;
757
758       /* The first token is the file name.  After a long name, other
759          data may be on the following line.  A valid directory name ends
760          in ".DIR;1" (any case), although some VMS FTP servers may omit
761          the version number (";1").
762       */
763
764       tok = strtok(line, " ");
765       if (tok == NULL) tok = line;
766       DEBUGP (("file name:   '%s'\n", tok));
767
768       /* Stripping the version number on a VMS system would be wrong.
769          It may be foolish on a non-VMS system, too, but that's someone
770          else's problem.  (Define PRESERVE_VMS_VERSIONS for proper
771          operation on other operating systems.)
772
773          2005-02-23 SMS.
774          ODS5 extended file names may contain escaped semi-colons, so
775          the version number is identified as right-side decimal digits
776          led by a non-escaped semi-colon.  It may be absent.
777       */
778
779 #if (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS))
780       for (p = tok + strlen (tok); (--p > tok) && c_isdigit( *p); );
781       if ((*p == ';') && (*(p- 1) != '^'))
782         {
783           *p = '\0';
784         }
785 #endif /* (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS)) */
786
787       /* 2005-02-23 SMS.
788          Eliminate "^" escape characters from ODS5 extended file name.
789          (A caret is invalid in an ODS2 name, so this is always safe.)
790       */
791       eat_carets (tok);
792       DEBUGP (("file name-^: '%s'\n", tok));
793
794       /* Differentiate between a directory and any other file.  A VMS
795          listing may not include file protections (permissions).  Set a
796          default permissions value (according to the file type), which
797          may be overwritten later.  Store directory names without the
798          ".DIR;1" file type and version number, as the plain name is
799          what will work in a CWD command.
800       */
801       len = strlen (tok);
802       if (!strncasecmp((tok + (len - 4)), ".DIR", 4))
803         {
804           *(tok+ (len - 4)) = '\0'; /* Discard ".DIR". */
805           cur.type  = FT_DIRECTORY;
806           cur.perms = VMS_DEFAULT_PROT_DIR;
807           DEBUGP (("Directory (nv)\n"));
808         }
809       else if (!strncasecmp ((tok + (len - 6)), ".DIR;1", 6))
810         {
811           *(tok+ (len - 6)) = '\0'; /* Discard ".DIR;1". */
812           cur.type  = FT_DIRECTORY;
813           cur.perms = VMS_DEFAULT_PROT_DIR;
814           DEBUGP (("Directory (v)\n"));
815         }
816       else
817         {
818           cur.type  = FT_PLAINFILE;
819           cur.perms = VMS_DEFAULT_PROT_FILE;
820           DEBUGP (("File\n"));
821         }
822       cur.name = xstrdup (tok);
823       DEBUGP (("Name: '%s'\n", cur.name));
824
825       /* Null the date and time string. */
826       *date_str = '\0';
827
828       /* VMS lacks symbolic links. */
829       cur.linkto = NULL;
830
831       /* VMS reports file sizes in (512-byte) disk blocks, not bytes,
832          hence useless for an integrity check based on byte-count.
833          Set size to unknown.
834       */
835       cur.size = 0;
836
837       /* Get token 2, if any.  A long name may force all other data onto
838          a second line.  If needed, read the second line.
839       */
840
841       tok = strtok (NULL, " ");
842       if (tok == NULL)
843         {
844           DEBUGP (("Getting additional line.\n"));
845           xfree (line);
846           line = read_whole_line (fp);
847           if (!line)
848             {
849               DEBUGP (("EOF.  Leaving listing parser.\n"));
850               break;
851             }
852
853           /* Second line must begin with " ".  Otherwise, it's a first
854              line (and we may be confused).
855           */
856           if (i <= 0)
857             {
858               /* Blank line.  End of significant file listing. */
859               DEBUGP (("Blank line.  Leaving listing parser.\n"));
860               xfree (line); /* Free useless line storage. */
861               break;
862             }
863           else if (line[ 0] != ' ')
864             {
865               DEBUGP (("Non-blank in column 1.  Must be a new file name?\n"));
866               continue;
867             }
868           else
869             {
870               tok = strtok (line, " ");
871               if (tok == NULL)
872                 {
873                   /* Unexpected non-empty but apparently blank line. */
874                   DEBUGP (("Null token.  Leaving listing parser.\n"));
875                   xfree (line); /* Free useless line storage. */
876                   break;
877                 }
878             }
879         }
880
881       /* Analyze tokens.  (Order is not significant, except date must
882          precede time.)
883
884          Size:       ddd or ddd/ddd (where "ddd" is a decimal number)
885          Date:       DD-MMM-YYYY
886          Time:       HH:MM or HH:MM:SS or HH:MM:SS.CC
887          Owner:      [user] or [user,group]
888          Protection: (ppp,ppp,ppp,ppp) (where "ppp" is "RWED" or some
889          subset thereof, for System, Owner, Group, World.
890
891          If permission is lacking, info may be replaced by the string:
892          "No privilege for attempted operation".
893       */
894       while (tok != NULL)
895         {
896           DEBUGP (("Token: >%s<: ", tok));
897
898           if ((strlen (tok) < 12) && (strchr( tok, '-') != NULL))
899             {
900               /* Date. */
901               DEBUGP (("Date.\n"));
902               strcpy( date_str, tok);
903               strcat( date_str, " ");
904             }
905           else if ((strlen (tok) < 12) && (strchr( tok, ':') != NULL))
906             {
907               /* Time. */
908               DEBUGP (("Time. "));
909               strncat( date_str,
910                        tok,
911                        (sizeof( date_str)- strlen (date_str) - 1));
912               DEBUGP (("Date time: >%s<\n", date_str));
913             }
914           else if (strchr ( tok, '[') != NULL)
915             {
916               /* Owner.  (Ignore.) */
917               DEBUGP (("Owner.\n"));
918             }
919           else if (strchr (tok, '(') != NULL)
920             {
921               /* Protections (permissions). */
922               perms = 0;
923               j = 0;
924               for (i = 0; i < strlen( tok); i++)
925                 {
926                   switch (tok[ i])
927                     {
928                     case '(':
929                       break;
930                     case ')':
931                       break;
932                     case ',':
933                       if (j == 0)
934                         {
935                           perms = 0;
936                           j = 1;
937                         }
938                       else
939                         {
940                           perms <<= 3;
941                         }
942                       break;
943                     case 'R':
944                       perms |= 4;
945                       break;
946                     case 'W':
947                       perms |= 2;
948                       break;
949                     case 'E':
950                       perms |= 1;
951                       break;
952                     case 'D':
953                       perms |= 2;
954                       break;
955                     }
956                 }
957               cur.perms = perms;
958               DEBUGP (("Prot.  perms = %0o.\n", cur.perms));
959             }
960           else
961             {
962               /* Nondescript.  Probably size(s), probably in blocks.
963                  Could be "No privilege ..." message.  (Ignore.)
964               */
965               DEBUGP (("Ignored (size?).\n"));
966             }
967
968           tok = strtok (NULL, " ");
969         }
970
971       /* Tokens exhausted.  Interpret the data, and fill in the
972          structure.
973       */
974       /* Fill tm timestruct according to date-time string.  Fractional
975          seconds are ignored.  Default to current time, if conversion
976          fails.
977       */
978       timenow = time( NULL);
979       timestruct = localtime( &timenow );
980       strptime( date_str, "%d-%b-%Y %H:%M:%S", timestruct);
981
982       /* Convert struct tm local time to time_t local time. */
983       timenow = mktime (timestruct);
984       /* Offset local time according to environment variable (seconds). */
985       if ((tok = getenv ( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
986         {
987           dt = atoi (tok);
988           DEBUGP (("Time differential = %d.\n", dt));
989         }
990       else
991         dt = 0;
992
993       if (dt >= 0)
994         timenow += dt;
995       else
996         timenow -= (-dt);
997
998       cur.tstamp = timenow; /* Store the time-stamp. */
999       DEBUGP (("Timestamp: %ld\n", cur.tstamp));
1000       cur.ptype = TT_HOUR_MIN;
1001
1002       /* Add the data for this item to the linked list, */
1003       if (!dir)
1004         {
1005           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
1006           memcpy (l, &cur, sizeof (cur));
1007           l->prev = l->next = NULL;
1008         }
1009       else
1010         {
1011           cur.prev = l;
1012           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
1013           l = l->next;
1014           memcpy (l, &cur, sizeof (cur));
1015           l->next = NULL;
1016         }
1017
1018       /* Free old line storage.  Read a new line. */
1019       xfree (line);
1020       line = read_whole_line (fp);
1021       if (line != NULL)
1022         {
1023           i = clean_line (line);
1024           if (i <= 0)
1025             {
1026               /* Blank line.  End of significant file listing. */
1027               xfree (line); /* Free useless line storage. */
1028               break;
1029             }
1030         }
1031     }
1032
1033   fclose (fp);
1034   return dir;
1035 }
1036
1037
1038 /* This function switches between the correct parsing routine depending on
1039    the SYSTEM_TYPE. The system type should be based on the result of the
1040    "SYST" response of the FTP server. According to this repsonse we will
1041    use on of the three different listing parsers that cover the most of FTP
1042    servers used nowadays.  */
1043
1044 struct fileinfo *
1045 ftp_parse_ls (const char *file, const enum stype system_type)
1046 {
1047   switch (system_type)
1048     {
1049     case ST_UNIX:
1050       return ftp_parse_unix_ls (file, 0);
1051     case ST_WINNT:
1052       {
1053         /* Detect whether the listing is simulating the UNIX format */
1054         FILE *fp;
1055         int   c;
1056         fp = fopen (file, "rb");
1057         if (!fp)
1058         {
1059           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1060           return NULL;
1061         }
1062         c = fgetc(fp);
1063         fclose(fp);
1064         /* If the first character of the file is '0'-'9', it's WINNT
1065            format. */
1066         if (c >= '0' && c <='9')
1067           return ftp_parse_winnt_ls (file);
1068         else
1069           return ftp_parse_unix_ls (file, 1);
1070       }
1071     case ST_VMS:
1072       return ftp_parse_vms_ls (file);
1073     case ST_MACOS:
1074       return ftp_parse_unix_ls (file, 1);
1075     default:
1076       logprintf (LOG_NOTQUIET, _("\
1077 Unsupported listing type, trying Unix listing parser.\n"));
1078       return ftp_parse_unix_ls (file, 0);
1079     }
1080 }
1081 \f
1082 /* Stuff for creating FTP index. */
1083
1084 /* The function creates an HTML index containing references to given
1085    directories and files on the appropriate host.  The references are
1086    FTP.  */
1087 uerr_t
1088 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1089 {
1090   FILE *fp;
1091   char *upwd;
1092   char *htcldir;                /* HTML-clean dir name */
1093   char *htclfile;               /* HTML-clean file name */
1094   char *urlclfile;              /* URL-clean file name */
1095
1096   if (!output_stream)
1097     {
1098       fp = fopen (file, "wb");
1099       if (!fp)
1100         {
1101           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1102           return FOPENERR;
1103         }
1104     }
1105   else
1106     fp = output_stream;
1107   if (u->user)
1108     {
1109       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
1110
1111       tmpu = url_escape (u->user);
1112       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1113       if (tmpp)
1114         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1115       else
1116         upwd = concat_strings (tmpu, "@", (char *) 0);
1117       xfree (tmpu);
1118       xfree_null (tmpp);
1119     }
1120   else
1121     upwd = xstrdup ("");
1122
1123   htcldir = html_quote_string (u->dir);
1124
1125   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1126   fprintf (fp, "<html>\n<head>\n<title>");
1127   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1128   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1129   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1130   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1131
1132   while (f)
1133     {
1134       fprintf (fp, "  ");
1135       if (f->tstamp != -1)
1136         {
1137           /* #### Should we translate the months?  Or, even better, use
1138              ISO 8601 dates?  */
1139           static const char *months[] = {
1140             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1141             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1142           };
1143           time_t tstamp = f->tstamp;
1144           struct tm *ptm = localtime (&tstamp);
1145
1146           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1147                   ptm->tm_mday);
1148           if (f->ptype == TT_HOUR_MIN)
1149             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
1150           else
1151             fprintf (fp, "       ");
1152         }
1153       else
1154         fprintf (fp, _("time unknown       "));
1155       switch (f->type)
1156         {
1157         case FT_PLAINFILE:
1158           fprintf (fp, _("File        "));
1159           break;
1160         case FT_DIRECTORY:
1161           fprintf (fp, _("Directory   "));
1162           break;
1163         case FT_SYMLINK:
1164           fprintf (fp, _("Link        "));
1165           break;
1166         default:
1167           fprintf (fp, _("Not sure    "));
1168           break;
1169         }
1170       htclfile = html_quote_string (f->name);
1171       urlclfile = url_escape_unsafe_and_reserved (f->name);
1172       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1173       if (*u->dir != '/')
1174         putc ('/', fp);
1175       /* XXX: Should probably URL-escape dir components here, rather
1176        * than just HTML-escape, for consistency with the next bit where
1177        * we use urlclfile for the file component. Anyway, this is safer
1178        * than what we had... */
1179       fprintf (fp, "%s", htcldir);
1180       if (*u->dir)
1181         putc ('/', fp);
1182       fprintf (fp, "%s", urlclfile);
1183       if (f->type == FT_DIRECTORY)
1184         putc ('/', fp);
1185       fprintf (fp, "\">%s", htclfile);
1186       if (f->type == FT_DIRECTORY)
1187         putc ('/', fp);
1188       fprintf (fp, "</a> ");
1189       if (f->type == FT_PLAINFILE)
1190         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1191       else if (f->type == FT_SYMLINK)
1192         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1193       putc ('\n', fp);
1194       xfree (htclfile);
1195       xfree (urlclfile);
1196       f = f->next;
1197     }
1198   fprintf (fp, "</pre>\n</body>\n</html>\n");
1199   xfree (htcldir);
1200   xfree (upwd);
1201   if (!output_stream)
1202     fclose (fp);
1203   else
1204     fflush (fp);
1205   return FTPOK;
1206 }