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