]> sjero.net Git - wget/blob - src/ftp-ls.c
Updated licensing exception for OpenSSL from the SFLC.
[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 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 <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, *ptok;      /* 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       tok = line;
200       while (ptok = tok,
201              (tok = strtok (NULL, " ")) != NULL)
202         {
203           --next;
204           if (next < 0)         /* a month name was not encountered */
205             {
206               for (i = 0; i < 12; i++)
207                 if (!strcmp (tok, months[i]))
208                   break;
209               /* If we got a month, it means the token before it is the
210                  size, and the filename is three tokens away.  */
211               if (i != 12)
212                 {
213                   wgint size;
214
215                   /* Parse the previous token with str_to_wgint.  */
216                   if (ptok == line)
217                     {
218                       /* Something has gone wrong during parsing. */
219                       error = 1;
220                       break;
221                     }
222                   errno = 0;
223                   size = str_to_wgint (ptok, NULL, 10);
224                   if (size == WGINT_MAX && errno == ERANGE)
225                     /* Out of range -- ignore the size.  #### Should
226                        we refuse to start the download.  */
227                     cur.size = 0;
228                   else
229                     cur.size = size;
230                   DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
231
232                   month = i;
233                   next = 5;
234                   DEBUGP (("month: %s; ", months[month]));
235                 }
236             }
237           else if (next == 4)   /* days */
238             {
239               if (tok[1])       /* two-digit... */
240                 day = 10 * (*tok - '0') + tok[1] - '0';
241               else              /* ...or one-digit */
242                 day = *tok - '0';
243               DEBUGP (("day: %d; ", day));
244             }
245           else if (next == 3)
246             {
247               /* This ought to be either the time, or the year.  Let's
248                  be flexible!
249
250                  If we have a number x, it's a year.  If we have x:y,
251                  it's hours and minutes.  If we have x:y:z, z are
252                  seconds.  */
253               year = 0;
254               min = hour = sec = 0;
255               /* We must deal with digits.  */
256               if (ISDIGIT (*tok))
257                 {
258                   /* Suppose it's year.  */
259                   for (; ISDIGIT (*tok); tok++)
260                     year = (*tok - '0') + 10 * year;
261                   if (*tok == ':')
262                     {
263                       /* This means these were hours!  */
264                       hour = year;
265                       year = 0;
266                       ++tok;
267                       /* Get the minutes...  */
268                       for (; ISDIGIT (*tok); tok++)
269                         min = (*tok - '0') + 10 * min;
270                       if (*tok == ':')
271                         {
272                           /* ...and the seconds.  */
273                           ++tok;
274                           for (; ISDIGIT (*tok); tok++)
275                             sec = (*tok - '0') + 10 * sec;
276                         }
277                     }
278                 }
279               if (year)
280                 DEBUGP (("year: %d (no tm); ", year));
281               else
282                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
283             }
284           else if (next == 2)    /* The file name */
285             {
286               int fnlen;
287               char *p;
288
289               /* Since the file name may contain a SPC, it is possible
290                  for strtok to handle it wrong.  */
291               fnlen = strlen (tok);
292               if (fnlen < len - (tok - line))
293                 {
294                   /* So we have a SPC in the file name.  Restore the
295                      original.  */
296                   tok[fnlen] = ' ';
297                   /* If the file is a symbolic link, it should have a
298                      ` -> ' somewhere.  */
299                   if (cur.type == FT_SYMLINK)
300                     {
301                       p = strstr (tok, " -> ");
302                       if (!p)
303                         {
304                           error = 1;
305                           break;
306                         }
307                       cur.linkto = xstrdup (p + 4);
308                       DEBUGP (("link to: %s\n", cur.linkto));
309                       /* And separate it from the file name.  */
310                       *p = '\0';
311                     }
312                 }
313               /* If we have the filename, add it to the list of files or
314                  directories.  */
315               /* "." and ".." are an exception!  */
316               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
317                 {
318                   DEBUGP (("\nIgnoring `.' and `..'; "));
319                   ignore = 1;
320                   break;
321                 }
322               /* Some FTP sites choose to have ls -F as their default
323                  LIST output, which marks the symlinks with a trailing
324                  `@', directory names with a trailing `/' and
325                  executables with a trailing `*'.  This is no problem
326                  unless encountering a symbolic link ending with `@',
327                  or an executable ending with `*' on a server without
328                  default -F output.  I believe these cases are very
329                  rare.  */
330               fnlen = strlen (tok); /* re-calculate `fnlen' */
331               cur.name = xmalloc (fnlen + 1);
332               memcpy (cur.name, tok, fnlen + 1);
333               if (fnlen)
334                 {
335                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
336                     {
337                       cur.name[fnlen - 1] = '\0';
338                       DEBUGP (("trailing `/' on dir.\n"));
339                     }
340                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
341                     {
342                       cur.name[fnlen - 1] = '\0';
343                       DEBUGP (("trailing `@' on link.\n"));
344                     }
345                   else if (cur.type == FT_PLAINFILE
346                            && (cur.perms & 0111)
347                            && cur.name[fnlen - 1] == '*')
348                     {
349                       cur.name[fnlen - 1] = '\0';
350                       DEBUGP (("trailing `*' on exec.\n"));
351                     }
352                 } /* if (fnlen) */
353               else
354                 error = 1;
355               break;
356             }
357           else
358             abort ();
359         } /* while */
360
361       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
362         error = 1;
363
364       DEBUGP (("%s\n", cur.name ? cur.name : ""));
365
366       if (error || ignore)
367         {
368           DEBUGP (("Skipping.\n"));
369           xfree_null (cur.name);
370           xfree_null (cur.linkto);
371           xfree (line);
372           continue;
373         }
374
375       if (!dir)
376         {
377           l = dir = xnew (struct fileinfo);
378           memcpy (l, &cur, sizeof (cur));
379           l->prev = l->next = NULL;
380         }
381       else
382         {
383           cur.prev = l;
384           l->next = xnew (struct fileinfo);
385           l = l->next;
386           memcpy (l, &cur, sizeof (cur));
387           l->next = NULL;
388         }
389       /* Get the current time.  */
390       timenow = time (NULL);
391       tnow = localtime (&timenow);
392       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
393       timestruct.tm_sec   = sec;
394       timestruct.tm_min   = min;
395       timestruct.tm_hour  = hour;
396       timestruct.tm_mday  = day;
397       timestruct.tm_mon   = month;
398       if (year == 0)
399         {
400           /* Some listings will not specify the year if it is "obvious"
401              that the file was from the previous year.  E.g. if today
402              is 97-01-12, and you see a file of Dec 15th, its year is
403              1996, not 1997.  Thanks to Vladimir Volovich for
404              mentioning this!  */
405           if (month > tnow->tm_mon)
406             timestruct.tm_year = tnow->tm_year - 1;
407           else
408             timestruct.tm_year = tnow->tm_year;
409         }
410       else
411         timestruct.tm_year = year;
412       if (timestruct.tm_year >= 1900)
413         timestruct.tm_year -= 1900;
414       timestruct.tm_wday  = 0;
415       timestruct.tm_yday  = 0;
416       timestruct.tm_isdst = -1;
417       l->tstamp = mktime (&timestruct); /* store the time-stamp */
418
419       xfree (line);
420     }
421
422   fclose (fp);
423   return dir;
424 }
425
426 static struct fileinfo *
427 ftp_parse_winnt_ls (const char *file)
428 {
429   FILE *fp;
430   int len;
431   int year, month, day;         /* for time analysis */
432   int hour, min;
433   struct tm timestruct;
434
435   char *line, *tok;             /* tokenizer */
436   struct fileinfo *dir, *l, cur; /* list creation */
437
438   fp = fopen (file, "rb");
439   if (!fp)
440     {
441       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
442       return NULL;
443     }
444   dir = l = NULL;
445
446   /* Line loop to end of file: */
447   while ((line = read_whole_line (fp)) != NULL)
448     {
449       len = clean_line (line);
450
451       /* Extracting name is a bit of black magic and we have to do it
452          before `strtok' inserted extra \0 characters in the line
453          string. For the moment let us just suppose that the name starts at
454          column 39 of the listing. This way we could also recognize
455          filenames that begin with a series of space characters (but who
456          really wants to use such filenames anyway?). */
457       if (len < 40) continue;
458       tok = line + 39;
459       cur.name = xstrdup(tok);
460       DEBUGP(("Name: '%s'\n", cur.name));
461
462       /* First column: mm-dd-yy. Should atoi() on the month fail, january
463          will be assumed.  */
464       tok = strtok(line, "-");
465       if (tok == NULL) continue;
466       month = atoi(tok) - 1;
467       if (month < 0) month = 0;
468       tok = strtok(NULL, "-");
469       if (tok == NULL) continue;
470       day = atoi(tok);
471       tok = strtok(NULL, " ");
472       if (tok == NULL) continue;
473       year = atoi(tok);
474       /* Assuming the epoch starting at 1.1.1970 */
475       if (year <= 70) year += 100;
476
477       /* Second column: hh:mm[AP]M, listing does not contain value for
478          seconds */
479       tok = strtok(NULL,  ":");
480       if (tok == NULL) continue;
481       hour = atoi(tok);
482       tok = strtok(NULL,  "M");
483       if (tok == NULL) continue;
484       min = atoi(tok);
485       /* Adjust hour from AM/PM. Just for the record, the sequence goes
486          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
487       tok+=2;
488       if (hour == 12)  hour  = 0;
489       if (*tok == 'P') hour += 12;
490
491       DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n", 
492               year+1900, month, day, hour, min));
493       
494       /* Build the time-stamp (copy & paste from above) */
495       timestruct.tm_sec   = 0;
496       timestruct.tm_min   = min;
497       timestruct.tm_hour  = hour;
498       timestruct.tm_mday  = day;
499       timestruct.tm_mon   = month;
500       timestruct.tm_year  = year;
501       timestruct.tm_wday  = 0;
502       timestruct.tm_yday  = 0;
503       timestruct.tm_isdst = -1;
504       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
505
506       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
507
508       /* Third column: Either file length, or <DIR>. We also set the
509          permissions (guessed as 0644 for plain files and 0755 for
510          directories as the listing does not give us a clue) and filetype
511          here. */
512       tok = strtok(NULL, " ");
513       if (tok == NULL) continue;
514       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
515       if (tok == NULL) continue;
516       if (*tok == '<')
517         {
518           cur.type  = FT_DIRECTORY;
519           cur.size  = 0;
520           cur.perms = 0755;
521           DEBUGP(("Directory\n"));
522         }
523       else
524         {
525           wgint size;
526           cur.type  = FT_PLAINFILE;
527           errno = 0;
528           size = str_to_wgint (tok, NULL, 10);
529           if (size == WGINT_MAX && errno == ERANGE)
530             cur.size = 0;       /* overflow */
531           else
532             cur.size = size;
533           cur.perms = 0644;
534           DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
535         }
536
537       cur.linkto = NULL;
538
539       /* And put everything into the linked list */
540       if (!dir)
541         {
542           l = dir = xnew (struct fileinfo);
543           memcpy (l, &cur, sizeof (cur));
544           l->prev = l->next = NULL;
545         }
546       else
547         {
548           cur.prev = l;
549           l->next = xnew (struct fileinfo);
550           l = l->next;
551           memcpy (l, &cur, sizeof (cur));
552           l->next = NULL;
553         }
554
555       xfree (line);
556     }
557
558   fclose(fp);
559   return dir;
560 }
561
562 /* Converts VMS symbolic permissions to number-style ones, e.g. string
563    RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
564    (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
565 static int
566 vmsperms (const char *s)
567 {
568   int perms = 0;
569
570   do
571     {
572       switch (*s) {
573         case ',': perms <<= 3; break;
574         case 'R': perms  |= 4; break;
575         case 'W': perms  |= 2; break;
576         case 'D': perms  |= 2; break;
577         case 'E': perms  |= 1; break;
578         default:  DEBUGP(("wrong VMS permissons!\n")); 
579       }
580     }
581   while (*++s);
582   return perms;
583 }
584
585
586 static struct fileinfo *
587 ftp_parse_vms_ls (const char *file)
588 {
589   FILE *fp;
590   /* #### A third copy of more-or-less the same array ? */
591   static const char *months[] = {
592     "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
593     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
594   };
595   int i;
596   int year, month, day;          /* for time analysis */
597   int hour, min, sec;
598   struct tm timestruct;
599
600   char *line, *tok;              /* tokenizer */
601   struct fileinfo *dir, *l, cur; /* list creation */
602
603   fp = fopen (file, "rb");
604   if (!fp)
605     {
606       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
607       return NULL;
608     }
609   dir = l = NULL;
610
611   /* Skip empty line. */
612   line = read_whole_line (fp);
613   xfree_null (line);
614
615   /* Skip "Directory PUB$DEVICE[PUB]" */
616   line = read_whole_line (fp);
617   xfree_null (line);
618
619   /* Skip empty line. */
620   line = read_whole_line (fp);
621   xfree_null (line);
622
623   /* Line loop to end of file: */
624   while ((line = read_whole_line (fp)) != NULL)
625     {
626       char *p;
627       i = clean_line (line);
628       if (!i)
629         {
630           xfree (line);
631           break;
632         }
633
634       /* First column: Name. A bit of black magic again. The name my be
635          either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
636          line. Therefore we will first try to get the complete name
637          until the first space character; if it fails, we assume that the name
638          occupies the whole line. After that we search for the version
639          separator ";", we remove it and check the extension of the file;
640          extension .DIR denotes directory. */
641
642       tok = strtok(line, " ");
643       if (tok == NULL) tok = line;
644       DEBUGP(("file name: '%s'\n", tok));
645       for (p = tok ; *p && *p != ';' ; p++)
646         ;
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         ;
730       if (*p)
731         min = atoi (++p);
732       for (; *p && *p != ':'; ++p)
733         ;
734       if (*p)
735         sec = atoi (++p);
736
737       DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n", 
738               year+1900, month, day, hour, min, sec));
739       
740       /* Build the time-stamp (copy & paste from above) */
741       timestruct.tm_sec   = sec;
742       timestruct.tm_min   = min;
743       timestruct.tm_hour  = hour;
744       timestruct.tm_mday  = day;
745       timestruct.tm_mon   = month;
746       timestruct.tm_year  = year;
747       timestruct.tm_wday  = 0;
748       timestruct.tm_yday  = 0;
749       timestruct.tm_isdst = -1;
750       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
751
752       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
753
754       /* Skip the fifth column */
755
756       tok = strtok(NULL, " ");
757       if (tok == NULL) continue;
758
759       /* Sixth column: Permissions */
760
761       tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
762       if (tok == NULL) continue;
763       tok = strtok(NULL, ")");
764       if (tok == NULL)
765         {
766           DEBUGP(("confusing VMS permissions, skipping line\n"));
767           xfree (line);
768           continue;
769         }
770       /* Permissons have the format "RWED,RWED,RE" */
771       cur.perms = vmsperms(tok);
772       DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
773
774       cur.linkto = NULL;
775
776       /* And put everything into the linked list */
777       if (!dir)
778         {
779           l = dir = xnew (struct fileinfo);
780           memcpy (l, &cur, sizeof (cur));
781           l->prev = l->next = NULL;
782         }
783       else
784         {
785           cur.prev = l;
786           l->next = xnew (struct fileinfo);
787           l = l->next;
788           memcpy (l, &cur, sizeof (cur));
789           l->next = NULL;
790         }
791
792       xfree (line);
793     }
794
795   fclose (fp);
796   return dir;
797 }
798
799
800 /* This function switches between the correct parsing routine depending on
801    the SYSTEM_TYPE. The system type should be based on the result of the
802    "SYST" response of the FTP server. According to this repsonse we will
803    use on of the three different listing parsers that cover the most of FTP
804    servers used nowadays.  */
805
806 struct fileinfo *
807 ftp_parse_ls (const char *file, const enum stype system_type)
808 {
809   switch (system_type)
810     {
811     case ST_UNIX:
812       return ftp_parse_unix_ls (file, 0);
813     case ST_WINNT:
814       {
815         /* Detect whether the listing is simulating the UNIX format */
816         FILE *fp;
817         int   c;
818         fp = fopen (file, "rb");
819         if (!fp)
820         {
821           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
822           return NULL;
823         }
824         c = fgetc(fp);
825         fclose(fp);
826         /* If the first character of the file is '0'-'9', it's WINNT
827            format. */
828         if (c >= '0' && c <='9')
829           return ftp_parse_winnt_ls (file);
830         else
831           return ftp_parse_unix_ls (file, 1);
832       }
833     case ST_VMS:
834       return ftp_parse_vms_ls (file);
835     case ST_MACOS:
836       return ftp_parse_unix_ls (file, 1);
837     default:
838       logprintf (LOG_NOTQUIET, _("\
839 Unsupported listing type, trying Unix listing parser.\n"));
840       return ftp_parse_unix_ls (file, 0);
841     }
842 }
843 \f
844 /* Stuff for creating FTP index. */
845
846 /* The function creates an HTML index containing references to given
847    directories and files on the appropriate host.  The references are
848    FTP.  */
849 uerr_t
850 ftp_index (const char *file, struct url *u, struct fileinfo *f)
851 {
852   FILE *fp;
853   char *upwd;
854   char *htclfile;               /* HTML-clean file name */
855
856   if (!output_stream)
857     {
858       fp = fopen (file, "wb");
859       if (!fp)
860         {
861           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
862           return FOPENERR;
863         }
864     }
865   else
866     fp = output_stream;
867   if (u->user)
868     {
869       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
870
871       tmpu = url_escape (u->user);
872       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
873       if (tmpp)
874         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
875       else
876         upwd = concat_strings (tmpu, "@", (char *) 0);
877       xfree (tmpu);
878       xfree_null (tmpp);
879     }
880   else
881     upwd = xstrdup ("");
882   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
883   fprintf (fp, "<html>\n<head>\n<title>");
884   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
885   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
886   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
887   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
888   while (f)
889     {
890       fprintf (fp, "  ");
891       if (f->tstamp != -1)
892         {
893           /* #### Should we translate the months?  Or, even better, use
894              ISO 8601 dates?  */
895           static const char *months[] = {
896             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
897             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
898           };
899           struct tm *ptm = localtime ((time_t *)&f->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 }