]> sjero.net Git - wget/blob - src/ftp-ls.c
[svn] Make indentation consistent (all-spaces, no tabs).
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1996-2004 Free Software Foundation, Inc. 
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
18
19 In addition, as a special exception, the Free Software Foundation
20 gives permission to link the code of its release of Wget with the
21 OpenSSL project's "OpenSSL" library (or with modified versions of it
22 that use the same license as the "OpenSSL" library), and distribute
23 the linked executables.  You must obey the GNU General Public License
24 in all respects for all of the code used other than "OpenSSL".  If you
25 modify this file, you may extend this exception to your version of the
26 file, but you are not obligated to do so.  If you do not wish to do
27 so, delete this exception statement from your version.  */
28
29 #include <config.h>
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #ifdef HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
37 #include <errno.h>
38 #include <time.h>
39
40 #include "wget.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 (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;
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       next = -1;
186       /* While there are tokens on the line, parse them.  Next is the
187          number of tokens left until the filename.
188
189          Use the month-name token as the "anchor" (the place where the
190          position wrt the file name is "known").  When a month name is
191          encountered, `next' is set to 5.  Also, the preceding
192          characters are parsed to get the file size.
193
194          This tactic is quite dubious when it comes to
195          internationalization issues (non-English month names), but it
196          works for now.  */
197       tok = line;
198       while (ptok = tok,
199              (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                   /* Parse the previous token with str_to_wgint.  */
214                   if (ptok == line)
215                     {
216                       /* Something has gone wrong during parsing. */
217                       error = 1;
218                       break;
219                     }
220                   errno = 0;
221                   size = str_to_wgint (ptok, NULL, 10);
222                   if (size == WGINT_MAX && errno == ERANGE)
223                     /* Out of range -- ignore the size.  #### Should
224                        we refuse to start the download.  */
225                     cur.size = 0;
226                   else
227                     cur.size = size;
228                   DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
229
230                   month = i;
231                   next = 5;
232                   DEBUGP (("month: %s; ", months[month]));
233                 }
234             }
235           else if (next == 4)   /* days */
236             {
237               if (tok[1])       /* two-digit... */
238                 day = 10 * (*tok - '0') + tok[1] - '0';
239               else              /* ...or one-digit */
240                 day = *tok - '0';
241               DEBUGP (("day: %d; ", day));
242             }
243           else if (next == 3)
244             {
245               /* This ought to be either the time, or the year.  Let's
246                  be flexible!
247
248                  If we have a number x, it's a year.  If we have x:y,
249                  it's hours and minutes.  If we have x:y:z, z are
250                  seconds.  */
251               year = 0;
252               min = hour = sec = 0;
253               /* We must deal with digits.  */
254               if (ISDIGIT (*tok))
255                 {
256                   /* Suppose it's year.  */
257                   for (; ISDIGIT (*tok); tok++)
258                     year = (*tok - '0') + 10 * year;
259                   if (*tok == ':')
260                     {
261                       /* This means these were hours!  */
262                       hour = year;
263                       year = 0;
264                       ++tok;
265                       /* Get the minutes...  */
266                       for (; ISDIGIT (*tok); tok++)
267                         min = (*tok - '0') + 10 * min;
268                       if (*tok == ':')
269                         {
270                           /* ...and the seconds.  */
271                           ++tok;
272                           for (; ISDIGIT (*tok); tok++)
273                             sec = (*tok - '0') + 10 * sec;
274                         }
275                     }
276                 }
277               if (year)
278                 DEBUGP (("year: %d (no tm); ", year));
279               else
280                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
281             }
282           else if (next == 2)    /* The file name */
283             {
284               int fnlen;
285               char *p;
286
287               /* Since the file name may contain a SPC, it is possible
288                  for strtok to handle it wrong.  */
289               fnlen = strlen (tok);
290               if (fnlen < len - (tok - line))
291                 {
292                   /* So we have a SPC in the file name.  Restore the
293                      original.  */
294                   tok[fnlen] = ' ';
295                   /* If the file is a symbolic link, it should have a
296                      ` -> ' somewhere.  */
297                   if (cur.type == FT_SYMLINK)
298                     {
299                       p = strstr (tok, " -> ");
300                       if (!p)
301                         {
302                           error = 1;
303                           break;
304                         }
305                       cur.linkto = xstrdup (p + 4);
306                       DEBUGP (("link to: %s\n", cur.linkto));
307                       /* And separate it from the file name.  */
308                       *p = '\0';
309                     }
310                 }
311               /* If we have the filename, add it to the list of files or
312                  directories.  */
313               /* "." and ".." are an exception!  */
314               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
315                 {
316                   DEBUGP (("\nIgnoring `.' and `..'; "));
317                   ignore = 1;
318                   break;
319                 }
320               /* Some FTP sites choose to have ls -F as their default
321                  LIST output, which marks the symlinks with a trailing
322                  `@', directory names with a trailing `/' and
323                  executables with a trailing `*'.  This is no problem
324                  unless encountering a symbolic link ending with `@',
325                  or an executable ending with `*' on a server without
326                  default -F output.  I believe these cases are very
327                  rare.  */
328               fnlen = strlen (tok); /* re-calculate `fnlen' */
329               cur.name = xmalloc (fnlen + 1);
330               memcpy (cur.name, tok, fnlen + 1);
331               if (fnlen)
332                 {
333                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
334                     {
335                       cur.name[fnlen - 1] = '\0';
336                       DEBUGP (("trailing `/' on dir.\n"));
337                     }
338                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
339                     {
340                       cur.name[fnlen - 1] = '\0';
341                       DEBUGP (("trailing `@' on link.\n"));
342                     }
343                   else if (cur.type == FT_PLAINFILE
344                            && (cur.perms & 0111)
345                            && cur.name[fnlen - 1] == '*')
346                     {
347                       cur.name[fnlen - 1] = '\0';
348                       DEBUGP (("trailing `*' on exec.\n"));
349                     }
350                 } /* if (fnlen) */
351               else
352                 error = 1;
353               break;
354             }
355           else
356             abort ();
357         } /* while */
358
359       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
360         error = 1;
361
362       DEBUGP (("%s\n", cur.name ? cur.name : ""));
363
364       if (error || ignore)
365         {
366           DEBUGP (("Skipping.\n"));
367           xfree_null (cur.name);
368           xfree_null (cur.linkto);
369           xfree (line);
370           continue;
371         }
372
373       if (!dir)
374         {
375           l = dir = xnew (struct fileinfo);
376           memcpy (l, &cur, sizeof (cur));
377           l->prev = l->next = NULL;
378         }
379       else
380         {
381           cur.prev = l;
382           l->next = xnew (struct fileinfo);
383           l = l->next;
384           memcpy (l, &cur, sizeof (cur));
385           l->next = NULL;
386         }
387       /* Get the current time.  */
388       timenow = time (NULL);
389       tnow = localtime (&timenow);
390       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
391       timestruct.tm_sec   = sec;
392       timestruct.tm_min   = min;
393       timestruct.tm_hour  = hour;
394       timestruct.tm_mday  = day;
395       timestruct.tm_mon   = month;
396       if (year == 0)
397         {
398           /* Some listings will not specify the year if it is "obvious"
399              that the file was from the previous year.  E.g. if today
400              is 97-01-12, and you see a file of Dec 15th, its year is
401              1996, not 1997.  Thanks to Vladimir Volovich for
402              mentioning this!  */
403           if (month > tnow->tm_mon)
404             timestruct.tm_year = tnow->tm_year - 1;
405           else
406             timestruct.tm_year = tnow->tm_year;
407         }
408       else
409         timestruct.tm_year = year;
410       if (timestruct.tm_year >= 1900)
411         timestruct.tm_year -= 1900;
412       timestruct.tm_wday  = 0;
413       timestruct.tm_yday  = 0;
414       timestruct.tm_isdst = -1;
415       l->tstamp = mktime (&timestruct); /* store the time-stamp */
416
417       xfree (line);
418     }
419
420   fclose (fp);
421   return dir;
422 }
423
424 static struct fileinfo *
425 ftp_parse_winnt_ls (const char *file)
426 {
427   FILE *fp;
428   int len;
429   int year, month, day;         /* for time analysis */
430   int hour, min;
431   struct tm timestruct;
432
433   char *line, *tok;             /* tokenizer */
434   struct fileinfo *dir, *l, cur; /* list creation */
435
436   fp = fopen (file, "rb");
437   if (!fp)
438     {
439       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
440       return NULL;
441     }
442   dir = l = NULL;
443
444   /* Line loop to end of file: */
445   while ((line = read_whole_line (fp)) != NULL)
446     {
447       len = clean_line (line);
448
449       /* Extracting name is a bit of black magic and we have to do it
450          before `strtok' inserted extra \0 characters in the line
451          string. For the moment let us just suppose that the name starts at
452          column 39 of the listing. This way we could also recognize
453          filenames that begin with a series of space characters (but who
454          really wants to use such filenames anyway?). */
455       if (len < 40) continue;
456       tok = line + 39;
457       cur.name = xstrdup(tok);
458       DEBUGP(("Name: '%s'\n", cur.name));
459
460       /* First column: mm-dd-yy. Should atoi() on the month fail, january
461          will be assumed.  */
462       tok = strtok(line, "-");
463       if (tok == NULL) continue;
464       month = atoi(tok) - 1;
465       if (month < 0) month = 0;
466       tok = strtok(NULL, "-");
467       if (tok == NULL) continue;
468       day = atoi(tok);
469       tok = strtok(NULL, " ");
470       if (tok == NULL) continue;
471       year = atoi(tok);
472       /* Assuming the epoch starting at 1.1.1970 */
473       if (year <= 70) year += 100;
474
475       /* Second column: hh:mm[AP]M, listing does not contain value for
476          seconds */
477       tok = strtok(NULL,  ":");
478       if (tok == NULL) continue;
479       hour = atoi(tok);
480       tok = strtok(NULL,  "M");
481       if (tok == NULL) continue;
482       min = atoi(tok);
483       /* Adjust hour from AM/PM. Just for the record, the sequence goes
484          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
485       tok+=2;
486       if (hour == 12)  hour  = 0;
487       if (*tok == 'P') hour += 12;
488
489       DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n", 
490               year+1900, month, day, hour, min));
491       
492       /* Build the time-stamp (copy & paste from above) */
493       timestruct.tm_sec   = 0;
494       timestruct.tm_min   = min;
495       timestruct.tm_hour  = hour;
496       timestruct.tm_mday  = day;
497       timestruct.tm_mon   = month;
498       timestruct.tm_year  = year;
499       timestruct.tm_wday  = 0;
500       timestruct.tm_yday  = 0;
501       timestruct.tm_isdst = -1;
502       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
503
504       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
505
506       /* Third column: Either file length, or <DIR>. We also set the
507          permissions (guessed as 0644 for plain files and 0755 for
508          directories as the listing does not give us a clue) and filetype
509          here. */
510       tok = strtok(NULL, " ");
511       if (tok == NULL) continue;
512       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
513       if (tok == NULL) continue;
514       if (*tok == '<')
515         {
516           cur.type  = FT_DIRECTORY;
517           cur.size  = 0;
518           cur.perms = 0755;
519           DEBUGP(("Directory\n"));
520         }
521       else
522         {
523           wgint size;
524           cur.type  = FT_PLAINFILE;
525           errno = 0;
526           size = str_to_wgint (tok, NULL, 10);
527           if (size == WGINT_MAX && errno == ERANGE)
528             cur.size = 0;       /* overflow */
529           else
530             cur.size = size;
531           cur.perms = 0644;
532           DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
533         }
534
535       cur.linkto = NULL;
536
537       /* And put everything into the linked list */
538       if (!dir)
539         {
540           l = dir = xnew (struct fileinfo);
541           memcpy (l, &cur, sizeof (cur));
542           l->prev = l->next = NULL;
543         }
544       else
545         {
546           cur.prev = l;
547           l->next = xnew (struct fileinfo);
548           l = l->next;
549           memcpy (l, &cur, sizeof (cur));
550           l->next = NULL;
551         }
552
553       xfree (line);
554     }
555
556   fclose(fp);
557   return dir;
558 }
559
560 /* Converts VMS symbolic permissions to number-style ones, e.g. string
561    RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
562    (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
563 static int
564 vmsperms (const char *s)
565 {
566   int perms = 0;
567
568   do
569     {
570       switch (*s) {
571         case ',': perms <<= 3; break;
572         case 'R': perms  |= 4; break;
573         case 'W': perms  |= 2; break;
574         case 'D': perms  |= 2; break;
575         case 'E': perms  |= 1; break;
576         default:  DEBUGP(("wrong VMS permissons!\n")); 
577       }
578     }
579   while (*++s);
580   return perms;
581 }
582
583
584 static struct fileinfo *
585 ftp_parse_vms_ls (const char *file)
586 {
587   FILE *fp;
588   /* #### A third copy of more-or-less the same array ? */
589   static const char *months[] = {
590     "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
591     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
592   };
593   int i;
594   int year, month, day;          /* for time analysis */
595   int hour, min, sec;
596   struct tm timestruct;
597
598   char *line, *tok;              /* tokenizer */
599   struct fileinfo *dir, *l, cur; /* list creation */
600
601   fp = fopen (file, "rb");
602   if (!fp)
603     {
604       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
605       return NULL;
606     }
607   dir = l = NULL;
608
609   /* Skip empty line. */
610   line = read_whole_line (fp);
611   xfree_null (line);
612
613   /* Skip "Directory PUB$DEVICE[PUB]" */
614   line = read_whole_line (fp);
615   xfree_null (line);
616
617   /* Skip empty line. */
618   line = read_whole_line (fp);
619   xfree_null (line);
620
621   /* Line loop to end of file: */
622   while ((line = read_whole_line (fp)) != NULL)
623     {
624       char *p;
625       i = clean_line (line);
626       if (!i)
627         {
628           xfree (line);
629           break;
630         }
631
632       /* First column: Name. A bit of black magic again. The name my be
633          either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
634          line. Therefore we will first try to get the complete name
635          until the first space character; if it fails, we assume that the name
636          occupies the whole line. After that we search for the version
637          separator ";", we remove it and check the extension of the file;
638          extension .DIR denotes directory. */
639
640       tok = strtok(line, " ");
641       if (tok == NULL) tok = line;
642       DEBUGP(("file name: '%s'\n", tok));
643       for (p = tok ; *p && *p != ';' ; p++)
644         ;
645       if (*p == ';') *p = '\0';
646       p   = tok + strlen(tok) - 4;
647       if (!strcmp(p, ".DIR")) *p = '\0';
648       cur.name = xstrdup(tok);
649       DEBUGP(("Name: '%s'\n", cur.name));
650
651       /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
652          the file size to zero as the listing does tell us only the size in
653          filesystem blocks - for an integrity check (when mirroring, for
654          example) we would need the size in bytes. */
655       
656       if (! *p)
657         {
658           cur.type  = FT_DIRECTORY;
659           cur.size  = 0;
660           DEBUGP(("Directory\n"));
661         }
662       else
663         {
664           cur.type  = FT_PLAINFILE;
665           DEBUGP(("File\n"));
666         }
667
668       cur.size  = 0;
669
670       /* Second column, if exists, or the first column of the next line
671          contain file size in blocks. We will skip it. */
672
673       tok = strtok(NULL, " ");
674       if (tok == NULL) 
675       {
676         DEBUGP(("Getting additional line\n"));
677         xfree (line);
678         line = read_whole_line (fp);
679         if (!line)
680         {
681           DEBUGP(("empty line read, leaving listing parser\n"));
682           break;
683         }
684         i = clean_line (line);
685         if (!i) 
686         {
687           DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
688           xfree (line);
689           break;
690         }
691         tok = strtok(line, " ");
692       }
693       DEBUGP(("second token: '%s'\n", tok));
694
695       /* Third/Second column: Date DD-MMM-YYYY. */
696
697       tok = strtok(NULL, "-");
698       if (tok == NULL) continue;
699       DEBUGP(("day: '%s'\n",tok));
700       day = atoi(tok);
701       tok = strtok(NULL, "-");
702       if (!tok)
703       {
704         /* If the server produces garbage like
705            'EA95_0PS.GZ;1      No privilege for attempted operation'
706            the first strtok(NULL, "-") will return everything until the end
707            of the line and only the next strtok() call will return NULL. */
708         DEBUGP(("nonsense in VMS listing, skipping this line\n"));
709         xfree (line);
710         break;
711       }
712       for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
713       /* Uknown months are mapped to January */
714       month = i % 12 ; 
715       tok = strtok (NULL, " ");
716       if (tok == NULL) continue;
717       year = atoi (tok) - 1900;
718       DEBUGP(("date parsed\n"));
719
720       /* Fourth/Third column: Time hh:mm[:ss] */
721       tok = strtok (NULL, " ");
722       if (tok == NULL) continue;
723       min = sec = 0;
724       p = tok;
725       hour = atoi (p);
726       for (; *p && *p != ':'; ++p)
727         ;
728       if (*p)
729         min = atoi (++p);
730       for (; *p && *p != ':'; ++p)
731         ;
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 }