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