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