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