]> sjero.net Git - wget/blob - src/ftp-ls.c
[svn] Removed ftpparse dependencies. New parser for VMS listings. MacOS
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1995, 1996, 1997, 2000, 2001 Free Software Foundation,
3    Inc. 
4
5 This file is part of Wget.
6
7 This program 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 This program 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 this program; 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 <ctype.h>
35 #include <errno.h>
36
37 #include "wget.h"
38 #include "utils.h"
39 #include "ftp.h"
40 #include "url.h"
41
42 /* Converts symbolic permissions to number-style ones, e.g. string
43    rwxr-xr-x to 755.  For now, it knows nothing of
44    setuid/setgid/sticky.  ACLs are ignored.  */
45 static int
46 symperms (const char *s)
47 {
48   int perms = 0, i;
49
50   if (strlen (s) < 9)
51     return 0;
52   for (i = 0; i < 3; i++, s += 3)
53     {
54       perms <<= 3;
55       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
56                 (s[2] == 'x' || s[2] == 's'));
57     }
58   return perms;
59 }
60
61
62 /* Cleans a line of text so that it can be consistently parsed. Destroys
63    <CR> and <LF> in case that thay occur at the end of the line and
64    replaces all <TAB> character with <SPACE>. Returns the length of the
65    modified line. */
66 static int
67 clean_line(char *line)
68 {
69   int len = strlen (line);
70   if (!len) return 0; 
71   if (line[len - 1] == '\n')
72     line[--len] = '\0';
73   if (line[len - 1] == '\r')
74     line[--len] = '\0';
75   for ( ; *line ; line++ ) if (*line == '\t') *line = ' '; 
76   return len;
77 }
78
79 /* Convert the Un*x-ish style directory listing stored in FILE to a
80    linked list of fileinfo (system-independent) entries.  The contents
81    of FILE are considered to be produced by the standard Unix `ls -la'
82    output (whatever that might be).  BSD (no group) and SYSV (with
83    group) listings are handled.
84
85    The time stamps are stored in a separate variable, time_t
86    compatible (I hope).  The timezones are ignored.  */
87 static struct fileinfo *
88 ftp_parse_unix_ls (const char *file, int ignore_perms)
89 {
90   FILE *fp;
91   static const char *months[] = {
92     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
93     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
94   };
95   int next, len, i, error, ignore;
96   int year, month, day;         /* for time analysis */
97   int hour, min, sec;
98   struct tm timestruct, *tnow;
99   time_t timenow;
100
101   char *line, *tok;             /* tokenizer */
102   struct fileinfo *dir, *l, cur; /* list creation */
103
104   fp = fopen (file, "rb");
105   if (!fp)
106     {
107       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
108       return NULL;
109     }
110   dir = l = NULL;
111
112   /* Line loop to end of file: */
113   while ((line = read_whole_line (fp)))
114     {
115       len = clean_line (line);
116       /* Skip if total...  */
117       if (!strncasecmp (line, "total", 5))
118         {
119           xfree (line);
120           continue;
121         }
122       /* Get the first token (permissions).  */
123       tok = strtok (line, " ");
124       if (!tok)
125         {
126           xfree (line);
127           continue;
128         }
129
130       cur.name = NULL;
131       cur.linkto = NULL;
132
133       /* Decide whether we deal with a file or a directory.  */
134       switch (*tok)
135         {
136         case '-':
137           cur.type = FT_PLAINFILE;
138           DEBUGP (("PLAINFILE; "));
139           break;
140         case 'd':
141           cur.type = FT_DIRECTORY;
142           DEBUGP (("DIRECTORY; "));
143           break;
144         case 'l':
145           cur.type = FT_SYMLINK;
146           DEBUGP (("SYMLINK; "));
147           break;
148         default:
149           cur.type = FT_UNKNOWN;
150           DEBUGP (("UNKOWN; "));
151           break;
152         }
153
154       if (ignore_perms)
155         {
156           switch (cur.type)
157             {
158             case FT_PLAINFILE:
159               cur.perms = 420;
160               break;
161             case FT_DIRECTORY:
162               cur.perms = 493;
163               break;
164             default:
165               cur.perms = 1023;
166             }
167           DEBUGP (("implicite 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, sec;
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 */
445       tok = strtok(line, "-");
446       month = atoi(tok);
447       tok = strtok(NULL, "-");
448       day = atoi(tok);
449       tok = strtok(NULL, " ");
450       year = atoi(tok);
451       /* Assuming the epoch starting at 1.1.1970 */
452       if (year <= 70) year += 100;
453
454       /* Second column: hh:mm[AP]M */
455       tok = strtok(NULL,  ":");
456       hour = atoi(tok);
457       tok = strtok(NULL,  "M");
458       min = atoi(tok);
459       /* Adjust hour from AM/PM */
460       tok+=2;
461       if (*tok == 'P') hour += 12;
462       /* Listing does not contain value for seconds */
463       sec = 0;
464
465       DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n", 
466               year+1900, month, day, hour, min));
467       
468       /* Build the time-stamp (copy & paste from above) */
469       timestruct.tm_sec   = sec;
470       timestruct.tm_min   = min;
471       timestruct.tm_hour  = hour;
472       timestruct.tm_mday  = day;
473       timestruct.tm_mon   = month;
474       timestruct.tm_year  = year;
475       timestruct.tm_wday  = 0;
476       timestruct.tm_yday  = 0;
477       timestruct.tm_isdst = -1;
478       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
479
480       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
481
482       /* Third column: Either file length, or <DIR>. We also set the
483          permissions (guessed as 0644 for plain files and 0755 for
484          directories as the listing does not give us a clue) and filetype
485          here. */
486       tok = strtok(NULL, " ");
487       while (*tok == '\0')  tok = strtok(NULL, " ");
488       if (*tok == '<')
489         {
490           cur.type  = FT_DIRECTORY;
491           cur.size  = 0;
492           cur.perms = 493; /* my gcc does not like 0755 ?? */
493           DEBUGP(("Directory\n"));
494         }
495       else
496         {
497           cur.type  = FT_PLAINFILE;
498           cur.size  = atoi(tok);
499           cur.perms = 420; /* 0664 octal */
500           DEBUGP(("File, size %ld bytes\n", cur.size));
501         }
502
503       cur.linkto = NULL;
504
505       /* And put everything into the linked list */
506       if (!dir)
507         {
508           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
509           memcpy (l, &cur, sizeof (cur));
510           l->prev = l->next = NULL;
511         }
512       else
513         {
514           cur.prev = l;
515           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
516           l = l->next;
517           memcpy (l, &cur, sizeof (cur));
518           l->next = NULL;
519         }
520
521       xfree(line);
522     }
523
524   fclose(fp);
525   return dir;
526 }
527
528 /* Converts VMS symbolic permissions to number-style ones, e.g. string
529    RWED,RWE,RE to 755. "D" (delete) is taken to be equal to "W"
530    (write). Inspired by a patch of Stoyan Lekov <lekov@eda.bg>. */
531 static int
532 vmsperms (const char *s)
533 {
534   int perms = 0;
535
536   do
537     {
538       switch (*s) {
539         case ',': perms <<= 3; break;
540         case 'R': perms  |= 4; break;
541         case 'W': perms  |= 2; break;
542         case 'D': perms  |= 2; break;
543         case 'E': perms  |= 1; break;
544         default:  DEBUGP(("wrong VMS permissons!\n")); 
545       }
546     }
547   while (*++s);
548   return perms;
549 }
550
551
552 static struct fileinfo *
553 ftp_parse_vms_ls (const char *file)
554 {
555   FILE *fp;
556   /* #### A third copy of more-or-less the same array ? */
557   static const char *months[] = {
558     "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
559     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
560   };
561   int i;
562   int year, month, day;          /* for time analysis */
563   int hour, min, sec;
564   struct tm timestruct;
565
566   char *line, *tok, *p;          /* tokenizer */
567   struct fileinfo *dir, *l, cur; /* list creation */
568
569   fp = fopen (file, "rb");
570   if (!fp)
571     {
572       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
573       return NULL;
574     }
575   dir = l = NULL;
576
577   /* Empty line */
578   read_whole_line (fp);
579   /* "Directory PUB$DEVICE[PUB]" */
580   read_whole_line (fp);
581   /* Empty line */
582   read_whole_line (fp);
583
584   /* Line loop to end of file: */
585   while ((line = read_whole_line (fp)))
586     {
587       i = clean_line (line);
588       if (!i) break;
589
590       /* First column: Name. A bit of black magic again. The name my be
591          either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
592          line. Therefore we will first try to get the complete name
593          until the first space character; if it fails, we assume that the name
594          occupies the whole line. After that we search for the version
595          separator ";", we remove it and check the extension of the file;
596          extension .DIR denotes directory. */
597
598       tok = strtok(line, " ");
599       if (tok == NULL) tok = line;
600       DEBUGP(("file name: '%s'\n", tok));
601       for (p = tok ; *p && *p != ';' ; p++);
602       if (*p == ';') *p = '\0';
603       p   = tok + strlen(tok) - 4;
604       if (!strcmp(p, ".DIR")) *p = '\0';
605       cur.name = xstrdup(tok);
606       DEBUGP(("Name: '%s'\n", cur.name));
607
608       /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
609          the file size to zero as the listing does tell us only the size in
610          filesystem blocks - for an integrity check (when mirroring, for
611          example) we would need the size in bytes. */
612       
613       if (! *p)
614         {
615           cur.type  = FT_DIRECTORY;
616           cur.size  = 0;
617           DEBUGP(("Directory\n"));
618         }
619       else
620         {
621           cur.type  = FT_PLAINFILE;
622           DEBUGP(("File\n"));
623         }
624
625       cur.size  = 0;
626
627       /* Second column, if exists, or the first column of the next line
628          contain file size in blocks. We will skip it. */
629
630       tok = strtok(NULL, " ");
631       if (tok == NULL) 
632       {
633         DEBUGP(("Getting additional line\n"));
634         xfree (line);
635         line = read_whole_line (fp);
636         if (!line)
637         {
638           DEBUGP(("empty line read, leaving listing parser\n"));
639           break;
640         }
641         i = clean_line (line);
642         if (!i) 
643         {
644           DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
645           break;
646         }
647         tok = strtok(line, " ");
648       }
649       DEBUGP(("second token: '%s'\n", tok));
650
651       /* Third/Second column: Date DD-MMM-YYYY. */
652
653       tok = strtok(NULL, "-");
654       DEBUGP(("day: '%s'\n",tok));
655       day = atoi(tok);
656       tok = strtok(NULL, "-");
657       if (!tok)
658       {
659         /* If the server produces garbage like
660            'EA95_0PS.GZ;1      No privilege for attempted operation'
661            the first strtok(NULL, "-") will return everything until the end
662            of the line and only the next strtok() call will return NULL. */
663         DEBUGP(("nonsense in VMS listing, skipping this line\n"));
664         break;
665       }
666       for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
667       /* Uknown months are mapped to January */
668       month = (i%12)+1; 
669       tok = strtok(NULL, " ");
670       year = atoi(tok)-1900;
671       DEBUGP(("date parsed\n"));
672
673       /* Fourth/Third column: Time hh:mm:ss */
674       tok = strtok(NULL,  ":");
675       hour = atoi(tok);
676       tok = strtok(NULL,  ":");
677       min = atoi(tok);
678       tok = strtok(NULL,  " ");
679       sec = atoi(tok);
680
681       DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n", 
682               year+1900, month, day, hour, min, sec));
683       
684       /* Build the time-stamp (copy & paste from above) */
685       timestruct.tm_sec   = sec;
686       timestruct.tm_min   = min;
687       timestruct.tm_hour  = hour;
688       timestruct.tm_mday  = day;
689       timestruct.tm_mon   = month;
690       timestruct.tm_year  = year;
691       timestruct.tm_wday  = 0;
692       timestruct.tm_yday  = 0;
693       timestruct.tm_isdst = -1;
694       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
695
696       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
697
698       /* Skip the fifth column */
699
700       tok = strtok(NULL, " ");
701
702       /* Sixth column: Permissions */
703
704       tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
705       tok = strtok(NULL, ")");
706       if (tok == NULL)
707         {
708           DEBUGP(("confusing VMS permissions, skipping line\n"));
709           continue;
710         }
711       /* Permissons have the format "RWED,RWED,RE" */
712       cur.perms = vmsperms(tok);
713       DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
714
715       cur.linkto = NULL;
716
717       /* And put everything into the linked list */
718       if (!dir)
719         {
720           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
721           memcpy (l, &cur, sizeof (cur));
722           l->prev = l->next = NULL;
723         }
724       else
725         {
726           cur.prev = l;
727           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
728           l = l->next;
729           memcpy (l, &cur, sizeof (cur));
730           l->next = NULL;
731         }
732
733       xfree (line);
734     }
735
736   fclose (fp);
737   return dir;
738 }
739
740
741 /* This function switches between the correct parsing routine depending on
742    the SYSTEM_TYPE. The system type should be based on the result of the
743    "SYST" response of the FTP server. According to this repsonse we will
744    use on of the three different listing parsers that cover the most of FTP
745    servers used nowadays.  */
746
747 struct fileinfo *
748 ftp_parse_ls (const char *file, const enum stype system_type)
749 {
750   switch (system_type)
751     {
752     case ST_UNIX:
753       return ftp_parse_unix_ls (file, FALSE);
754     case ST_WINNT:
755       {
756         /* Detect whether the listing is simulating the UNIX format */
757         FILE *fp;
758         int   c;
759         fp = fopen (file, "rb");
760         if (!fp)
761         {
762           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
763           return NULL;
764         }
765         c = fgetc(fp);
766         fclose(fp);
767         /* If the first character of the file is '0'-'9', it's WINNT
768            format. */
769         if (c >= '0' && c <='9')
770           return ftp_parse_winnt_ls (file);
771         else
772           return ftp_parse_unix_ls (file, TRUE);
773       }
774     case ST_VMS:
775       return ftp_parse_vms_ls (file);
776     case ST_MACOS:
777       return ftp_parse_unix_ls (file, TRUE);
778     default:
779       logprintf (LOG_NOTQUIET, _("\
780 Usupported listing type, trying Unix listing parser.\n"));
781       return ftp_parse_unix_ls (file, FALSE);
782     }
783 }
784 \f
785 /* Stuff for creating FTP index. */
786
787 /* The function creates an HTML index containing references to given
788    directories and files on the appropriate host.  The references are
789    FTP.  */
790 uerr_t
791 ftp_index (const char *file, struct urlinfo *u, struct fileinfo *f)
792 {
793   FILE *fp;
794   char *upwd;
795   char *htclfile;               /* HTML-clean file name */
796
797   if (!opt.dfp)
798     {
799       fp = fopen (file, "wb");
800       if (!fp)
801         {
802           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
803           return FOPENERR;
804         }
805     }
806   else
807     fp = opt.dfp;
808   if (u->user)
809     {
810       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
811
812       tmpu = CLEANDUP (u->user);
813       tmpp = u->passwd ? CLEANDUP (u->passwd) : NULL;
814       upwd = (char *)xmalloc (strlen (tmpu)
815                              + (tmpp ? (1 + strlen (tmpp)) : 0) + 2);
816       sprintf (upwd, "%s%s%s@", tmpu, tmpp ? ":" : "", tmpp ? tmpp : "");
817       xfree (tmpu);
818       FREE_MAYBE (tmpp);
819     }
820   else
821     upwd = xstrdup ("");
822   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
823   fprintf (fp, "<html>\n<head>\n<title>");
824   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
825   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
826   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
827   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
828   while (f)
829     {
830       fprintf (fp, "  ");
831       if (f->tstamp != -1)
832         {
833           /* #### Should we translate the months? */
834           static char *months[] = {
835             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
836             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
837           };
838           struct tm *ptm = localtime ((time_t *)&f->tstamp);
839
840           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
841                   ptm->tm_mday);
842           if (ptm->tm_hour)
843             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
844           else
845             fprintf (fp, "       ");
846         }
847       else
848         fprintf (fp, _("time unknown       "));
849       switch (f->type)
850         {
851         case FT_PLAINFILE:
852           fprintf (fp, _("File        "));
853           break;
854         case FT_DIRECTORY:
855           fprintf (fp, _("Directory   "));
856           break;
857         case FT_SYMLINK:
858           fprintf (fp, _("Link        "));
859           break;
860         default:
861           fprintf (fp, _("Not sure    "));
862           break;
863         }
864       htclfile = html_quote_string (f->name);
865       fprintf (fp, "<a href=\"ftp://%s%s:%hu", upwd, u->host, u->port);
866       if (*u->dir != '/')
867         putc ('/', fp);
868       fprintf (fp, "%s", u->dir);
869       if (*u->dir)
870         putc ('/', fp);
871       fprintf (fp, "%s", htclfile);
872       if (f->type == FT_DIRECTORY)
873         putc ('/', fp);
874       fprintf (fp, "\">%s", htclfile);
875       if (f->type == FT_DIRECTORY)
876         putc ('/', fp);
877       fprintf (fp, "</a> ");
878       if (f->type == FT_PLAINFILE)
879         fprintf (fp, _(" (%s bytes)"), legible (f->size));
880       else if (f->type == FT_SYMLINK)
881         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
882       putc ('\n', fp);
883       xfree (htclfile);
884       f = f->next;
885     }
886   fprintf (fp, "</pre>\n</body>\n</html>\n");
887   xfree (upwd);
888   if (!opt.dfp)
889     fclose (fp);
890   else
891     fflush (fp);
892   return FTPOK;
893 }