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