]> sjero.net Git - wget/blob - src/ftp-ls.c
Fix underflow, incorrect assertion.
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
3    2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. 
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #include "wget.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #ifdef HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 #include <errno.h>
40 #include <time.h>
41 #include "utils.h"
42 #include "ftp.h"
43 #include "url.h"
44 #include "convert.h"            /* for html_quote_string prototype */
45 #include "retr.h"               /* for output_stream */
46
47 /* Converts symbolic permissions to number-style ones, e.g. string
48    rwxr-xr-x to 755.  For now, it knows nothing of
49    setuid/setgid/sticky.  ACLs are ignored.  */
50 static int
51 symperms (const char *s)
52 {
53   int perms = 0, i;
54
55   if (strlen (s) < 9)
56     return 0;
57   for (i = 0; i < 3; i++, s += 3)
58     {
59       perms <<= 3;
60       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
61                 (s[2] == 'x' || s[2] == 's'));
62     }
63   return perms;
64 }
65
66
67 /* Cleans a line of text so that it can be consistently parsed. Destroys
68    <CR> and <LF> in case that thay occur at the end of the line and
69    replaces all <TAB> character with <SPACE>. Returns the length of the
70    modified line. */
71 static int
72 clean_line(char *line)
73 {
74   int len = strlen (line);
75   if (!len) return 0; 
76   if (line[len - 1] == '\n')
77     line[--len] = '\0';
78   if (!len) return 0; 
79   if (line[len - 1] == '\r')
80     line[--len] = '\0';
81   for ( ; *line ; line++ ) if (*line == '\t') *line = ' '; 
82   return len;
83 }
84
85 /* Convert the Un*x-ish style directory listing stored in FILE to a
86    linked list of fileinfo (system-independent) entries.  The contents
87    of FILE are considered to be produced by the standard Unix `ls -la'
88    output (whatever that might be).  BSD (no group) and SYSV (with
89    group) listings are handled.
90
91    The time stamps are stored in a separate variable, time_t
92    compatible (I hope).  The timezones are ignored.  */
93 static struct fileinfo *
94 ftp_parse_unix_ls (const char *file, int ignore_perms)
95 {
96   FILE *fp;
97   static const char *months[] = {
98     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
99     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
100   };
101   int next, len, i, error, ignore;
102   int year, month, day;         /* for time analysis */
103   int hour, min, sec;
104   struct tm timestruct, *tnow;
105   time_t timenow;
106
107   char *line, *tok, *ptok;      /* tokenizer */
108   struct fileinfo *dir, *l, cur; /* list creation */
109
110   fp = fopen (file, "rb");
111   if (!fp)
112     {
113       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
114       return NULL;
115     }
116   dir = l = NULL;
117
118   /* Line loop to end of file: */
119   while ((line = read_whole_line (fp)) != NULL)
120     {
121       len = clean_line (line);
122       /* Skip if total...  */
123       if (!strncasecmp (line, "total", 5))
124         {
125           xfree (line);
126           continue;
127         }
128       /* Get the first token (permissions).  */
129       tok = strtok (line, " ");
130       if (!tok)
131         {
132           xfree (line);
133           continue;
134         }
135
136       cur.name = NULL;
137       cur.linkto = NULL;
138
139       /* Decide whether we deal with a file or a directory.  */
140       switch (*tok)
141         {
142         case '-':
143           cur.type = FT_PLAINFILE;
144           DEBUGP (("PLAINFILE; "));
145           break;
146         case 'd':
147           cur.type = FT_DIRECTORY;
148           DEBUGP (("DIRECTORY; "));
149           break;
150         case 'l':
151           cur.type = FT_SYMLINK;
152           DEBUGP (("SYMLINK; "));
153           break;
154         default:
155           cur.type = FT_UNKNOWN;
156           DEBUGP (("UNKNOWN; "));
157           break;
158         }
159
160       if (ignore_perms)
161         {
162           switch (cur.type)
163             {
164             case FT_PLAINFILE:
165               cur.perms = 0644;
166               break;
167             case FT_DIRECTORY:
168               cur.perms = 0755;
169               break;
170             default:
171               /*cur.perms = 1023;*/     /* #### What is this?  --hniksic */
172               cur.perms = 0644;
173             }
174           DEBUGP (("implicit perms %0o; ", cur.perms));
175         }
176        else
177          {
178            cur.perms = symperms (tok + 1);
179            DEBUGP (("perms %0o; ", cur.perms));
180          }
181
182       error = ignore = 0;       /* Erroneous and ignoring entries are
183                                    treated equally for now.  */
184       year = hour = min = sec = 0; /* Silence the compiler.  */
185       month = day = 0;
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                       ++tok;
266                       /* Get the minutes...  */
267                       for (; c_isdigit (*tok); tok++)
268                         min = (*tok - '0') + 10 * min;
269                       if (*tok == ':')
270                         {
271                           /* ...and the seconds.  */
272                           ++tok;
273                           for (; c_isdigit (*tok); tok++)
274                             sec = (*tok - '0') + 10 * sec;
275                         }
276                     }
277                 }
278               if (year)
279                 DEBUGP (("year: %d (no tm); ", year));
280               else
281                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
282             }
283           else if (next == 2)    /* The file name */
284             {
285               int fnlen;
286               char *p;
287
288               /* Since the file name may contain a SPC, it is possible
289                  for strtok to handle it wrong.  */
290               fnlen = strlen (tok);
291               if (fnlen < len - (tok - line))
292                 {
293                   /* So we have a SPC in the file name.  Restore the
294                      original.  */
295                   tok[fnlen] = ' ';
296                   /* If the file is a symbolic link, it should have a
297                      ` -> ' somewhere.  */
298                   if (cur.type == FT_SYMLINK)
299                     {
300                       p = strstr (tok, " -> ");
301                       if (!p)
302                         {
303                           error = 1;
304                           break;
305                         }
306                       cur.linkto = xstrdup (p + 4);
307                       DEBUGP (("link to: %s\n", cur.linkto));
308                       /* And separate it from the file name.  */
309                       *p = '\0';
310                     }
311                 }
312               /* If we have the filename, add it to the list of files or
313                  directories.  */
314               /* "." and ".." are an exception!  */
315               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
316                 {
317                   DEBUGP (("\nIgnoring `.' and `..'; "));
318                   ignore = 1;
319                   break;
320                 }
321               /* Some FTP sites choose to have ls -F as their default
322                  LIST output, which marks the symlinks with a trailing
323                  `@', directory names with a trailing `/' and
324                  executables with a trailing `*'.  This is no problem
325                  unless encountering a symbolic link ending with `@',
326                  or an executable ending with `*' on a server without
327                  default -F output.  I believe these cases are very
328                  rare.  */
329               fnlen = strlen (tok); /* re-calculate `fnlen' */
330               cur.name = xmalloc (fnlen + 1);
331               memcpy (cur.name, tok, fnlen + 1);
332               if (fnlen)
333                 {
334                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
335                     {
336                       cur.name[fnlen - 1] = '\0';
337                       DEBUGP (("trailing `/' on dir.\n"));
338                     }
339                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
340                     {
341                       cur.name[fnlen - 1] = '\0';
342                       DEBUGP (("trailing `@' on link.\n"));
343                     }
344                   else if (cur.type == FT_PLAINFILE
345                            && (cur.perms & 0111)
346                            && cur.name[fnlen - 1] == '*')
347                     {
348                       cur.name[fnlen - 1] = '\0';
349                       DEBUGP (("trailing `*' on exec.\n"));
350                     }
351                 } /* if (fnlen) */
352               else
353                 error = 1;
354               break;
355             }
356           else
357             abort ();
358         } /* while */
359
360       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
361         error = 1;
362
363       DEBUGP (("%s\n", cur.name ? cur.name : ""));
364
365       if (error || ignore)
366         {
367           DEBUGP (("Skipping.\n"));
368           xfree_null (cur.name);
369           xfree_null (cur.linkto);
370           xfree (line);
371           continue;
372         }
373
374       if (!dir)
375         {
376           l = dir = xnew (struct fileinfo);
377           memcpy (l, &cur, sizeof (cur));
378           l->prev = l->next = NULL;
379         }
380       else
381         {
382           cur.prev = l;
383           l->next = xnew (struct fileinfo);
384           l = l->next;
385           memcpy (l, &cur, sizeof (cur));
386           l->next = NULL;
387         }
388       /* Get the current time.  */
389       timenow = time (NULL);
390       tnow = localtime (&timenow);
391       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
392       timestruct.tm_sec   = sec;
393       timestruct.tm_min   = min;
394       timestruct.tm_hour  = hour;
395       timestruct.tm_mday  = day;
396       timestruct.tm_mon   = month;
397       if (year == 0)
398         {
399           /* Some listings will not specify the year if it is "obvious"
400              that the file was from the previous year.  E.g. if today
401              is 97-01-12, and you see a file of Dec 15th, its year is
402              1996, not 1997.  Thanks to Vladimir Volovich for
403              mentioning this!  */
404           if (month > tnow->tm_mon)
405             timestruct.tm_year = tnow->tm_year - 1;
406           else
407             timestruct.tm_year = tnow->tm_year;
408         }
409       else
410         timestruct.tm_year = year;
411       if (timestruct.tm_year >= 1900)
412         timestruct.tm_year -= 1900;
413       timestruct.tm_wday  = 0;
414       timestruct.tm_yday  = 0;
415       timestruct.tm_isdst = -1;
416       l->tstamp = mktime (&timestruct); /* store the time-stamp */
417
418       xfree (line);
419     }
420
421   fclose (fp);
422   return dir;
423 }
424
425 static struct fileinfo *
426 ftp_parse_winnt_ls (const char *file)
427 {
428   FILE *fp;
429   int len;
430   int year, month, day;         /* for time analysis */
431   int hour, min;
432   struct tm timestruct;
433
434   char *line, *tok;             /* tokenizer */
435   struct fileinfo *dir, *l, cur; /* list creation */
436
437   fp = fopen (file, "rb");
438   if (!fp)
439     {
440       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
441       return NULL;
442     }
443   dir = l = NULL;
444
445   /* Line loop to end of file: */
446   while ((line = read_whole_line (fp)) != NULL)
447     {
448       len = clean_line (line);
449
450       /* Extracting name is a bit of black magic and we have to do it
451          before `strtok' inserted extra \0 characters in the line
452          string. For the moment let us just suppose that the name starts at
453          column 39 of the listing. This way we could also recognize
454          filenames that begin with a series of space characters (but who
455          really wants to use such filenames anyway?). */
456       if (len < 40) continue;
457       tok = line + 39;
458       cur.name = xstrdup(tok);
459       DEBUGP(("Name: '%s'\n", cur.name));
460
461       /* First column: mm-dd-yy. Should atoi() on the month fail, january
462          will be assumed.  */
463       tok = strtok(line, "-");
464       if (tok == NULL) continue;
465       month = atoi(tok) - 1;
466       if (month < 0) month = 0;
467       tok = strtok(NULL, "-");
468       if (tok == NULL) continue;
469       day = atoi(tok);
470       tok = strtok(NULL, " ");
471       if (tok == NULL) continue;
472       year = atoi(tok);
473       /* Assuming the epoch starting at 1.1.1970 */
474       if (year <= 70) year += 100;
475
476       /* Second column: hh:mm[AP]M, listing does not contain value for
477          seconds */
478       tok = strtok(NULL,  ":");
479       if (tok == NULL) continue;
480       hour = atoi(tok);
481       tok = strtok(NULL,  "M");
482       if (tok == NULL) continue;
483       min = atoi(tok);
484       /* Adjust hour from AM/PM. Just for the record, the sequence goes
485          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
486       tok+=2;
487       if (hour == 12)  hour  = 0;
488       if (*tok == 'P') hour += 12;
489
490       DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n", 
491               year+1900, month, day, hour, min));
492       
493       /* Build the time-stamp (copy & paste from above) */
494       timestruct.tm_sec   = 0;
495       timestruct.tm_min   = min;
496       timestruct.tm_hour  = hour;
497       timestruct.tm_mday  = day;
498       timestruct.tm_mon   = month;
499       timestruct.tm_year  = year;
500       timestruct.tm_wday  = 0;
501       timestruct.tm_yday  = 0;
502       timestruct.tm_isdst = -1;
503       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
504
505       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
506
507       /* Third column: Either file length, or <DIR>. We also set the
508          permissions (guessed as 0644 for plain files and 0755 for
509          directories as the listing does not give us a clue) and filetype
510          here. */
511       tok = strtok(NULL, " ");
512       if (tok == NULL) continue;
513       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
514       if (tok == NULL) continue;
515       if (*tok == '<')
516         {
517           cur.type  = FT_DIRECTORY;
518           cur.size  = 0;
519           cur.perms = 0755;
520           DEBUGP(("Directory\n"));
521         }
522       else
523         {
524           wgint size;
525           cur.type  = FT_PLAINFILE;
526           errno = 0;
527           size = str_to_wgint (tok, NULL, 10);
528           if (size == WGINT_MAX && errno == ERANGE)
529             cur.size = 0;       /* overflow */
530           else
531             cur.size = size;
532           cur.perms = 0644;
533           DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
534         }
535
536       cur.linkto = NULL;
537
538       /* And put everything into the linked list */
539       if (!dir)
540         {
541           l = dir = xnew (struct fileinfo);
542           memcpy (l, &cur, sizeof (cur));
543           l->prev = l->next = NULL;
544         }
545       else
546         {
547           cur.prev = l;
548           l->next = xnew (struct fileinfo);
549           l = l->next;
550           memcpy (l, &cur, sizeof (cur));
551           l->next = NULL;
552         }
553
554       xfree (line);
555     }
556
557   fclose(fp);
558   return dir;
559 }
560
561 /* Converts VMS symbolic permissions to number-style ones, e.g. string
562    RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
563    (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
564 static int
565 vmsperms (const char *s)
566 {
567   int perms = 0;
568
569   do
570     {
571       switch (*s) {
572         case ',': perms <<= 3; break;
573         case 'R': perms  |= 4; break;
574         case 'W': perms  |= 2; break;
575         case 'D': perms  |= 2; break;
576         case 'E': perms  |= 1; break;
577         default:  DEBUGP(("wrong VMS permissons!\n")); 
578       }
579     }
580   while (*++s);
581   return perms;
582 }
583
584
585 static struct fileinfo *
586 ftp_parse_vms_ls (const char *file)
587 {
588   FILE *fp;
589   /* #### A third copy of more-or-less the same array ? */
590   static const char *months[] = {
591     "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
592     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
593   };
594   int i;
595   int year, month, day;          /* for time analysis */
596   int hour, min, sec;
597   struct tm timestruct;
598
599   char *line, *tok;              /* tokenizer */
600   struct fileinfo *dir, *l, cur; /* list creation */
601
602   fp = fopen (file, "rb");
603   if (!fp)
604     {
605       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
606       return NULL;
607     }
608   dir = l = NULL;
609
610   /* Skip empty line. */
611   line = read_whole_line (fp);
612   xfree_null (line);
613
614   /* Skip "Directory PUB$DEVICE[PUB]" */
615   line = read_whole_line (fp);
616   xfree_null (line);
617
618   /* Skip empty line. */
619   line = read_whole_line (fp);
620   xfree_null (line);
621
622   /* Line loop to end of file: */
623   while ((line = read_whole_line (fp)) != NULL)
624     {
625       char *p;
626       i = clean_line (line);
627       if (!i)
628         {
629           xfree (line);
630           break;
631         }
632
633       /* First column: Name. A bit of black magic again. The name my be
634          either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
635          line. Therefore we will first try to get the complete name
636          until the first space character; if it fails, we assume that the name
637          occupies the whole line. After that we search for the version
638          separator ";", we remove it and check the extension of the file;
639          extension .DIR denotes directory. */
640
641       tok = strtok(line, " ");
642       if (tok == NULL) tok = line;
643       DEBUGP(("file name: '%s'\n", tok));
644       for (p = tok ; *p && *p != ';' ; p++)
645         ;
646       if (*p == ';') *p = '\0';
647       p   = tok + strlen(tok) - 4;
648       if (!strcmp(p, ".DIR")) *p = '\0';
649       cur.name = xstrdup(tok);
650       DEBUGP(("Name: '%s'\n", cur.name));
651
652       /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
653          the file size to zero as the listing does tell us only the size in
654          filesystem blocks - for an integrity check (when mirroring, for
655          example) we would need the size in bytes. */
656       
657       if (! *p)
658         {
659           cur.type  = FT_DIRECTORY;
660           cur.size  = 0;
661           DEBUGP(("Directory\n"));
662         }
663       else
664         {
665           cur.type  = FT_PLAINFILE;
666           DEBUGP(("File\n"));
667         }
668
669       cur.size  = 0;
670
671       /* Second column, if exists, or the first column of the next line
672          contain file size in blocks. We will skip it. */
673
674       tok = strtok(NULL, " ");
675       if (tok == NULL) 
676       {
677         DEBUGP(("Getting additional line\n"));
678         xfree (line);
679         line = read_whole_line (fp);
680         if (!line)
681         {
682           DEBUGP(("empty line read, leaving listing parser\n"));
683           break;
684         }
685         i = clean_line (line);
686         if (!i) 
687         {
688           DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
689           xfree (line);
690           break;
691         }
692         tok = strtok(line, " ");
693       }
694       DEBUGP(("second token: '%s'\n", tok));
695
696       /* Third/Second column: Date DD-MMM-YYYY. */
697
698       tok = strtok(NULL, "-");
699       if (tok == NULL) continue;
700       DEBUGP(("day: '%s'\n",tok));
701       day = atoi(tok);
702       tok = strtok(NULL, "-");
703       if (!tok)
704       {
705         /* If the server produces garbage like
706            'EA95_0PS.GZ;1      No privilege for attempted operation'
707            the first strtok(NULL, "-") will return everything until the end
708            of the line and only the next strtok() call will return NULL. */
709         DEBUGP(("nonsense in VMS listing, skipping this line\n"));
710         xfree (line);
711         break;
712       }
713       for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
714       /* Uknown months are mapped to January */
715       month = i % 12 ; 
716       tok = strtok (NULL, " ");
717       if (tok == NULL) continue;
718       year = atoi (tok) - 1900;
719       DEBUGP(("date parsed\n"));
720
721       /* Fourth/Third column: Time hh:mm[:ss] */
722       tok = strtok (NULL, " ");
723       if (tok == NULL) continue;
724       min = sec = 0;
725       p = tok;
726       hour = atoi (p);
727       for (; *p && *p != ':'; ++p)
728         ;
729       if (*p)
730         min = atoi (++p);
731       for (; *p && *p != ':'; ++p)
732         ;
733       if (*p)
734         sec = atoi (++p);
735
736       DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n", 
737               year+1900, month, day, hour, min, sec));
738       
739       /* Build the time-stamp (copy & paste from above) */
740       timestruct.tm_sec   = sec;
741       timestruct.tm_min   = min;
742       timestruct.tm_hour  = hour;
743       timestruct.tm_mday  = day;
744       timestruct.tm_mon   = month;
745       timestruct.tm_year  = year;
746       timestruct.tm_wday  = 0;
747       timestruct.tm_yday  = 0;
748       timestruct.tm_isdst = -1;
749       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
750
751       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
752
753       /* Skip the fifth column */
754
755       tok = strtok(NULL, " ");
756       if (tok == NULL) continue;
757
758       /* Sixth column: Permissions */
759
760       tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
761       if (tok == NULL) continue;
762       tok = strtok(NULL, ")");
763       if (tok == NULL)
764         {
765           DEBUGP(("confusing VMS permissions, skipping line\n"));
766           xfree (line);
767           continue;
768         }
769       /* Permissons have the format "RWED,RWED,RE" */
770       cur.perms = vmsperms(tok);
771       DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
772
773       cur.linkto = NULL;
774
775       /* And put everything into the linked list */
776       if (!dir)
777         {
778           l = dir = xnew (struct fileinfo);
779           memcpy (l, &cur, sizeof (cur));
780           l->prev = l->next = NULL;
781         }
782       else
783         {
784           cur.prev = l;
785           l->next = xnew (struct fileinfo);
786           l = l->next;
787           memcpy (l, &cur, sizeof (cur));
788           l->next = NULL;
789         }
790
791       xfree (line);
792     }
793
794   fclose (fp);
795   return dir;
796 }
797
798
799 /* This function switches between the correct parsing routine depending on
800    the SYSTEM_TYPE. The system type should be based on the result of the
801    "SYST" response of the FTP server. According to this repsonse we will
802    use on of the three different listing parsers that cover the most of FTP
803    servers used nowadays.  */
804
805 struct fileinfo *
806 ftp_parse_ls (const char *file, const enum stype system_type)
807 {
808   switch (system_type)
809     {
810     case ST_UNIX:
811       return ftp_parse_unix_ls (file, 0);
812     case ST_WINNT:
813       {
814         /* Detect whether the listing is simulating the UNIX format */
815         FILE *fp;
816         int   c;
817         fp = fopen (file, "rb");
818         if (!fp)
819         {
820           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
821           return NULL;
822         }
823         c = fgetc(fp);
824         fclose(fp);
825         /* If the first character of the file is '0'-'9', it's WINNT
826            format. */
827         if (c >= '0' && c <='9')
828           return ftp_parse_winnt_ls (file);
829         else
830           return ftp_parse_unix_ls (file, 1);
831       }
832     case ST_VMS:
833       return ftp_parse_vms_ls (file);
834     case ST_MACOS:
835       return ftp_parse_unix_ls (file, 1);
836     default:
837       logprintf (LOG_NOTQUIET, _("\
838 Unsupported listing type, trying Unix listing parser.\n"));
839       return ftp_parse_unix_ls (file, 0);
840     }
841 }
842 \f
843 /* Stuff for creating FTP index. */
844
845 /* The function creates an HTML index containing references to given
846    directories and files on the appropriate host.  The references are
847    FTP.  */
848 uerr_t
849 ftp_index (const char *file, struct url *u, struct fileinfo *f)
850 {
851   FILE *fp;
852   char *upwd;
853   char *htclfile;               /* HTML-clean file name */
854
855   if (!output_stream)
856     {
857       fp = fopen (file, "wb");
858       if (!fp)
859         {
860           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
861           return FOPENERR;
862         }
863     }
864   else
865     fp = output_stream;
866   if (u->user)
867     {
868       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
869
870       tmpu = url_escape (u->user);
871       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
872       if (tmpp)
873         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
874       else
875         upwd = concat_strings (tmpu, "@", (char *) 0);
876       xfree (tmpu);
877       xfree_null (tmpp);
878     }
879   else
880     upwd = xstrdup ("");
881   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
882   fprintf (fp, "<html>\n<head>\n<title>");
883   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
884   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
885   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
886   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
887   while (f)
888     {
889       fprintf (fp, "  ");
890       if (f->tstamp != -1)
891         {
892           /* #### Should we translate the months?  Or, even better, use
893              ISO 8601 dates?  */
894           static const char *months[] = {
895             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
896             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
897           };
898           time_t tstamp = f->tstamp;
899           struct tm *ptm = localtime (&tstamp);
900
901           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
902                   ptm->tm_mday);
903           if (ptm->tm_hour)
904             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
905           else
906             fprintf (fp, "       ");
907         }
908       else
909         fprintf (fp, _("time unknown       "));
910       switch (f->type)
911         {
912         case FT_PLAINFILE:
913           fprintf (fp, _("File        "));
914           break;
915         case FT_DIRECTORY:
916           fprintf (fp, _("Directory   "));
917           break;
918         case FT_SYMLINK:
919           fprintf (fp, _("Link        "));
920           break;
921         default:
922           fprintf (fp, _("Not sure    "));
923           break;
924         }
925       htclfile = html_quote_string (f->name);
926       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
927       if (*u->dir != '/')
928         putc ('/', fp);
929       fprintf (fp, "%s", u->dir);
930       if (*u->dir)
931         putc ('/', fp);
932       fprintf (fp, "%s", htclfile);
933       if (f->type == FT_DIRECTORY)
934         putc ('/', fp);
935       fprintf (fp, "\">%s", htclfile);
936       if (f->type == FT_DIRECTORY)
937         putc ('/', fp);
938       fprintf (fp, "</a> ");
939       if (f->type == FT_PLAINFILE)
940         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
941       else if (f->type == FT_SYMLINK)
942         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
943       putc ('\n', fp);
944       xfree (htclfile);
945       f = f->next;
946     }
947   fprintf (fp, "</pre>\n</body>\n</html>\n");
948   xfree (upwd);
949   if (!output_stream)
950     fclose (fp);
951   else
952     fflush (fp);
953   return FTPOK;
954 }