]> sjero.net Git - wget/blob - src/ftp-ls.c
Fix ISDIGIT to c_isdigit.
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
3    2004, 2005, 2006, 2007, 2008 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 3 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, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #include "wget.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #ifdef HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 #include <errno.h>
40 #include <time.h>
41 #include "utils.h"
42 #include "ftp.h"
43 #include "url.h"
44 #include "convert.h"            /* for html_quote_string prototype */
45 #include "retr.h"               /* for output_stream */
46
47 /* Converts symbolic permissions to number-style ones, e.g. string
48    rwxr-xr-x to 755.  For now, it knows nothing of
49    setuid/setgid/sticky.  ACLs are ignored.  */
50 static int
51 symperms (const char *s)
52 {
53   int perms = 0, i;
54
55   if (strlen (s) < 9)
56     return 0;
57   for (i = 0; i < 3; i++, s += 3)
58     {
59       perms <<= 3;
60       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
61                 (s[2] == 'x' || s[2] == 's'));
62     }
63   return perms;
64 }
65
66
67 /* Cleans a line of text so that it can be consistently parsed. Destroys
68    <CR> and <LF> in case that thay occur at the end of the line and
69    replaces all <TAB> character with <SPACE>. Returns the length of the
70    modified line. */
71 static int
72 clean_line(char *line)
73 {
74   int len = strlen (line);
75   if (!len) return 0; 
76   if (line[len - 1] == '\n')
77     line[--len] = '\0';
78   if (line[len - 1] == '\r')
79     line[--len] = '\0';
80   for ( ; *line ; line++ ) if (*line == '\t') *line = ' '; 
81   return len;
82 }
83
84 /* Convert the Un*x-ish style directory listing stored in FILE to a
85    linked list of fileinfo (system-independent) entries.  The contents
86    of FILE are considered to be produced by the standard Unix `ls -la'
87    output (whatever that might be).  BSD (no group) and SYSV (with
88    group) listings are handled.
89
90    The time stamps are stored in a separate variable, time_t
91    compatible (I hope).  The timezones are ignored.  */
92 static struct fileinfo *
93 ftp_parse_unix_ls (const char *file, int ignore_perms)
94 {
95   FILE *fp;
96   static const char *months[] = {
97     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
98     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
99   };
100   int next, len, i, error, ignore;
101   int year, month, day;         /* for time analysis */
102   int hour, min, sec;
103   struct tm timestruct, *tnow;
104   time_t timenow;
105
106   char *line, *tok, *ptok;      /* tokenizer */
107   struct fileinfo *dir, *l, cur; /* list creation */
108
109   fp = fopen (file, "rb");
110   if (!fp)
111     {
112       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
113       return NULL;
114     }
115   dir = l = NULL;
116
117   /* Line loop to end of file: */
118   while ((line = read_whole_line (fp)) != NULL)
119     {
120       len = clean_line (line);
121       /* Skip if total...  */
122       if (!strncasecmp (line, "total", 5))
123         {
124           xfree (line);
125           continue;
126         }
127       /* Get the first token (permissions).  */
128       tok = strtok (line, " ");
129       if (!tok)
130         {
131           xfree (line);
132           continue;
133         }
134
135       cur.name = NULL;
136       cur.linkto = NULL;
137
138       /* Decide whether we deal with a file or a directory.  */
139       switch (*tok)
140         {
141         case '-':
142           cur.type = FT_PLAINFILE;
143           DEBUGP (("PLAINFILE; "));
144           break;
145         case 'd':
146           cur.type = FT_DIRECTORY;
147           DEBUGP (("DIRECTORY; "));
148           break;
149         case 'l':
150           cur.type = FT_SYMLINK;
151           DEBUGP (("SYMLINK; "));
152           break;
153         default:
154           cur.type = FT_UNKNOWN;
155           DEBUGP (("UNKNOWN; "));
156           break;
157         }
158
159       if (ignore_perms)
160         {
161           switch (cur.type)
162             {
163             case FT_PLAINFILE:
164               cur.perms = 0644;
165               break;
166             case FT_DIRECTORY:
167               cur.perms = 0755;
168               break;
169             default:
170               /*cur.perms = 1023;*/     /* #### What is this?  --hniksic */
171               cur.perms = 0644;
172             }
173           DEBUGP (("implicit perms %0o; ", cur.perms));
174         }
175        else
176          {
177            cur.perms = symperms (tok + 1);
178            DEBUGP (("perms %0o; ", cur.perms));
179          }
180
181       error = ignore = 0;       /* Erroneous and ignoring entries are
182                                    treated equally for now.  */
183       year = hour = min = sec = 0; /* Silence the compiler.  */
184       month = day = 0;
185       next = -1;
186       /* While there are tokens on the line, parse them.  Next is the
187          number of tokens left until the filename.
188
189          Use the month-name token as the "anchor" (the place where the
190          position wrt the file name is "known").  When a month name is
191          encountered, `next' is set to 5.  Also, the preceding
192          characters are parsed to get the file size.
193
194          This tactic is quite dubious when it comes to
195          internationalization issues (non-English month names), but it
196          works for now.  */
197       tok = line;
198       while (ptok = tok,
199              (tok = strtok (NULL, " ")) != NULL)
200         {
201           --next;
202           if (next < 0)         /* a month name was not encountered */
203             {
204               for (i = 0; i < 12; i++)
205                 if (!strcmp (tok, months[i]))
206                   break;
207               /* If we got a month, it means the token before it is the
208                  size, and the filename is three tokens away.  */
209               if (i != 12)
210                 {
211                   wgint size;
212
213                   /* Parse the previous token with str_to_wgint.  */
214                   if (ptok == line)
215                     {
216                       /* Something has gone wrong during parsing. */
217                       error = 1;
218                       break;
219                     }
220                   errno = 0;
221                   size = str_to_wgint (ptok, NULL, 10);
222                   if (size == WGINT_MAX && errno == ERANGE)
223                     /* Out of range -- ignore the size.  #### Should
224                        we refuse to start the download.  */
225                     cur.size = 0;
226                   else
227                     cur.size = size;
228                   DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
229
230                   month = i;
231                   next = 5;
232                   DEBUGP (("month: %s; ", months[month]));
233                 }
234             }
235           else if (next == 4)   /* days */
236             {
237               if (tok[1])       /* two-digit... */
238                 day = 10 * (*tok - '0') + tok[1] - '0';
239               else              /* ...or one-digit */
240                 day = *tok - '0';
241               DEBUGP (("day: %d; ", day));
242             }
243           else if (next == 3)
244             {
245               /* This ought to be either the time, or the year.  Let's
246                  be flexible!
247
248                  If we have a number x, it's a year.  If we have x:y,
249                  it's hours and minutes.  If we have x:y:z, z are
250                  seconds.  */
251               year = 0;
252               min = hour = sec = 0;
253               /* We must deal with digits.  */
254               if (c_isdigit (*tok))
255                 {
256                   /* Suppose it's year.  */
257                   for (; c_isdigit (*tok); tok++)
258                     year = (*tok - '0') + 10 * year;
259                   if (*tok == ':')
260                     {
261                       /* This means these were hours!  */
262                       hour = year;
263                       year = 0;
264                       ++tok;
265                       /* Get the minutes...  */
266                       for (; c_isdigit (*tok); tok++)
267                         min = (*tok - '0') + 10 * min;
268                       if (*tok == ':')
269                         {
270                           /* ...and the seconds.  */
271                           ++tok;
272                           for (; c_isdigit (*tok); tok++)
273                             sec = (*tok - '0') + 10 * sec;
274                         }
275                     }
276                 }
277               if (year)
278                 DEBUGP (("year: %d (no tm); ", year));
279               else
280                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
281             }
282           else if (next == 2)    /* The file name */
283             {
284               int fnlen;
285               char *p;
286
287               /* Since the file name may contain a SPC, it is possible
288                  for strtok to handle it wrong.  */
289               fnlen = strlen (tok);
290               if (fnlen < len - (tok - line))
291                 {
292                   /* So we have a SPC in the file name.  Restore the
293                      original.  */
294                   tok[fnlen] = ' ';
295                   /* If the file is a symbolic link, it should have a
296                      ` -> ' somewhere.  */
297                   if (cur.type == FT_SYMLINK)
298                     {
299                       p = strstr (tok, " -> ");
300                       if (!p)
301                         {
302                           error = 1;
303                           break;
304                         }
305                       cur.linkto = xstrdup (p + 4);
306                       DEBUGP (("link to: %s\n", cur.linkto));
307                       /* And separate it from the file name.  */
308                       *p = '\0';
309                     }
310                 }
311               /* If we have the filename, add it to the list of files or
312                  directories.  */
313               /* "." and ".." are an exception!  */
314               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
315                 {
316                   DEBUGP (("\nIgnoring `.' and `..'; "));
317                   ignore = 1;
318                   break;
319                 }
320               /* Some FTP sites choose to have ls -F as their default
321                  LIST output, which marks the symlinks with a trailing
322                  `@', directory names with a trailing `/' and
323                  executables with a trailing `*'.  This is no problem
324                  unless encountering a symbolic link ending with `@',
325                  or an executable ending with `*' on a server without
326                  default -F output.  I believe these cases are very
327                  rare.  */
328               fnlen = strlen (tok); /* re-calculate `fnlen' */
329               cur.name = xmalloc (fnlen + 1);
330               memcpy (cur.name, tok, fnlen + 1);
331               if (fnlen)
332                 {
333                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
334                     {
335                       cur.name[fnlen - 1] = '\0';
336                       DEBUGP (("trailing `/' on dir.\n"));
337                     }
338                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
339                     {
340                       cur.name[fnlen - 1] = '\0';
341                       DEBUGP (("trailing `@' on link.\n"));
342                     }
343                   else if (cur.type == FT_PLAINFILE
344                            && (cur.perms & 0111)
345                            && cur.name[fnlen - 1] == '*')
346                     {
347                       cur.name[fnlen - 1] = '\0';
348                       DEBUGP (("trailing `*' on exec.\n"));
349                     }
350                 } /* if (fnlen) */
351               else
352                 error = 1;
353               break;
354             }
355           else
356             abort ();
357         } /* while */
358
359       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
360         error = 1;
361
362       DEBUGP (("%s\n", cur.name ? cur.name : ""));
363
364       if (error || ignore)
365         {
366           DEBUGP (("Skipping.\n"));
367           xfree_null (cur.name);
368           xfree_null (cur.linkto);
369           xfree (line);
370           continue;
371         }
372
373       if (!dir)
374         {
375           l = dir = xnew (struct fileinfo);
376           memcpy (l, &cur, sizeof (cur));
377           l->prev = l->next = NULL;
378         }
379       else
380         {
381           cur.prev = l;
382           l->next = xnew (struct fileinfo);
383           l = l->next;
384           memcpy (l, &cur, sizeof (cur));
385           l->next = NULL;
386         }
387       /* Get the current time.  */
388       timenow = time (NULL);
389       tnow = localtime (&timenow);
390       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
391       timestruct.tm_sec   = sec;
392       timestruct.tm_min   = min;
393       timestruct.tm_hour  = hour;
394       timestruct.tm_mday  = day;
395       timestruct.tm_mon   = month;
396       if (year == 0)
397         {
398           /* Some listings will not specify the year if it is "obvious"
399              that the file was from the previous year.  E.g. if today
400              is 97-01-12, and you see a file of Dec 15th, its year is
401              1996, not 1997.  Thanks to Vladimir Volovich for
402              mentioning this!  */
403           if (month > tnow->tm_mon)
404             timestruct.tm_year = tnow->tm_year - 1;
405           else
406             timestruct.tm_year = tnow->tm_year;
407         }
408       else
409         timestruct.tm_year = year;
410       if (timestruct.tm_year >= 1900)
411         timestruct.tm_year -= 1900;
412       timestruct.tm_wday  = 0;
413       timestruct.tm_yday  = 0;
414       timestruct.tm_isdst = -1;
415       l->tstamp = mktime (&timestruct); /* store the time-stamp */
416
417       xfree (line);
418     }
419
420   fclose (fp);
421   return dir;
422 }
423
424 static struct fileinfo *
425 ftp_parse_winnt_ls (const char *file)
426 {
427   FILE *fp;
428   int len;
429   int year, month, day;         /* for time analysis */
430   int hour, min;
431   struct tm timestruct;
432
433   char *line, *tok;             /* tokenizer */
434   struct fileinfo *dir, *l, cur; /* list creation */
435
436   fp = fopen (file, "rb");
437   if (!fp)
438     {
439       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
440       return NULL;
441     }
442   dir = l = NULL;
443
444   /* Line loop to end of file: */
445   while ((line = read_whole_line (fp)) != NULL)
446     {
447       len = clean_line (line);
448
449       /* Extracting name is a bit of black magic and we have to do it
450          before `strtok' inserted extra \0 characters in the line
451          string. For the moment let us just suppose that the name starts at
452          column 39 of the listing. This way we could also recognize
453          filenames that begin with a series of space characters (but who
454          really wants to use such filenames anyway?). */
455       if (len < 40) continue;
456       tok = line + 39;
457       cur.name = xstrdup(tok);
458       DEBUGP(("Name: '%s'\n", cur.name));
459
460       /* First column: mm-dd-yy. Should atoi() on the month fail, january
461          will be assumed.  */
462       tok = strtok(line, "-");
463       if (tok == NULL) continue;
464       month = atoi(tok) - 1;
465       if (month < 0) month = 0;
466       tok = strtok(NULL, "-");
467       if (tok == NULL) continue;
468       day = atoi(tok);
469       tok = strtok(NULL, " ");
470       if (tok == NULL) continue;
471       year = atoi(tok);
472       /* Assuming the epoch starting at 1.1.1970 */
473       if (year <= 70) year += 100;
474
475       /* Second column: hh:mm[AP]M, listing does not contain value for
476          seconds */
477       tok = strtok(NULL,  ":");
478       if (tok == NULL) continue;
479       hour = atoi(tok);
480       tok = strtok(NULL,  "M");
481       if (tok == NULL) continue;
482       min = atoi(tok);
483       /* Adjust hour from AM/PM. Just for the record, the sequence goes
484          11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
485       tok+=2;
486       if (hour == 12)  hour  = 0;
487       if (*tok == 'P') hour += 12;
488
489       DEBUGP(("YYYY/MM/DD HH:MM - %d/%02d/%02d %02d:%02d\n", 
490               year+1900, month, day, hour, min));
491       
492       /* Build the time-stamp (copy & paste from above) */
493       timestruct.tm_sec   = 0;
494       timestruct.tm_min   = min;
495       timestruct.tm_hour  = hour;
496       timestruct.tm_mday  = day;
497       timestruct.tm_mon   = month;
498       timestruct.tm_year  = year;
499       timestruct.tm_wday  = 0;
500       timestruct.tm_yday  = 0;
501       timestruct.tm_isdst = -1;
502       cur.tstamp = mktime (&timestruct); /* store the time-stamp */
503
504       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
505
506       /* Third column: Either file length, or <DIR>. We also set the
507          permissions (guessed as 0644 for plain files and 0755 for
508          directories as the listing does not give us a clue) and filetype
509          here. */
510       tok = strtok(NULL, " ");
511       if (tok == NULL) continue;
512       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
513       if (tok == NULL) continue;
514       if (*tok == '<')
515         {
516           cur.type  = FT_DIRECTORY;
517           cur.size  = 0;
518           cur.perms = 0755;
519           DEBUGP(("Directory\n"));
520         }
521       else
522         {
523           wgint size;
524           cur.type  = FT_PLAINFILE;
525           errno = 0;
526           size = str_to_wgint (tok, NULL, 10);
527           if (size == WGINT_MAX && errno == ERANGE)
528             cur.size = 0;       /* overflow */
529           else
530             cur.size = size;
531           cur.perms = 0644;
532           DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
533         }
534
535       cur.linkto = NULL;
536
537       /* And put everything into the linked list */
538       if (!dir)
539         {
540           l = dir = xnew (struct fileinfo);
541           memcpy (l, &cur, sizeof (cur));
542           l->prev = l->next = NULL;
543         }
544       else
545         {
546           cur.prev = l;
547           l->next = xnew (struct fileinfo);
548           l = l->next;
549           memcpy (l, &cur, sizeof (cur));
550           l->next = NULL;
551         }
552
553       xfree (line);
554     }
555
556   fclose(fp);
557   return dir;
558 }
559
560
561
562 /* Convert the VMS-style directory listing stored in "file" to a
563    linked list of fileinfo (system-independent) entries.  The contents
564    of FILE are considered to be produced by the standard VMS
565    "DIRECTORY [/SIZE [= ALL]] /DATE [/OWNER] [/PROTECTION]" command,
566    more or less.  (Different VMS FTP servers may have different headers,
567    and may not supply the same data, but all should be subsets of this.)
568
569    VMS normally provides local (server) time and date information. 
570    Define the logical name or environment variable
571    "WGET_TIMEZONE_DIFFERENTIAL" (seconds) to adjust the receiving local
572    times if different from the remote local times.
573
574    2005-02-23 SMS.
575    Added code to eliminate "^" escape characters from ODS5 extended file
576    names.  The TCPIP FTP server (V5.4) seems to prefer requests which do
577    not use the escaped names which it provides.
578 */
579
580 #define VMS_DEFAULT_PROT_FILE 0644
581 #define VMS_DEFAULT_PROT_DIR 0755
582
583 /* 2005-02-23 SMS.
584    eat_carets().
585
586    Delete ODS5 extended file name escape characters ("^") in the
587    original buffer.
588    Note that the current scheme does not handle all EFN cases, but it
589    could be made more complicated.
590 */
591
592 static void eat_carets( char *str)
593 /* char *str;      Source pointer. */
594 {
595   char *strd;   /* Destination pointer. */
596   char hdgt;
597   unsigned char uchr;
598   unsigned char prop;
599
600   /* Skip ahead to the first "^", if any. */
601   while ((*str != '\0') && (*str != '^'))
602      str++;
603
604   /* If no caret was found, quit early. */
605   if (*str != '\0')
606   {
607     /* Shift characters leftward as carets are found. */
608     strd = str;
609     while (*str != '\0')
610     {
611       uchr = *str;
612       if (uchr == '^')
613       {
614         /* Found a caret.  Skip it, and check the next character. */
615         uchr = *(++str);
616         prop = char_prop[ uchr];
617         if (prop& 64)
618         {
619           /* Hex digit.  Get char code from this and next hex digit. */
620           if (uchr <= '9')
621           {
622             hdgt = uchr- '0';           /* '0' - '9' -> 0 - 9. */
623           }
624           else
625           {
626             hdgt = ((uchr- 'A')& 7)+ 10;    /* [Aa] - [Ff] -> 10 - 15. */
627           }
628           hdgt <<= 4;                   /* X16. */
629           uchr = *(++str);              /* Next char must be hex digit. */
630           if (uchr <= '9')
631           {
632             uchr = hdgt+ uchr- '0';
633           }
634           else
635           {
636             uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
637           }
638         }
639         else if (uchr == '_')
640         {
641           /* Convert escaped "_" to " ". */
642           uchr = ' ';
643         }
644         else if (uchr == '/')
645         {
646           /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
647           /* Note that this is a left-over from Info-ZIP code, and is
648              probably of little value here, except perhaps to avoid
649              directory confusion which an unconverted slash might cause.
650           */
651           uchr = '?';
652         }
653         /* Else, not a hex digit.  Must be a simple escaped character
654            (or Unicode, which is not yet handled here).
655         */
656       }
657       /* Else, not a caret.  Use as-is. */
658       *strd = uchr;
659
660       /* Advance destination and source pointers. */
661       strd++;
662       str++;
663     }
664     /* Terminate the destination string. */
665     *strd = '\0';
666   }
667 }
668
669
670 static struct fileinfo *
671 ftp_parse_vms_ls (const char *file)
672 {
673   FILE *fp;
674   int dt, i, j, len;
675   int perms;
676   time_t timenow;
677   struct tm timestruct;
678   char date_str[ 32];
679
680   char *line, *tok;              /* tokenizer */
681   struct fileinfo *dir, *l, cur; /* list creation */
682
683   fp = fopen (file, "r");
684   if (!fp)
685     {
686       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
687       return NULL;
688     }
689   dir = l = NULL;
690
691   /* Skip blank lines, Directory heading, and more blank lines. */
692
693   j = 0; /* Expecting initial blank line(s). */
694   while (1)
695     {
696       line = read_whole_line (fp);
697       if (line == NULL)
698         {
699         break;
700         }
701       else
702         {
703           i = clean_line (line);
704           if (i <= 0)
705             {
706               xfree (line); /* Free useless line storage. */
707               continue; /* Blank line.  Keep looking. */
708             }
709           else
710             {
711               if ((j == 0) && (line[ i- 1] == ']'))
712                 {
713                   /* Found Directory heading line.  Next non-blank line
714                   is significant.
715                   */
716                   j = 1;
717                 }
718               else if (!strncmp (line, "Total of ", 9))
719                 {
720                   /* Found "Total of ..." footing line.  No valid data
721                      will follow (empty directory).
722                   */
723                   xfree (line); /* Free useless line storage. */
724                   line = NULL; /* Arrange for early exit. */
725                   break;
726                 }
727               else
728                 {
729                   break; /* Must be significant data. */
730                 }
731             }
732           xfree (line); /* Free useless line storage. */
733         }
734     }
735
736   /* Read remainder of file until the next blank line or EOF. */
737
738   while (line != NULL)
739     {
740       char *p;
741
742       /* The first token is the file name.  After a long name, other
743          data may be on the following line.  A valid directory name ends
744          in ".DIR;1" (any case), although some VMS FTP servers may omit
745          the version number (";1").
746       */
747
748       tok = strtok(line, " ");
749       if (tok == NULL) tok = line;
750       DEBUGP(("file name:   '%s'\n", tok));
751
752       /* Stripping the version number on a VMS system would be wrong.
753          It may be foolish on a non-VMS system, too, but that's someone
754          else's problem.  (Define PRESERVE_VMS_VERSIONS for proper
755          operation on other operating systems.)
756
757          2005-02-23 SMS.
758          ODS5 extended file names may contain escaped semi-colons, so
759          the version number is identified as right-side decimal digits
760          led by a non-escaped semi-colon.  It may be absent.
761       */
762
763 #if (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS))
764       for (p = tok+ strlen( tok); (--p > tok) && c_isdigit( *p); );
765       if ((*p == ';') && (*(p- 1) != '^'))
766         {
767           *p = '\0';
768         }
769 #endif /* (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS)) */
770
771       /* 2005-02-23 SMS.
772          Eliminate "^" escape characters from ODS5 extended file name. 
773          (A caret is invalid in an ODS2 name, so this is always safe.)
774       */
775       eat_carets( tok);
776       DEBUGP(("file name-^: '%s'\n", tok));
777
778       /* Differentiate between a directory and any other file.  A VMS
779          listing may not include file protections (permissions).  Set a
780          default permissions value (according to the file type), which
781          may be overwritten later.  Store directory names without the
782          ".DIR;1" file type and version number, as the plain name is
783          what will work in a CWD command.
784       */
785       len = strlen( tok);
786       if (!strncasecmp( (tok+ (len- 4)), ".DIR", 4))
787         {
788           *(tok+ (len -= 4)) = '\0'; /* Discard ".DIR". */
789           cur.type  = FT_DIRECTORY;
790           cur.perms = VMS_DEFAULT_PROT_DIR;
791           DEBUGP(("Directory (nv)\n"));
792         }
793       else if (!strncasecmp( (tok+ (len- 6)), ".DIR;1", 6))
794         {
795           *(tok+ (len -= 6)) = '\0'; /* Discard ".DIR;1". */
796           cur.type  = FT_DIRECTORY;
797           cur.perms = VMS_DEFAULT_PROT_DIR;
798           DEBUGP(("Directory (v)\n"));
799         }
800       else
801         {
802           cur.type  = FT_PLAINFILE;
803           cur.perms = VMS_DEFAULT_PROT_FILE;
804           DEBUGP(("File\n"));
805         }
806       cur.name = xstrdup(tok);
807       DEBUGP(("Name: '%s'\n", cur.name));
808
809       /* Null the date and time string. */
810       *date_str = '\0';
811
812       /* VMS lacks symbolic links. */
813       cur.linkto = NULL;
814
815       /* VMS reports file sizes in (512-byte) disk blocks, not bytes,
816          hence useless for an integrity check based on byte-count.
817          Set size to unknown.
818       */
819       cur.size  = 0;
820
821       /* Get token 2, if any.  A long name may force all other data onto
822          a second line.  If needed, read the second line.
823       */
824
825       tok = strtok(NULL, " ");
826       if (tok == NULL) 
827         {
828           DEBUGP(("Getting additional line.\n"));
829           xfree (line);
830           line = read_whole_line (fp);
831           if (!line)
832             {
833               DEBUGP(("EOF.  Leaving listing parser.\n"));
834               break;
835             }
836
837           /* Second line must begin with " ".  Otherwise, it's a first
838              line (and we may be confused).
839           */
840           if (i <= 0)
841             {
842               /* Blank line.  End of significant file listing. */
843               DEBUGP(("Blank line.  Leaving listing parser.\n"));
844               xfree (line); /* Free useless line storage. */
845               break;
846             }
847           else if (line[ 0] != ' ')
848             {
849               DEBUGP(("Non-blank in column 1.  Must be a new file name?\n"));
850               continue;
851             }
852           else
853             {
854               tok = strtok (line, " ");
855               if (tok == NULL)
856                 {
857                   /* Unexpected non-empty but apparently blank line. */
858                   DEBUGP(("Null token.  Leaving listing parser.\n"));
859                   xfree (line); /* Free useless line storage. */
860                   break;
861                 }
862             }
863         }
864
865       /* Analyze tokens.  (Order is not significant, except date must
866          precede time.)
867
868          Size:       ddd or ddd/ddd (where "ddd" is a decimal number)
869          Date:       DD-MMM-YYYY
870          Time:       HH:MM or HH:MM:SS or HH:MM:SS.CC
871          Owner:      [user] or [user,group]
872          Protection: (ppp,ppp,ppp,ppp) (where "ppp" is "RWED" or some
873                      subset thereof, for System, Owner, Group, World.
874
875          If permission is lacking, info may be replaced by the string:
876          "No privilege for attempted operation".
877       */
878       while (tok != NULL)
879         {
880           DEBUGP (("Token: >%s<: ", tok));
881
882           if ((strlen( tok) < 12) && (strchr( tok, '-') != NULL))
883             {
884               /* Date. */
885               DEBUGP (("Date.\n"));
886               strcpy( date_str, tok);
887               strcat( date_str, " ");
888             }
889           else if ((strlen( tok) < 12) && (strchr( tok, ':') != NULL))
890             {
891               /* Time. */
892               DEBUGP (("Time. "));
893               strncat( date_str,
894                tok,
895                (sizeof( date_str)- strlen( date_str)- 1));
896               DEBUGP (("Date time: >%s<\n", date_str));
897             }
898           else if (strchr( tok, '[') != NULL)
899             {
900               /* Owner.  (Ignore.) */
901               DEBUGP (("Owner.\n"));
902             }
903           else if (strchr( tok, '(') != NULL)
904             {
905               /* Protections (permissions). */
906               perms = 0;
907               j = 0;
908               for (i = 0; i < strlen( tok); i++)
909                 {
910                   switch (tok[ i])
911                     {
912                       case '(':
913                         break;
914                       case ')':
915                         break;
916                       case ',':
917                         if (j == 0)
918                           {
919                             perms = 0;
920                             j = 1;
921                           }
922                         else
923                           {
924                             perms <<= 3;
925                           }
926                         break;
927                     case 'R':
928                       perms |= 4;
929                       break;
930                     case 'W':
931                       perms |= 2;
932                       break;
933                     case 'E':
934                       perms |= 1;
935                       break;
936                     case 'D':
937                       perms |= 2;
938                       break;
939                     }
940                 }
941               cur.perms = perms;
942               DEBUGP (("Prot.  perms = %0o.\n", cur.perms));
943             }
944           else
945             {
946               /* Nondescript.  Probably size(s), probably in blocks. 
947                  Could be "No privilege ..." message.  (Ignore.)
948               */
949               DEBUGP (("Ignored (size?).\n"));
950             }
951
952           tok = strtok (NULL, " ");
953         }
954
955       /* Tokens exhausted.  Interpret the data, and fill in the
956          structure.
957       */
958       /* Fill tm timestruct according to date-time string.  Fractional
959          seconds are ignored.  Default to current time, if conversion
960          fails.
961       */
962       timenow = time( NULL);
963       localtime_r( &timenow, &timestruct);
964       strptime( date_str, "%d-%b-%Y %H:%M:%S", &timestruct);
965
966       /* Convert struct tm local time to time_t local time. */
967       timenow = mktime (&timestruct);
968       /* Offset local time according to environment variable (seconds). */
969       if ((tok = getenv( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
970         {
971           dt = atoi( tok);
972           DEBUGP (("Time differential = %d.\n", dt));
973         }
974       else
975         {
976           dt = 0;
977         }
978
979       if (dt >= 0)
980         {
981           timenow += dt;
982         }
983       else
984         {
985           timenow -= (-dt);
986         }
987       cur.tstamp = timenow; /* Store the time-stamp. */
988       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
989
990       /* Add the data for this item to the linked list, */
991       if (!dir)
992         {
993           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
994           memcpy (l, &cur, sizeof (cur));
995           l->prev = l->next = NULL;
996         }
997       else
998         {
999           cur.prev = l;
1000           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
1001           l = l->next;
1002           memcpy (l, &cur, sizeof (cur));
1003           l->next = NULL;
1004         }
1005
1006       /* Free old line storage.  Read a new line. */
1007       xfree (line);
1008       line = read_whole_line (fp);
1009       if (line != NULL)
1010         {
1011           i = clean_line (line);
1012           if (i <= 0)
1013             {
1014               /* Blank line.  End of significant file listing. */
1015               xfree (line); /* Free useless line storage. */
1016               break;
1017             }
1018         }
1019     }
1020
1021   fclose (fp);
1022   return dir;
1023 }
1024
1025
1026 /* This function switches between the correct parsing routine depending on
1027    the SYSTEM_TYPE. The system type should be based on the result of the
1028    "SYST" response of the FTP server. According to this repsonse we will
1029    use on of the three different listing parsers that cover the most of FTP
1030    servers used nowadays.  */
1031
1032 struct fileinfo *
1033 ftp_parse_ls (const char *file, const enum stype system_type)
1034 {
1035   switch (system_type)
1036     {
1037     case ST_UNIX:
1038       return ftp_parse_unix_ls (file, 0);
1039     case ST_WINNT:
1040       {
1041         /* Detect whether the listing is simulating the UNIX format */
1042         FILE *fp;
1043         int   c;
1044         fp = fopen (file, "rb");
1045         if (!fp)
1046         {
1047           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1048           return NULL;
1049         }
1050         c = fgetc(fp);
1051         fclose(fp);
1052         /* If the first character of the file is '0'-'9', it's WINNT
1053            format. */
1054         if (c >= '0' && c <='9')
1055           return ftp_parse_winnt_ls (file);
1056         else
1057           return ftp_parse_unix_ls (file, 1);
1058       }
1059     case ST_VMS:
1060       return ftp_parse_vms_ls (file);
1061     case ST_MACOS:
1062       return ftp_parse_unix_ls (file, 1);
1063     default:
1064       logprintf (LOG_NOTQUIET, _("\
1065 Unsupported listing type, trying Unix listing parser.\n"));
1066       return ftp_parse_unix_ls (file, 0);
1067     }
1068 }
1069 \f
1070 /* Stuff for creating FTP index. */
1071
1072 /* The function creates an HTML index containing references to given
1073    directories and files on the appropriate host.  The references are
1074    FTP.  */
1075 uerr_t
1076 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1077 {
1078   FILE *fp;
1079   char *upwd;
1080   char *htclfile;               /* HTML-clean file name */
1081
1082   if (!output_stream)
1083     {
1084       fp = fopen (file, "wb");
1085       if (!fp)
1086         {
1087           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1088           return FOPENERR;
1089         }
1090     }
1091   else
1092     fp = output_stream;
1093   if (u->user)
1094     {
1095       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
1096
1097       tmpu = url_escape (u->user);
1098       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1099       if (tmpp)
1100         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1101       else
1102         upwd = concat_strings (tmpu, "@", (char *) 0);
1103       xfree (tmpu);
1104       xfree_null (tmpp);
1105     }
1106   else
1107     upwd = xstrdup ("");
1108   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1109   fprintf (fp, "<html>\n<head>\n<title>");
1110   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
1111   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1112   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
1113   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1114   while (f)
1115     {
1116       fprintf (fp, "  ");
1117       if (f->tstamp != -1)
1118         {
1119           /* #### Should we translate the months?  Or, even better, use
1120              ISO 8601 dates?  */
1121           static const char *months[] = {
1122             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1123             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1124           };
1125           struct tm *ptm = localtime ((time_t *)&f->tstamp);
1126
1127           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1128                   ptm->tm_mday);
1129           if (ptm->tm_hour)
1130             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
1131           else
1132             fprintf (fp, "       ");
1133         }
1134       else
1135         fprintf (fp, _("time unknown       "));
1136       switch (f->type)
1137         {
1138         case FT_PLAINFILE:
1139           fprintf (fp, _("File        "));
1140           break;
1141         case FT_DIRECTORY:
1142           fprintf (fp, _("Directory   "));
1143           break;
1144         case FT_SYMLINK:
1145           fprintf (fp, _("Link        "));
1146           break;
1147         default:
1148           fprintf (fp, _("Not sure    "));
1149           break;
1150         }
1151       htclfile = html_quote_string (f->name);
1152       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1153       if (*u->dir != '/')
1154         putc ('/', fp);
1155       fprintf (fp, "%s", u->dir);
1156       if (*u->dir)
1157         putc ('/', fp);
1158       fprintf (fp, "%s", htclfile);
1159       if (f->type == FT_DIRECTORY)
1160         putc ('/', fp);
1161       fprintf (fp, "\">%s", htclfile);
1162       if (f->type == FT_DIRECTORY)
1163         putc ('/', fp);
1164       fprintf (fp, "</a> ");
1165       if (f->type == FT_PLAINFILE)
1166         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1167       else if (f->type == FT_SYMLINK)
1168         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1169       putc ('\n', fp);
1170       xfree (htclfile);
1171       f = f->next;
1172     }
1173   fprintf (fp, "</pre>\n</body>\n</html>\n");
1174   xfree (upwd);
1175   if (!output_stream)
1176     fclose (fp);
1177   else
1178     fflush (fp);
1179   return FTPOK;
1180 }