]> sjero.net Git - wget/blob - src/ftp-ls.c
[svn] Make seconds optional in VMS time specification.
[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 <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 (("UNKOWN; "));
150           break;
151         }
152
153       if (ignore_perms)
154         {
155           switch (cur.type)
156             {
157             case FT_PLAINFILE:
158               cur.perms = 420;
159               break;
160             case FT_DIRECTORY:
161               cur.perms = 493;
162               break;
163             default:
164               cur.perms = 1023;
165             }
166           DEBUGP (("implicite perms %0o; ", cur.perms));
167         }
168        else
169          {
170            cur.perms = symperms (tok + 1);
171            DEBUGP (("perms %0o; ", cur.perms));
172          }
173
174       error = ignore = 0;       /* Erroneous and ignoring entries are
175                                    treated equally for now.  */
176       year = hour = min = sec = 0; /* Silence the compiler.  */
177       month = day = 0;
178       next = -1;
179       /* While there are tokens on the line, parse them.  Next is the
180          number of tokens left until the filename.
181
182          Use the month-name token as the "anchor" (the place where the
183          position wrt the file name is "known").  When a month name is
184          encountered, `next' is set to 5.  Also, the preceding
185          characters are parsed to get the file size.
186
187          This tactic is quite dubious when it comes to
188          internationalization issues (non-English month names), but it
189          works for now.  */
190       while ((tok = strtok (NULL, " ")))
191         {
192           --next;
193           if (next < 0)         /* a month name was not encountered */
194             {
195               for (i = 0; i < 12; i++)
196                 if (!strcmp (tok, months[i]))
197                   break;
198               /* If we got a month, it means the token before it is the
199                  size, and the filename is three tokens away.  */
200               if (i != 12)
201                 {
202                   char *t = tok - 2;
203                   long mul = 1;
204
205                   for (cur.size = 0; t > line && ISDIGIT (*t); mul *= 10, t--)
206                     cur.size += mul * (*t - '0');
207                   if (t == line)
208                     {
209                       /* Something is seriously wrong.  */
210                       error = 1;
211                       break;
212                     }
213                   month = i;
214                   next = 5;
215                   DEBUGP (("month: %s; ", months[month]));
216                 }
217             }
218           else if (next == 4)   /* days */
219             {
220               if (tok[1])       /* two-digit... */
221                 day = 10 * (*tok - '0') + tok[1] - '0';
222               else              /* ...or one-digit */
223                 day = *tok - '0';
224               DEBUGP (("day: %d; ", day));
225             }
226           else if (next == 3)
227             {
228               /* This ought to be either the time, or the year.  Let's
229                  be flexible!
230
231                  If we have a number x, it's a year.  If we have x:y,
232                  it's hours and minutes.  If we have x:y:z, z are
233                  seconds.  */
234               year = 0;
235               min = hour = sec = 0;
236               /* We must deal with digits.  */
237               if (ISDIGIT (*tok))
238                 {
239                   /* Suppose it's year.  */
240                   for (; ISDIGIT (*tok); tok++)
241                     year = (*tok - '0') + 10 * year;
242                   if (*tok == ':')
243                     {
244                       /* This means these were hours!  */
245                       hour = year;
246                       year = 0;
247                       ++tok;
248                       /* Get the minutes...  */
249                       for (; ISDIGIT (*tok); tok++)
250                         min = (*tok - '0') + 10 * min;
251                       if (*tok == ':')
252                         {
253                           /* ...and the seconds.  */
254                           ++tok;
255                           for (; ISDIGIT (*tok); tok++)
256                             sec = (*tok - '0') + 10 * sec;
257                         }
258                     }
259                 }
260               if (year)
261                 DEBUGP (("year: %d (no tm); ", year));
262               else
263                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
264             }
265           else if (next == 2)    /* The file name */
266             {
267               int fnlen;
268               char *p;
269
270               /* Since the file name may contain a SPC, it is possible
271                  for strtok to handle it wrong.  */
272               fnlen = strlen (tok);
273               if (fnlen < len - (tok - line))
274                 {
275                   /* So we have a SPC in the file name.  Restore the
276                      original.  */
277                   tok[fnlen] = ' ';
278                   /* If the file is a symbolic link, it should have a
279                      ` -> ' somewhere.  */
280                   if (cur.type == FT_SYMLINK)
281                     {
282                       p = strstr (tok, " -> ");
283                       if (!p)
284                         {
285                           error = 1;
286                           break;
287                         }
288                       cur.linkto = xstrdup (p + 4);
289                       DEBUGP (("link to: %s\n", cur.linkto));
290                       /* And separate it from the file name.  */
291                       *p = '\0';
292                     }
293                 }
294               /* If we have the filename, add it to the list of files or
295                  directories.  */
296               /* "." and ".." are an exception!  */
297               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
298                 {
299                   DEBUGP (("\nIgnoring `.' and `..'; "));
300                   ignore = 1;
301                   break;
302                 }
303               /* Some FTP sites choose to have ls -F as their default
304                  LIST output, which marks the symlinks with a trailing
305                  `@', directory names with a trailing `/' and
306                  executables with a trailing `*'.  This is no problem
307                  unless encountering a symbolic link ending with `@',
308                  or an executable ending with `*' on a server without
309                  default -F output.  I believe these cases are very
310                  rare.  */
311               fnlen = strlen (tok); /* re-calculate `fnlen' */
312               cur.name = (char *)xmalloc (fnlen + 1);
313               memcpy (cur.name, tok, fnlen + 1);
314               if (fnlen)
315                 {
316                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
317                     {
318                       cur.name[fnlen - 1] = '\0';
319                       DEBUGP (("trailing `/' on dir.\n"));
320                     }
321                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
322                     {
323                       cur.name[fnlen - 1] = '\0';
324                       DEBUGP (("trailing `@' on link.\n"));
325                     }
326                   else if (cur.type == FT_PLAINFILE
327                            && (cur.perms & 0111)
328                            && cur.name[fnlen - 1] == '*')
329                     {
330                       cur.name[fnlen - 1] = '\0';
331                       DEBUGP (("trailing `*' on exec.\n"));
332                     }
333                 } /* if (fnlen) */
334               else
335                 error = 1;
336               break;
337             }
338           else
339             abort ();
340         } /* while */
341
342       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
343         error = 1;
344
345       DEBUGP (("\n"));
346
347       if (error || ignore)
348         {
349           DEBUGP (("Skipping.\n"));
350           FREE_MAYBE (cur.name);
351           FREE_MAYBE (cur.linkto);
352           xfree (line);
353           continue;
354         }
355
356       if (!dir)
357         {
358           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
359           memcpy (l, &cur, sizeof (cur));
360           l->prev = l->next = NULL;
361         }
362       else
363         {
364           cur.prev = l;
365           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
366           l = l->next;
367           memcpy (l, &cur, sizeof (cur));
368           l->next = NULL;
369         }
370       /* Get the current time.  */
371       timenow = time (NULL);
372       tnow = localtime (&timenow);
373       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
374       timestruct.tm_sec   = sec;
375       timestruct.tm_min   = min;
376       timestruct.tm_hour  = hour;
377       timestruct.tm_mday  = day;
378       timestruct.tm_mon   = month;
379       if (year == 0)
380         {
381           /* Some listings will not specify the year if it is "obvious"
382              that the file was from the previous year.  E.g. if today
383              is 97-01-12, and you see a file of Dec 15th, its year is
384              1996, not 1997.  Thanks to Vladimir Volovich for
385              mentioning this!  */
386           if (month > tnow->tm_mon)
387             timestruct.tm_year = tnow->tm_year - 1;
388           else
389             timestruct.tm_year = tnow->tm_year;
390         }
391       else
392         timestruct.tm_year = year;
393       if (timestruct.tm_year >= 1900)
394         timestruct.tm_year -= 1900;
395       timestruct.tm_wday  = 0;
396       timestruct.tm_yday  = 0;
397       timestruct.tm_isdst = -1;
398       l->tstamp = mktime (&timestruct); /* store the time-stamp */
399
400       xfree (line);
401     }
402
403   fclose (fp);
404   return dir;
405 }
406
407 static struct fileinfo *
408 ftp_parse_winnt_ls (const char *file)
409 {
410   FILE *fp;
411   int len;
412   int year, month, day;         /* for time analysis */
413   int hour, min;
414   struct tm timestruct;
415
416   char *line, *tok;             /* tokenizer */
417   struct fileinfo *dir, *l, cur; /* list creation */
418
419   fp = fopen (file, "rb");
420   if (!fp)
421     {
422       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
423       return NULL;
424     }
425   dir = l = NULL;
426
427   /* Line loop to end of file: */
428   while ((line = read_whole_line (fp)))
429     {
430       len = clean_line (line);
431
432       /* Extracting name is a bit of black magic and we have to do it
433          before `strtok' inserted extra \0 characters in the line
434          string. For the moment let us just suppose that the name starts at
435          column 39 of the listing. This way we could also recognize
436          filenames that begin with a series of space characters (but who
437          really wants to use such filenames anyway?). */
438       if (len < 40) continue;
439       tok = line + 39;
440       cur.name = xstrdup(tok);
441       DEBUGP(("Name: '%s'\n", cur.name));
442
443       /* First column: mm-dd-yy */
444       tok = strtok(line, "-");
445       month = atoi(tok);
446       tok = strtok(NULL, "-");
447       day = atoi(tok);
448       tok = strtok(NULL, " ");
449       year = atoi(tok);
450       /* Assuming the epoch starting at 1.1.1970 */
451       if (year <= 70) year += 100;
452
453       /* Second column: hh:mm[AP]M, listing does not contain value for
454          seconds */
455       tok = strtok(NULL,  ":");
456       hour = atoi(tok);
457       tok = strtok(NULL,  "M");
458       min = atoi(tok);
459       /* Adjust hour from AM/PM. Just for the record, the sequence goes
460          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
461       tok+=2;
462       if (hour == 12)  hour  = 0;
463       if (*tok == 'P') hour += 12;
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   = 0;
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       char *p;
588       i = clean_line (line);
589       if (!i) break;
590
591       /* First column: Name. A bit of black magic again. The name my be
592          either ABCD.EXT or ABCD.EXT;NUM and it might be on a separate
593          line. Therefore we will first try to get the complete name
594          until the first space character; if it fails, we assume that the name
595          occupies the whole line. After that we search for the version
596          separator ";", we remove it and check the extension of the file;
597          extension .DIR denotes directory. */
598
599       tok = strtok(line, " ");
600       if (tok == NULL) tok = line;
601       DEBUGP(("file name: '%s'\n", tok));
602       for (p = tok ; *p && *p != ';' ; p++);
603       if (*p == ';') *p = '\0';
604       p   = tok + strlen(tok) - 4;
605       if (!strcmp(p, ".DIR")) *p = '\0';
606       cur.name = xstrdup(tok);
607       DEBUGP(("Name: '%s'\n", cur.name));
608
609       /* If the name ends on .DIR or .DIR;#, it's a directory. We also set
610          the file size to zero as the listing does tell us only the size in
611          filesystem blocks - for an integrity check (when mirroring, for
612          example) we would need the size in bytes. */
613       
614       if (! *p)
615         {
616           cur.type  = FT_DIRECTORY;
617           cur.size  = 0;
618           DEBUGP(("Directory\n"));
619         }
620       else
621         {
622           cur.type  = FT_PLAINFILE;
623           DEBUGP(("File\n"));
624         }
625
626       cur.size  = 0;
627
628       /* Second column, if exists, or the first column of the next line
629          contain file size in blocks. We will skip it. */
630
631       tok = strtok(NULL, " ");
632       if (tok == NULL) 
633       {
634         DEBUGP(("Getting additional line\n"));
635         xfree (line);
636         line = read_whole_line (fp);
637         if (!line)
638         {
639           DEBUGP(("empty line read, leaving listing parser\n"));
640           break;
641         }
642         i = clean_line (line);
643         if (!i) 
644         {
645           DEBUGP(("confusing VMS listing item, leaving listing parser\n"));
646           break;
647         }
648         tok = strtok(line, " ");
649       }
650       DEBUGP(("second token: '%s'\n", tok));
651
652       /* Third/Second column: Date DD-MMM-YYYY. */
653
654       tok = strtok(NULL, "-");
655       DEBUGP(("day: '%s'\n",tok));
656       day = atoi(tok);
657       tok = strtok(NULL, "-");
658       if (!tok)
659       {
660         /* If the server produces garbage like
661            'EA95_0PS.GZ;1      No privilege for attempted operation'
662            the first strtok(NULL, "-") will return everything until the end
663            of the line and only the next strtok() call will return NULL. */
664         DEBUGP(("nonsense in VMS listing, skipping this line\n"));
665         break;
666       }
667       for (i=0; i<12; i++) if (!strcmp(tok,months[i])) break;
668       /* Uknown months are mapped to January */
669       month = (i%12)+1; 
670       tok = strtok(NULL, " ");
671       year = atoi(tok)-1900;
672       DEBUGP(("date parsed\n"));
673
674       /* Fourth/Third column: Time hh:mm[:ss] */
675       tok = strtok (NULL, " ");
676       hour = min = sec = 0;
677       p = tok;
678       hour = atoi (p);
679       for (; *p && *p != ':'; ++p);
680       if (*p)
681         min = atoi (++p);
682       for (; *p && *p != ':'; ++p);
683       if (*p)
684         sec = atoi (++p);
685
686       DEBUGP(("YYYY/MM/DD HH:MM:SS - %d/%02d/%02d %02d:%02d:%02d\n", 
687               year+1900, month, day, hour, min, sec));
688       
689       /* Build the time-stamp (copy & paste from above) */
690       timestruct.tm_sec   = sec;
691       timestruct.tm_min   = min;
692       timestruct.tm_hour  = hour;
693       timestruct.tm_mday  = day;
694       timestruct.tm_mon   = month;
695       timestruct.tm_year  = year;
696       timestruct.tm_wday  = 0;
697       timestruct.tm_yday  = 0;
698       timestruct.tm_isdst = -1;
699       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
700
701       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
702
703       /* Skip the fifth column */
704
705       tok = strtok(NULL, " ");
706
707       /* Sixth column: Permissions */
708
709       tok = strtok(NULL, ","); /* Skip the VMS-specific SYSTEM permissons */
710       tok = strtok(NULL, ")");
711       if (tok == NULL)
712         {
713           DEBUGP(("confusing VMS permissions, skipping line\n"));
714           continue;
715         }
716       /* Permissons have the format "RWED,RWED,RE" */
717       cur.perms = vmsperms(tok);
718       DEBUGP(("permissions: %s -> 0%o\n", tok, cur.perms));
719
720       cur.linkto = NULL;
721
722       /* And put everything into the linked list */
723       if (!dir)
724         {
725           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
726           memcpy (l, &cur, sizeof (cur));
727           l->prev = l->next = NULL;
728         }
729       else
730         {
731           cur.prev = l;
732           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
733           l = l->next;
734           memcpy (l, &cur, sizeof (cur));
735           l->next = NULL;
736         }
737
738       xfree (line);
739     }
740
741   fclose (fp);
742   return dir;
743 }
744
745
746 /* This function switches between the correct parsing routine depending on
747    the SYSTEM_TYPE. The system type should be based on the result of the
748    "SYST" response of the FTP server. According to this repsonse we will
749    use on of the three different listing parsers that cover the most of FTP
750    servers used nowadays.  */
751
752 struct fileinfo *
753 ftp_parse_ls (const char *file, const enum stype system_type)
754 {
755   switch (system_type)
756     {
757     case ST_UNIX:
758       return ftp_parse_unix_ls (file, FALSE);
759     case ST_WINNT:
760       {
761         /* Detect whether the listing is simulating the UNIX format */
762         FILE *fp;
763         int   c;
764         fp = fopen (file, "rb");
765         if (!fp)
766         {
767           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
768           return NULL;
769         }
770         c = fgetc(fp);
771         fclose(fp);
772         /* If the first character of the file is '0'-'9', it's WINNT
773            format. */
774         if (c >= '0' && c <='9')
775           return ftp_parse_winnt_ls (file);
776         else
777           return ftp_parse_unix_ls (file, TRUE);
778       }
779     case ST_VMS:
780       return ftp_parse_vms_ls (file);
781     case ST_MACOS:
782       return ftp_parse_unix_ls (file, TRUE);
783     default:
784       logprintf (LOG_NOTQUIET, _("\
785 Usupported listing type, trying Unix listing parser.\n"));
786       return ftp_parse_unix_ls (file, FALSE);
787     }
788 }
789 \f
790 /* Stuff for creating FTP index. */
791
792 /* The function creates an HTML index containing references to given
793    directories and files on the appropriate host.  The references are
794    FTP.  */
795 uerr_t
796 ftp_index (const char *file, struct urlinfo *u, struct fileinfo *f)
797 {
798   FILE *fp;
799   char *upwd;
800   char *htclfile;               /* HTML-clean file name */
801
802   if (!opt.dfp)
803     {
804       fp = fopen (file, "wb");
805       if (!fp)
806         {
807           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
808           return FOPENERR;
809         }
810     }
811   else
812     fp = opt.dfp;
813   if (u->user)
814     {
815       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
816
817       tmpu = CLEANDUP (u->user);
818       tmpp = u->passwd ? CLEANDUP (u->passwd) : NULL;
819       upwd = (char *)xmalloc (strlen (tmpu)
820                              + (tmpp ? (1 + strlen (tmpp)) : 0) + 2);
821       sprintf (upwd, "%s%s%s@", tmpu, tmpp ? ":" : "", tmpp ? tmpp : "");
822       xfree (tmpu);
823       FREE_MAYBE (tmpp);
824     }
825   else
826     upwd = xstrdup ("");
827   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
828   fprintf (fp, "<html>\n<head>\n<title>");
829   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
830   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
831   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
832   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
833   while (f)
834     {
835       fprintf (fp, "  ");
836       if (f->tstamp != -1)
837         {
838           /* #### Should we translate the months? */
839           static char *months[] = {
840             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
841             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
842           };
843           struct tm *ptm = localtime ((time_t *)&f->tstamp);
844
845           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
846                   ptm->tm_mday);
847           if (ptm->tm_hour)
848             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
849           else
850             fprintf (fp, "       ");
851         }
852       else
853         fprintf (fp, _("time unknown       "));
854       switch (f->type)
855         {
856         case FT_PLAINFILE:
857           fprintf (fp, _("File        "));
858           break;
859         case FT_DIRECTORY:
860           fprintf (fp, _("Directory   "));
861           break;
862         case FT_SYMLINK:
863           fprintf (fp, _("Link        "));
864           break;
865         default:
866           fprintf (fp, _("Not sure    "));
867           break;
868         }
869       htclfile = html_quote_string (f->name);
870       fprintf (fp, "<a href=\"ftp://%s%s:%hu", upwd, u->host, u->port);
871       if (*u->dir != '/')
872         putc ('/', fp);
873       fprintf (fp, "%s", u->dir);
874       if (*u->dir)
875         putc ('/', fp);
876       fprintf (fp, "%s", htclfile);
877       if (f->type == FT_DIRECTORY)
878         putc ('/', fp);
879       fprintf (fp, "\">%s", htclfile);
880       if (f->type == FT_DIRECTORY)
881         putc ('/', fp);
882       fprintf (fp, "</a> ");
883       if (f->type == FT_PLAINFILE)
884         fprintf (fp, _(" (%s bytes)"), legible (f->size));
885       else if (f->type == FT_SYMLINK)
886         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
887       putc ('\n', fp);
888       xfree (htclfile);
889       f = f->next;
890     }
891   fprintf (fp, "</pre>\n</body>\n</html>\n");
892   xfree (upwd);
893   if (!opt.dfp)
894     fclose (fp);
895   else
896     fflush (fp);
897   return FTPOK;
898 }