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