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