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