]> sjero.net Git - wget/blob - src/ftp-ls.c
ftp: display hour:minute information if it is available.
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3    2005, 2006, 2007, 2008, 2009, 2010 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 (!len) return 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, ptype;
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       ptype = TT_DAY;
187       next = -1;
188       /* While there are tokens on the line, parse them.  Next is the
189          number of tokens left until the filename.
190
191          Use the month-name token as the "anchor" (the place where the
192          position wrt the file name is "known").  When a month name is
193          encountered, `next' is set to 5.  Also, the preceding
194          characters are parsed to get the file size.
195
196          This tactic is quite dubious when it comes to
197          internationalization issues (non-English month names), but it
198          works for now.  */
199       tok = line;
200       while (ptok = tok,
201              (tok = strtok (NULL, " ")) != NULL)
202         {
203           --next;
204           if (next < 0)         /* a month name was not encountered */
205             {
206               for (i = 0; i < 12; i++)
207                 if (!strcmp (tok, months[i]))
208                   break;
209               /* If we got a month, it means the token before it is the
210                  size, and the filename is three tokens away.  */
211               if (i != 12)
212                 {
213                   wgint size;
214
215                   /* Parse the previous token with str_to_wgint.  */
216                   if (ptok == line)
217                     {
218                       /* Something has gone wrong during parsing. */
219                       error = 1;
220                       break;
221                     }
222                   errno = 0;
223                   size = str_to_wgint (ptok, NULL, 10);
224                   if (size == WGINT_MAX && errno == ERANGE)
225                     /* Out of range -- ignore the size.  #### Should
226                        we refuse to start the download.  */
227                     cur.size = 0;
228                   else
229                     cur.size = size;
230                   DEBUGP (("size: %s; ", number_to_static_string(cur.size)));
231
232                   month = i;
233                   next = 5;
234                   DEBUGP (("month: %s; ", months[month]));
235                 }
236             }
237           else if (next == 4)   /* days */
238             {
239               if (tok[1])       /* two-digit... */
240                 day = 10 * (*tok - '0') + tok[1] - '0';
241               else              /* ...or one-digit */
242                 day = *tok - '0';
243               DEBUGP (("day: %d; ", day));
244             }
245           else if (next == 3)
246             {
247               /* This ought to be either the time, or the year.  Let's
248                  be flexible!
249
250                  If we have a number x, it's a year.  If we have x:y,
251                  it's hours and minutes.  If we have x:y:z, z are
252                  seconds.  */
253               year = 0;
254               min = hour = sec = 0;
255               /* We must deal with digits.  */
256               if (c_isdigit (*tok))
257                 {
258                   /* Suppose it's year.  */
259                   for (; c_isdigit (*tok); tok++)
260                     year = (*tok - '0') + 10 * year;
261                   if (*tok == ':')
262                     {
263                       /* This means these were hours!  */
264                       hour = year;
265                       year = 0;
266                       ptype = TT_HOUR_MIN;
267                       ++tok;
268                       /* Get the minutes...  */
269                       for (; c_isdigit (*tok); tok++)
270                         min = (*tok - '0') + 10 * min;
271                       if (*tok == ':')
272                         {
273                           /* ...and the seconds.  */
274                           ++tok;
275                           for (; c_isdigit (*tok); tok++)
276                             sec = (*tok - '0') + 10 * sec;
277                         }
278                     }
279                 }
280               if (year)
281                 DEBUGP (("year: %d (no tm); ", year));
282               else
283                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
284             }
285           else if (next == 2)    /* The file name */
286             {
287               int fnlen;
288               char *p;
289
290               /* Since the file name may contain a SPC, it is possible
291                  for strtok to handle it wrong.  */
292               fnlen = strlen (tok);
293               if (fnlen < len - (tok - line))
294                 {
295                   /* So we have a SPC in the file name.  Restore the
296                      original.  */
297                   tok[fnlen] = ' ';
298                   /* If the file is a symbolic link, it should have a
299                      ` -> ' somewhere.  */
300                   if (cur.type == FT_SYMLINK)
301                     {
302                       p = strstr (tok, " -> ");
303                       if (!p)
304                         {
305                           error = 1;
306                           break;
307                         }
308                       cur.linkto = xstrdup (p + 4);
309                       DEBUGP (("link to: %s\n", cur.linkto));
310                       /* And separate it from the file name.  */
311                       *p = '\0';
312                     }
313                 }
314               /* If we have the filename, add it to the list of files or
315                  directories.  */
316               /* "." and ".." are an exception!  */
317               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
318                 {
319                   DEBUGP (("\nIgnoring `.' and `..'; "));
320                   ignore = 1;
321                   break;
322                 }
323               /* Some FTP sites choose to have ls -F as their default
324                  LIST output, which marks the symlinks with a trailing
325                  `@', directory names with a trailing `/' and
326                  executables with a trailing `*'.  This is no problem
327                  unless encountering a symbolic link ending with `@',
328                  or an executable ending with `*' on a server without
329                  default -F output.  I believe these cases are very
330                  rare.  */
331               fnlen = strlen (tok); /* re-calculate `fnlen' */
332               cur.name = xmalloc (fnlen + 1);
333               memcpy (cur.name, tok, fnlen + 1);
334               if (fnlen)
335                 {
336                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
337                     {
338                       cur.name[fnlen - 1] = '\0';
339                       DEBUGP (("trailing `/' on dir.\n"));
340                     }
341                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
342                     {
343                       cur.name[fnlen - 1] = '\0';
344                       DEBUGP (("trailing `@' on link.\n"));
345                     }
346                   else if (cur.type == FT_PLAINFILE
347                            && (cur.perms & 0111)
348                            && cur.name[fnlen - 1] == '*')
349                     {
350                       cur.name[fnlen - 1] = '\0';
351                       DEBUGP (("trailing `*' on exec.\n"));
352                     }
353                 } /* if (fnlen) */
354               else
355                 error = 1;
356               break;
357             }
358           else
359             abort ();
360         } /* while */
361
362       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
363         error = 1;
364
365       DEBUGP (("%s\n", cur.name ? cur.name : ""));
366
367       if (error || ignore)
368         {
369           DEBUGP (("Skipping.\n"));
370           xfree_null (cur.name);
371           xfree_null (cur.linkto);
372           xfree (line);
373           continue;
374         }
375
376       if (!dir)
377         {
378           l = dir = xnew (struct fileinfo);
379           memcpy (l, &cur, sizeof (cur));
380           l->prev = l->next = NULL;
381         }
382       else
383         {
384           cur.prev = l;
385           l->next = xnew (struct fileinfo);
386           l = l->next;
387           memcpy (l, &cur, sizeof (cur));
388           l->next = NULL;
389         }
390       /* Get the current time.  */
391       timenow = time (NULL);
392       tnow = localtime (&timenow);
393       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
394       timestruct.tm_sec   = sec;
395       timestruct.tm_min   = min;
396       timestruct.tm_hour  = hour;
397       timestruct.tm_mday  = day;
398       timestruct.tm_mon   = month;
399       if (year == 0)
400         {
401           /* Some listings will not specify the year if it is "obvious"
402              that the file was from the previous year.  E.g. if today
403              is 97-01-12, and you see a file of Dec 15th, its year is
404              1996, not 1997.  Thanks to Vladimir Volovich for
405              mentioning this!  */
406           if (month > tnow->tm_mon)
407             timestruct.tm_year = tnow->tm_year - 1;
408           else
409             timestruct.tm_year = tnow->tm_year;
410         }
411       else
412         timestruct.tm_year = year;
413       if (timestruct.tm_year >= 1900)
414         timestruct.tm_year -= 1900;
415       timestruct.tm_wday  = 0;
416       timestruct.tm_yday  = 0;
417       timestruct.tm_isdst = -1;
418       l->tstamp = mktime (&timestruct); /* store the time-stamp */
419       l->ptype = ptype;
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       cur.ptype = TT_HOUR_MIN;
508
509       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
510
511       /* Third column: Either file length, or <DIR>. We also set the
512          permissions (guessed as 0644 for plain files and 0755 for
513          directories as the listing does not give us a clue) and filetype
514          here. */
515       tok = strtok(NULL, " ");
516       if (tok == NULL) continue;
517       while ((tok != NULL) && (*tok == '\0'))  tok = strtok(NULL, " ");
518       if (tok == NULL) continue;
519       if (*tok == '<')
520         {
521           cur.type  = FT_DIRECTORY;
522           cur.size  = 0;
523           cur.perms = 0755;
524           DEBUGP(("Directory\n"));
525         }
526       else
527         {
528           wgint size;
529           cur.type  = FT_PLAINFILE;
530           errno = 0;
531           size = str_to_wgint (tok, NULL, 10);
532           if (size == WGINT_MAX && errno == ERANGE)
533             cur.size = 0;       /* overflow */
534           else
535             cur.size = size;
536           cur.perms = 0644;
537           DEBUGP(("File, size %s bytes\n", number_to_static_string (cur.size)));
538         }
539
540       cur.linkto = NULL;
541
542       /* And put everything into the linked list */
543       if (!dir)
544         {
545           l = dir = xnew (struct fileinfo);
546           memcpy (l, &cur, sizeof (cur));
547           l->prev = l->next = NULL;
548         }
549       else
550         {
551           cur.prev = l;
552           l->next = xnew (struct fileinfo);
553           l = l->next;
554           memcpy (l, &cur, sizeof (cur));
555           l->next = NULL;
556         }
557
558       xfree (line);
559     }
560
561   fclose(fp);
562   return dir;
563 }
564
565
566
567 /* Convert the VMS-style directory listing stored in "file" to a
568    linked list of fileinfo (system-independent) entries.  The contents
569    of FILE are considered to be produced by the standard VMS
570    "DIRECTORY [/SIZE [= ALL]] /DATE [/OWNER] [/PROTECTION]" command,
571    more or less.  (Different VMS FTP servers may have different headers,
572    and may not supply the same data, but all should be subsets of this.)
573
574    VMS normally provides local (server) time and date information.
575    Define the logical name or environment variable
576    "WGET_TIMEZONE_DIFFERENTIAL" (seconds) to adjust the receiving local
577    times if different from the remote local times.
578
579    2005-02-23 SMS.
580    Added code to eliminate "^" escape characters from ODS5 extended file
581    names.  The TCPIP FTP server (V5.4) seems to prefer requests which do
582    not use the escaped names which it provides.
583 */
584
585 #define VMS_DEFAULT_PROT_FILE 0644
586 #define VMS_DEFAULT_PROT_DIR 0755
587
588 /* 2005-02-23 SMS.
589    eat_carets().
590
591    Delete ODS5 extended file name escape characters ("^") in the
592    original buffer.
593    Note that the current scheme does not handle all EFN cases, but it
594    could be made more complicated.
595 */
596
597 static void eat_carets( char *str)
598 /* char *str;      Source pointer. */
599 {
600   char *strd;   /* Destination pointer. */
601   char hdgt;
602   unsigned char uchr;
603   unsigned char prop;
604
605   /* Skip ahead to the first "^", if any. */
606   while ((*str != '\0') && (*str != '^'))
607      str++;
608
609   /* If no caret was found, quit early. */
610   if (*str != '\0')
611   {
612     /* Shift characters leftward as carets are found. */
613     strd = str;
614     while (*str != '\0')
615     {
616       uchr = *str;
617       if (uchr == '^')
618       {
619         /* Found a caret.  Skip it, and check the next character. */
620         uchr = *(++str);
621         prop = char_prop[ uchr];
622         if (prop& 64)
623         {
624           /* Hex digit.  Get char code from this and next hex digit. */
625           if (uchr <= '9')
626           {
627             hdgt = uchr- '0';           /* '0' - '9' -> 0 - 9. */
628           }
629           else
630           {
631             hdgt = ((uchr- 'A')& 7)+ 10;    /* [Aa] - [Ff] -> 10 - 15. */
632           }
633           hdgt <<= 4;                   /* X16. */
634           uchr = *(++str);              /* Next char must be hex digit. */
635           if (uchr <= '9')
636           {
637             uchr = hdgt+ uchr- '0';
638           }
639           else
640           {
641             uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
642           }
643         }
644         else if (uchr == '_')
645         {
646           /* Convert escaped "_" to " ". */
647           uchr = ' ';
648         }
649         else if (uchr == '/')
650         {
651           /* Convert escaped "/" (invalid Zip) to "?" (invalid VMS). */
652           /* Note that this is a left-over from Info-ZIP code, and is
653              probably of little value here, except perhaps to avoid
654              directory confusion which an unconverted slash might cause.
655           */
656           uchr = '?';
657         }
658         /* Else, not a hex digit.  Must be a simple escaped character
659            (or Unicode, which is not yet handled here).
660         */
661       }
662       /* Else, not a caret.  Use as-is. */
663       *strd = uchr;
664
665       /* Advance destination and source pointers. */
666       strd++;
667       str++;
668     }
669     /* Terminate the destination string. */
670     *strd = '\0';
671   }
672 }
673
674
675 static struct fileinfo *
676 ftp_parse_vms_ls (const char *file)
677 {
678   FILE *fp;
679   int dt, i, j, len;
680   int perms;
681   time_t timenow;
682   struct tm *timestruct;
683   char date_str[ 32];
684
685   char *line, *tok;              /* tokenizer */
686   struct fileinfo *dir, *l, cur; /* list creation */
687
688   fp = fopen (file, "r");
689   if (!fp)
690     {
691       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
692       return NULL;
693     }
694   dir = l = NULL;
695
696   /* Skip blank lines, Directory heading, and more blank lines. */
697
698   j = 0; /* Expecting initial blank line(s). */
699   while (1)
700     {
701       line = read_whole_line (fp);
702       if (line == NULL)
703         {
704         break;
705         }
706       else
707         {
708           i = clean_line (line);
709           if (i <= 0)
710             {
711               xfree (line); /* Free useless line storage. */
712               continue; /* Blank line.  Keep looking. */
713             }
714           else
715             {
716               if ((j == 0) && (line[ i- 1] == ']'))
717                 {
718                   /* Found Directory heading line.  Next non-blank line
719                   is significant.
720                   */
721                   j = 1;
722                 }
723               else if (!strncmp (line, "Total of ", 9))
724                 {
725                   /* Found "Total of ..." footing line.  No valid data
726                      will follow (empty directory).
727                   */
728                   xfree (line); /* Free useless line storage. */
729                   line = NULL; /* Arrange for early exit. */
730                   break;
731                 }
732               else
733                 {
734                   break; /* Must be significant data. */
735                 }
736             }
737           xfree (line); /* Free useless line storage. */
738         }
739     }
740
741   /* Read remainder of file until the next blank line or EOF. */
742
743   while (line != NULL)
744     {
745       char *p;
746
747       /* The first token is the file name.  After a long name, other
748          data may be on the following line.  A valid directory name ends
749          in ".DIR;1" (any case), although some VMS FTP servers may omit
750          the version number (";1").
751       */
752
753       tok = strtok(line, " ");
754       if (tok == NULL) tok = line;
755       DEBUGP(("file name:   '%s'\n", tok));
756
757       /* Stripping the version number on a VMS system would be wrong.
758          It may be foolish on a non-VMS system, too, but that's someone
759          else's problem.  (Define PRESERVE_VMS_VERSIONS for proper
760          operation on other operating systems.)
761
762          2005-02-23 SMS.
763          ODS5 extended file names may contain escaped semi-colons, so
764          the version number is identified as right-side decimal digits
765          led by a non-escaped semi-colon.  It may be absent.
766       */
767
768 #if (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS))
769       for (p = tok+ strlen( tok); (--p > tok) && c_isdigit( *p); );
770       if ((*p == ';') && (*(p- 1) != '^'))
771         {
772           *p = '\0';
773         }
774 #endif /* (!defined( __VMS) && !defined( PRESERVE_VMS_VERSIONS)) */
775
776       /* 2005-02-23 SMS.
777          Eliminate "^" escape characters from ODS5 extended file name.
778          (A caret is invalid in an ODS2 name, so this is always safe.)
779       */
780       eat_carets( tok);
781       DEBUGP(("file name-^: '%s'\n", tok));
782
783       /* Differentiate between a directory and any other file.  A VMS
784          listing may not include file protections (permissions).  Set a
785          default permissions value (according to the file type), which
786          may be overwritten later.  Store directory names without the
787          ".DIR;1" file type and version number, as the plain name is
788          what will work in a CWD command.
789       */
790       len = strlen( tok);
791       if (!strncasecmp( (tok+ (len- 4)), ".DIR", 4))
792         {
793           *(tok+ (len -= 4)) = '\0'; /* Discard ".DIR". */
794           cur.type  = FT_DIRECTORY;
795           cur.perms = VMS_DEFAULT_PROT_DIR;
796           DEBUGP(("Directory (nv)\n"));
797         }
798       else if (!strncasecmp( (tok+ (len- 6)), ".DIR;1", 6))
799         {
800           *(tok+ (len -= 6)) = '\0'; /* Discard ".DIR;1". */
801           cur.type  = FT_DIRECTORY;
802           cur.perms = VMS_DEFAULT_PROT_DIR;
803           DEBUGP(("Directory (v)\n"));
804         }
805       else
806         {
807           cur.type  = FT_PLAINFILE;
808           cur.perms = VMS_DEFAULT_PROT_FILE;
809           DEBUGP(("File\n"));
810         }
811       cur.name = xstrdup(tok);
812       DEBUGP(("Name: '%s'\n", cur.name));
813
814       /* Null the date and time string. */
815       *date_str = '\0';
816
817       /* VMS lacks symbolic links. */
818       cur.linkto = NULL;
819
820       /* VMS reports file sizes in (512-byte) disk blocks, not bytes,
821          hence useless for an integrity check based on byte-count.
822          Set size to unknown.
823       */
824       cur.size  = 0;
825
826       /* Get token 2, if any.  A long name may force all other data onto
827          a second line.  If needed, read the second line.
828       */
829
830       tok = strtok(NULL, " ");
831       if (tok == NULL)
832         {
833           DEBUGP(("Getting additional line.\n"));
834           xfree (line);
835           line = read_whole_line (fp);
836           if (!line)
837             {
838               DEBUGP(("EOF.  Leaving listing parser.\n"));
839               break;
840             }
841
842           /* Second line must begin with " ".  Otherwise, it's a first
843              line (and we may be confused).
844           */
845           if (i <= 0)
846             {
847               /* Blank line.  End of significant file listing. */
848               DEBUGP(("Blank line.  Leaving listing parser.\n"));
849               xfree (line); /* Free useless line storage. */
850               break;
851             }
852           else if (line[ 0] != ' ')
853             {
854               DEBUGP(("Non-blank in column 1.  Must be a new file name?\n"));
855               continue;
856             }
857           else
858             {
859               tok = strtok (line, " ");
860               if (tok == NULL)
861                 {
862                   /* Unexpected non-empty but apparently blank line. */
863                   DEBUGP(("Null token.  Leaving listing parser.\n"));
864                   xfree (line); /* Free useless line storage. */
865                   break;
866                 }
867             }
868         }
869
870       /* Analyze tokens.  (Order is not significant, except date must
871          precede time.)
872
873          Size:       ddd or ddd/ddd (where "ddd" is a decimal number)
874          Date:       DD-MMM-YYYY
875          Time:       HH:MM or HH:MM:SS or HH:MM:SS.CC
876          Owner:      [user] or [user,group]
877          Protection: (ppp,ppp,ppp,ppp) (where "ppp" is "RWED" or some
878                      subset thereof, for System, Owner, Group, World.
879
880          If permission is lacking, info may be replaced by the string:
881          "No privilege for attempted operation".
882       */
883       while (tok != NULL)
884         {
885           DEBUGP (("Token: >%s<: ", tok));
886
887           if ((strlen( tok) < 12) && (strchr( tok, '-') != NULL))
888             {
889               /* Date. */
890               DEBUGP (("Date.\n"));
891               strcpy( date_str, tok);
892               strcat( date_str, " ");
893             }
894           else if ((strlen( tok) < 12) && (strchr( tok, ':') != NULL))
895             {
896               /* Time. */
897               DEBUGP (("Time. "));
898               strncat( date_str,
899                tok,
900                (sizeof( date_str)- strlen( date_str)- 1));
901               DEBUGP (("Date time: >%s<\n", date_str));
902             }
903           else if (strchr( tok, '[') != NULL)
904             {
905               /* Owner.  (Ignore.) */
906               DEBUGP (("Owner.\n"));
907             }
908           else if (strchr( tok, '(') != NULL)
909             {
910               /* Protections (permissions). */
911               perms = 0;
912               j = 0;
913               for (i = 0; i < strlen( tok); i++)
914                 {
915                   switch (tok[ i])
916                     {
917                       case '(':
918                         break;
919                       case ')':
920                         break;
921                       case ',':
922                         if (j == 0)
923                           {
924                             perms = 0;
925                             j = 1;
926                           }
927                         else
928                           {
929                             perms <<= 3;
930                           }
931                         break;
932                     case 'R':
933                       perms |= 4;
934                       break;
935                     case 'W':
936                       perms |= 2;
937                       break;
938                     case 'E':
939                       perms |= 1;
940                       break;
941                     case 'D':
942                       perms |= 2;
943                       break;
944                     }
945                 }
946               cur.perms = perms;
947               DEBUGP (("Prot.  perms = %0o.\n", cur.perms));
948             }
949           else
950             {
951               /* Nondescript.  Probably size(s), probably in blocks.
952                  Could be "No privilege ..." message.  (Ignore.)
953               */
954               DEBUGP (("Ignored (size?).\n"));
955             }
956
957           tok = strtok (NULL, " ");
958         }
959
960       /* Tokens exhausted.  Interpret the data, and fill in the
961          structure.
962       */
963       /* Fill tm timestruct according to date-time string.  Fractional
964          seconds are ignored.  Default to current time, if conversion
965          fails.
966       */
967       timenow = time( NULL);
968       timestruct = localtime( &timenow );
969       strptime( date_str, "%d-%b-%Y %H:%M:%S", timestruct);
970
971       /* Convert struct tm local time to time_t local time. */
972       timenow = mktime (timestruct);
973       /* Offset local time according to environment variable (seconds). */
974       if ((tok = getenv( "WGET_TIMEZONE_DIFFERENTIAL")) != NULL)
975         {
976           dt = atoi( tok);
977           DEBUGP (("Time differential = %d.\n", dt));
978         }
979       else
980         {
981           dt = 0;
982         }
983
984       if (dt >= 0)
985         {
986           timenow += dt;
987         }
988       else
989         {
990           timenow -= (-dt);
991         }
992       cur.tstamp = timenow; /* Store the time-stamp. */
993       DEBUGP(("Timestamp: %ld\n", cur.tstamp));
994       cur.ptype = TT_HOUR_MIN;
995
996       /* Add the data for this item to the linked list, */
997       if (!dir)
998         {
999           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
1000           memcpy (l, &cur, sizeof (cur));
1001           l->prev = l->next = NULL;
1002         }
1003       else
1004         {
1005           cur.prev = l;
1006           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
1007           l = l->next;
1008           memcpy (l, &cur, sizeof (cur));
1009           l->next = NULL;
1010         }
1011
1012       /* Free old line storage.  Read a new line. */
1013       xfree (line);
1014       line = read_whole_line (fp);
1015       if (line != NULL)
1016         {
1017           i = clean_line (line);
1018           if (i <= 0)
1019             {
1020               /* Blank line.  End of significant file listing. */
1021               xfree (line); /* Free useless line storage. */
1022               break;
1023             }
1024         }
1025     }
1026
1027   fclose (fp);
1028   return dir;
1029 }
1030
1031
1032 /* This function switches between the correct parsing routine depending on
1033    the SYSTEM_TYPE. The system type should be based on the result of the
1034    "SYST" response of the FTP server. According to this repsonse we will
1035    use on of the three different listing parsers that cover the most of FTP
1036    servers used nowadays.  */
1037
1038 struct fileinfo *
1039 ftp_parse_ls (const char *file, const enum stype system_type)
1040 {
1041   switch (system_type)
1042     {
1043     case ST_UNIX:
1044       return ftp_parse_unix_ls (file, 0);
1045     case ST_WINNT:
1046       {
1047         /* Detect whether the listing is simulating the UNIX format */
1048         FILE *fp;
1049         int   c;
1050         fp = fopen (file, "rb");
1051         if (!fp)
1052         {
1053           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1054           return NULL;
1055         }
1056         c = fgetc(fp);
1057         fclose(fp);
1058         /* If the first character of the file is '0'-'9', it's WINNT
1059            format. */
1060         if (c >= '0' && c <='9')
1061           return ftp_parse_winnt_ls (file);
1062         else
1063           return ftp_parse_unix_ls (file, 1);
1064       }
1065     case ST_VMS:
1066       return ftp_parse_vms_ls (file);
1067     case ST_MACOS:
1068       return ftp_parse_unix_ls (file, 1);
1069     default:
1070       logprintf (LOG_NOTQUIET, _("\
1071 Unsupported listing type, trying Unix listing parser.\n"));
1072       return ftp_parse_unix_ls (file, 0);
1073     }
1074 }
1075 \f
1076 /* Stuff for creating FTP index. */
1077
1078 /* The function creates an HTML index containing references to given
1079    directories and files on the appropriate host.  The references are
1080    FTP.  */
1081 uerr_t
1082 ftp_index (const char *file, struct url *u, struct fileinfo *f)
1083 {
1084   FILE *fp;
1085   char *upwd;
1086   char *htcldir;                /* HTML-clean dir name */
1087   char *htclfile;               /* HTML-clean file name */
1088   char *urlclfile;              /* URL-clean file name */
1089
1090   if (!output_stream)
1091     {
1092       fp = fopen (file, "wb");
1093       if (!fp)
1094         {
1095           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
1096           return FOPENERR;
1097         }
1098     }
1099   else
1100     fp = output_stream;
1101   if (u->user)
1102     {
1103       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
1104
1105       tmpu = url_escape (u->user);
1106       tmpp = u->passwd ? url_escape (u->passwd) : NULL;
1107       if (tmpp)
1108         upwd = concat_strings (tmpu, ":", tmpp, "@", (char *) 0);
1109       else
1110         upwd = concat_strings (tmpu, "@", (char *) 0);
1111       xfree (tmpu);
1112       xfree_null (tmpp);
1113     }
1114   else
1115     upwd = xstrdup ("");
1116
1117   htcldir = html_quote_string (u->dir);
1118
1119   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
1120   fprintf (fp, "<html>\n<head>\n<title>");
1121   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1122   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
1123   fprintf (fp, _("Index of /%s on %s:%d"), htcldir, u->host, u->port);
1124   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
1125
1126   while (f)
1127     {
1128       fprintf (fp, "  ");
1129       if (f->tstamp != -1)
1130         {
1131           /* #### Should we translate the months?  Or, even better, use
1132              ISO 8601 dates?  */
1133           static const char *months[] = {
1134             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1135             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1136           };
1137           time_t tstamp = f->tstamp;
1138           struct tm *ptm = localtime (&tstamp);
1139
1140           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
1141                   ptm->tm_mday);
1142           if (f->ptype == TT_HOUR_MIN)
1143             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
1144           else
1145             fprintf (fp, "       ");
1146         }
1147       else
1148         fprintf (fp, _("time unknown       "));
1149       switch (f->type)
1150         {
1151         case FT_PLAINFILE:
1152           fprintf (fp, _("File        "));
1153           break;
1154         case FT_DIRECTORY:
1155           fprintf (fp, _("Directory   "));
1156           break;
1157         case FT_SYMLINK:
1158           fprintf (fp, _("Link        "));
1159           break;
1160         default:
1161           fprintf (fp, _("Not sure    "));
1162           break;
1163         }
1164       htclfile = html_quote_string (f->name);
1165       urlclfile = url_escape_unsafe_and_reserved (f->name);
1166       fprintf (fp, "<a href=\"ftp://%s%s:%d", upwd, u->host, u->port);
1167       if (*u->dir != '/')
1168         putc ('/', fp);
1169       /* XXX: Should probably URL-escape dir components here, rather
1170        * than just HTML-escape, for consistency with the next bit where
1171        * we use urlclfile for the file component. Anyway, this is safer
1172        * than what we had... */
1173       fprintf (fp, "%s", htcldir);
1174       if (*u->dir)
1175         putc ('/', fp);
1176       fprintf (fp, "%s", urlclfile);
1177       if (f->type == FT_DIRECTORY)
1178         putc ('/', fp);
1179       fprintf (fp, "\">%s", htclfile);
1180       if (f->type == FT_DIRECTORY)
1181         putc ('/', fp);
1182       fprintf (fp, "</a> ");
1183       if (f->type == FT_PLAINFILE)
1184         fprintf (fp, _(" (%s bytes)"), number_to_static_string (f->size));
1185       else if (f->type == FT_SYMLINK)
1186         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
1187       putc ('\n', fp);
1188       xfree (htclfile);
1189       xfree (urlclfile);
1190       f = f->next;
1191     }
1192   fprintf (fp, "</pre>\n</body>\n</html>\n");
1193   xfree (htcldir);
1194   xfree (upwd);
1195   if (!output_stream)
1196     fclose (fp);
1197   else
1198     fflush (fp);
1199   return FTPOK;
1200 }