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