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