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