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