]> sjero.net Git - wget/blob - src/ftp-ls.c
[svn] Committed <sxsbsv854j9.fsf@florida.arsdigita.de>.
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
3
4 This file is part of Wget.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #else
27 # include <strings.h>
28 #endif
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <sys/types.h>
33 #include <ctype.h>
34 #include <errno.h>
35
36 #include "wget.h"
37 #include "utils.h"
38 #include "ftp.h"
39 #include "url.h"
40
41 /* Undef this if FTPPARSE is not available.  In that case, Wget will
42    still work with Unix FTP servers, which covers most cases.  */
43
44 #define HAVE_FTPPARSE
45
46 #ifdef HAVE_FTPPARSE
47 #include "ftpparse.h"
48 #endif
49
50 /* Converts symbolic permissions to number-style ones, e.g. string
51    rwxr-xr-x to 755.  For now, it knows nothing of
52    setuid/setgid/sticky.  ACLs are ignored.  */
53 static int
54 symperms (const char *s)
55 {
56   int perms = 0, i;
57
58   if (strlen (s) < 9)
59     return 0;
60   for (i = 0; i < 3; i++, s += 3)
61     {
62       perms <<= 3;
63       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
64                 (s[2] == 'x' || s[2] == 's'));
65     }
66   return perms;
67 }
68
69
70 /* Convert the Un*x-ish style directory listing stored in FILE to a
71    linked list of fileinfo (system-independent) entries.  The contents
72    of FILE are considered to be produced by the standard Unix `ls -la'
73    output (whatever that might be).  BSD (no group) and SYSV (with
74    group) listings are handled.
75
76    The time stamps are stored in a separate variable, time_t
77    compatible (I hope).  The timezones are ignored.  */
78 static struct fileinfo *
79 ftp_parse_unix_ls (const char *file)
80 {
81   FILE *fp;
82   static const char *months[] = {
83     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
84     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
85   };
86   int next, len, i, error, ignore;
87   int year, month, day;         /* for time analysis */
88   int hour, min, sec;
89   struct tm timestruct, *tnow;
90   time_t timenow;
91
92   char *line, *tok;             /* tokenizer */
93   struct fileinfo *dir, *l, cur; /* list creation */
94
95   fp = fopen (file, "rb");
96   if (!fp)
97     {
98       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
99       return NULL;
100     }
101   dir = l = NULL;
102
103   /* Line loop to end of file: */
104   while ((line = read_whole_line (fp)))
105     {
106       DEBUGP (("%s\n", line));
107       len = strlen (line);
108       /* Destroy <CR><LF> if present.  */
109       if (len && line[len - 1] == '\n')
110         line[--len] = '\0';
111       if (len && line[len - 1] == '\r')
112         line[--len] = '\0';
113
114       /* Skip if total...  */
115       if (!strncasecmp (line, "total", 5))
116         {
117           xfree (line);
118           continue;
119         }
120       /* Get the first token (permissions).  */
121       tok = strtok (line, " ");
122       if (!tok)
123         {
124           xfree (line);
125           continue;
126         }
127
128       cur.name = NULL;
129       cur.linkto = NULL;
130
131       /* Decide whether we deal with a file or a directory.  */
132       switch (*tok)
133         {
134         case '-':
135           cur.type = FT_PLAINFILE;
136           DEBUGP (("PLAINFILE; "));
137           break;
138         case 'd':
139           cur.type = FT_DIRECTORY;
140           DEBUGP (("DIRECTORY; "));
141           break;
142         case 'l':
143           cur.type = FT_SYMLINK;
144           DEBUGP (("SYMLINK; "));
145           break;
146         default:
147           cur.type = FT_UNKNOWN;
148           DEBUGP (("UNKOWN; "));
149           break;
150         }
151
152       cur.perms = symperms (tok + 1);
153       DEBUGP (("perms %0o; ", cur.perms));
154
155       error = ignore = 0;       /* Errnoeous and ignoring entries are
156                                    treated equally for now.  */
157       year = hour = min = sec = 0; /* Silence the compiler.  */
158       month = day = 0;
159       next = -1;
160       /* While there are tokens on the line, parse them.  Next is the
161          number of tokens left until the filename.
162
163          Use the month-name token as the "anchor" (the place where the
164          position wrt the file name is "known").  When a month name is
165          encountered, `next' is set to 5.  Also, the preceding
166          characters are parsed to get the file size.
167
168          This tactic is quite dubious when it comes to
169          internationalization issues (non-English month names), but it
170          works for now.  */
171       while ((tok = strtok (NULL, " ")))
172         {
173           --next;
174           if (next < 0)         /* a month name was not encountered */
175             {
176               for (i = 0; i < 12; i++)
177                 if (!strcmp (tok, months[i]))
178                   break;
179               /* If we got a month, it means the token before it is the
180                  size, and the filename is three tokens away.  */
181               if (i != 12)
182                 {
183                   char *t = tok - 2;
184                   long mul = 1;
185
186                   for (cur.size = 0; t > line && ISDIGIT (*t); mul *= 10, t--)
187                     cur.size += mul * (*t - '0');
188                   if (t == line)
189                     {
190                       /* Something is seriously wrong.  */
191                       error = 1;
192                       break;
193                     }
194                   month = i;
195                   next = 5;
196                   DEBUGP (("month: %s; ", months[month]));
197                 }
198             }
199           else if (next == 4)   /* days */
200             {
201               if (tok[1])       /* two-digit... */
202                 day = 10 * (*tok - '0') + tok[1] - '0';
203               else              /* ...or one-digit */
204                 day = *tok - '0';
205               DEBUGP (("day: %d; ", day));
206             }
207           else if (next == 3)
208             {
209               /* This ought to be either the time, or the year.  Let's
210                  be flexible!
211
212                  If we have a number x, it's a year.  If we have x:y,
213                  it's hours and minutes.  If we have x:y:z, z are
214                  seconds.  */
215               year = 0;
216               min = hour = sec = 0;
217               /* We must deal with digits.  */
218               if (ISDIGIT (*tok))
219                 {
220                   /* Suppose it's year.  */
221                   for (; ISDIGIT (*tok); tok++)
222                     year = (*tok - '0') + 10 * year;
223                   if (*tok == ':')
224                     {
225                       /* This means these were hours!  */
226                       hour = year;
227                       year = 0;
228                       ++tok;
229                       /* Get the minutes...  */
230                       for (; ISDIGIT (*tok); tok++)
231                         min = (*tok - '0') + 10 * min;
232                       if (*tok == ':')
233                         {
234                           /* ...and the seconds.  */
235                           ++tok;
236                           for (; ISDIGIT (*tok); tok++)
237                             sec = (*tok - '0') + 10 * sec;
238                         }
239                     }
240                 }
241               if (year)
242                 DEBUGP (("year: %d (no tm); ", year));
243               else
244                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
245             }
246           else if (next == 2)    /* The file name */
247             {
248               int fnlen;
249               char *p;
250
251               /* Since the file name may contain a SPC, it is possible
252                  for strtok to handle it wrong.  */
253               fnlen = strlen (tok);
254               if (fnlen < len - (tok - line))
255                 {
256                   /* So we have a SPC in the file name.  Restore the
257                      original.  */
258                   tok[fnlen] = ' ';
259                   /* If the file is a symbolic link, it should have a
260                      ` -> ' somewhere.  */
261                   if (cur.type == FT_SYMLINK)
262                     {
263                       p = strstr (tok, " -> ");
264                       if (!p)
265                         {
266                           error = 1;
267                           break;
268                         }
269                       cur.linkto = xstrdup (p + 4);
270                       DEBUGP (("link to: %s\n", cur.linkto));
271                       /* And separate it from the file name.  */
272                       *p = '\0';
273                     }
274                 }
275               /* If we have the filename, add it to the list of files or
276                  directories.  */
277               /* "." and ".." are an exception!  */
278               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
279                 {
280                   DEBUGP (("\nIgnoring `.' and `..'; "));
281                   ignore = 1;
282                   break;
283                 }
284               /* Some FTP sites choose to have ls -F as their default
285                  LIST output, which marks the symlinks with a trailing
286                  `@', directory names with a trailing `/' and
287                  executables with a trailing `*'.  This is no problem
288                  unless encountering a symbolic link ending with `@',
289                  or an executable ending with `*' on a server without
290                  default -F output.  I believe these cases are very
291                  rare.  */
292               fnlen = strlen (tok); /* re-calculate `fnlen' */
293               cur.name = (char *)xmalloc (fnlen + 1);
294               memcpy (cur.name, tok, fnlen + 1);
295               if (fnlen)
296                 {
297                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
298                     {
299                       cur.name[fnlen - 1] = '\0';
300                       DEBUGP (("trailing `/' on dir.\n"));
301                     }
302                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
303                     {
304                       cur.name[fnlen - 1] = '\0';
305                       DEBUGP (("trailing `@' on link.\n"));
306                     }
307                   else if (cur.type == FT_PLAINFILE
308                            && (cur.perms & 0111)
309                            && cur.name[fnlen - 1] == '*')
310                     {
311                       cur.name[fnlen - 1] = '\0';
312                       DEBUGP (("trailing `*' on exec.\n"));
313                     }
314                 } /* if (fnlen) */
315               else
316                 error = 1;
317               break;
318             }
319           else
320             abort ();
321         } /* while */
322
323       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
324         error = 1;
325
326       DEBUGP (("\n"));
327
328       if (error || ignore)
329         {
330           DEBUGP (("Skipping.\n"));
331           FREE_MAYBE (cur.name);
332           FREE_MAYBE (cur.linkto);
333           xfree (line);
334           continue;
335         }
336
337       if (!dir)
338         {
339           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
340           memcpy (l, &cur, sizeof (cur));
341           l->prev = l->next = NULL;
342         }
343       else
344         {
345           cur.prev = l;
346           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
347           l = l->next;
348           memcpy (l, &cur, sizeof (cur));
349           l->next = NULL;
350         }
351       /* Get the current time.  */
352       timenow = time (NULL);
353       tnow = localtime (&timenow);
354       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
355       timestruct.tm_sec   = sec;
356       timestruct.tm_min   = min;
357       timestruct.tm_hour  = hour;
358       timestruct.tm_mday  = day;
359       timestruct.tm_mon   = month;
360       if (year == 0)
361         {
362           /* Some listings will not specify the year if it is "obvious"
363              that the file was from the previous year.  E.g. if today
364              is 97-01-12, and you see a file of Dec 15th, its year is
365              1996, not 1997.  Thanks to Vladimir Volovich for
366              mentioning this!  */
367           if (month > tnow->tm_mon)
368             timestruct.tm_year = tnow->tm_year - 1;
369           else
370             timestruct.tm_year = tnow->tm_year;
371         }
372       else
373         timestruct.tm_year = year;
374       if (timestruct.tm_year >= 1900)
375         timestruct.tm_year -= 1900;
376       timestruct.tm_wday  = 0;
377       timestruct.tm_yday  = 0;
378       timestruct.tm_isdst = -1;
379       l->tstamp = mktime (&timestruct); /* store the time-stamp */
380
381       xfree (line);
382     }
383
384   fclose (fp);
385   return dir;
386 }
387
388 #ifdef HAVE_FTPPARSE
389
390 /* This is a "glue function" that connects the ftpparse interface to
391    the interface Wget expects.  ftpparse is used to parse listings
392    from servers other than Unix, like those running VMS or NT. */
393
394 static struct fileinfo *
395 ftp_parse_nonunix_ls (const char *file)
396 {
397   FILE *fp;
398   int len;
399
400   char *line;          /* tokenizer */
401   struct fileinfo *dir, *l, cur; /* list creation */
402
403   fp = fopen (file, "rb");
404   if (!fp)
405     {
406       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
407       return NULL;
408     }
409   dir = l = NULL;
410
411   /* Line loop to end of file: */
412   while ((line = read_whole_line (fp)))
413     {
414       struct ftpparse fp;
415
416       DEBUGP (("%s\n", line));
417       len = strlen (line);
418       /* Destroy <CR><LF> if present.  */
419       if (len && line[len - 1] == '\n')
420         line[--len] = '\0';
421       if (len && line[len - 1] == '\r')
422         line[--len] = '\0';
423
424       if (ftpparse(&fp, line, len))
425         {
426           cur.size = fp.size;
427           cur.name = (char *)xmalloc (fp.namelen + 1);
428           memcpy (cur.name, fp.name, fp.namelen);
429           cur.name[fp.namelen] = '\0';
430           DEBUGP (("%s\n", cur.name));
431           /* No links on non-UNIX systems */
432           cur.linkto = NULL;
433           /* ftpparse won't tell us correct permisions. So lets just invent
434              something. */
435           if (fp.flagtrycwd)
436             {
437               cur.type = FT_DIRECTORY;
438               cur.perms = 0755;
439             } 
440           else 
441             {
442               cur.type = FT_PLAINFILE;
443               cur.perms = 0644;
444             }
445           if (!dir)
446             {
447               l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
448               memcpy (l, &cur, sizeof (cur));
449               l->prev = l->next = NULL;
450             }
451           else 
452             {
453               cur.prev = l;
454               l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
455               l = l->next;
456               memcpy (l, &cur, sizeof (cur));
457               l->next = NULL;
458             }
459           l->tstamp = fp.mtime;
460       }
461
462       xfree (line);
463     }
464
465   fclose (fp);
466   return dir;
467 }
468 #endif
469
470 /* This function switches between the correct parsing routine
471    depending on the SYSTEM_TYPE.  If system type is ST_UNIX, we use
472    our home-grown ftp_parse_unix_ls; otherwise, we use our interface
473    to ftpparse, also known as ftp_parse_nonunix_ls.  The system type
474    should be based on the result of the "SYST" response of the FTP
475    server.  */
476
477 struct fileinfo *
478 ftp_parse_ls (const char *file, const enum stype system_type)
479 {
480   if (system_type == ST_UNIX)
481     {
482       return ftp_parse_unix_ls (file);
483     }
484   else
485     {
486 #ifdef HAVE_FTPPARSE
487       return ftp_parse_nonunix_ls (file);
488 #else
489       /* #### Maybe log some warning here? */ 
490       return ftp_parse_unix_ls (file);
491 #endif
492     }
493 }
494 \f
495 /* Stuff for creating FTP index. */
496
497 /* The function creates an HTML index containing references to given
498    directories and files on the appropriate host.  The references are
499    FTP.  */
500 uerr_t
501 ftp_index (const char *file, struct urlinfo *u, struct fileinfo *f)
502 {
503   FILE *fp;
504   char *upwd;
505   char *htclfile;               /* HTML-clean file name */
506
507   if (!opt.dfp)
508     {
509       fp = fopen (file, "wb");
510       if (!fp)
511         {
512           logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
513           return FOPENERR;
514         }
515     }
516   else
517     fp = opt.dfp;
518   if (u->user)
519     {
520       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
521
522       tmpu = CLEANDUP (u->user);
523       tmpp = u->passwd ? CLEANDUP (u->passwd) : NULL;
524       upwd = (char *)xmalloc (strlen (tmpu)
525                              + (tmpp ? (1 + strlen (tmpp)) : 0) + 2);
526       sprintf (upwd, "%s%s%s@", tmpu, tmpp ? ":" : "", tmpp ? tmpp : "");
527       xfree (tmpu);
528       FREE_MAYBE (tmpp);
529     }
530   else
531     upwd = xstrdup ("");
532   fprintf (fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
533   fprintf (fp, "<html>\n<head>\n<title>");
534   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
535   fprintf (fp, "</title>\n</head>\n<body>\n<h1>");
536   fprintf (fp, _("Index of /%s on %s:%d"), u->dir, u->host, u->port);
537   fprintf (fp, "</h1>\n<hr>\n<pre>\n");
538   while (f)
539     {
540       fprintf (fp, "  ");
541       if (f->tstamp != -1)
542         {
543           /* #### Should we translate the months? */
544           static char *months[] = {
545             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
546             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
547           };
548           struct tm *ptm = localtime ((time_t *)&f->tstamp);
549
550           fprintf (fp, "%d %s %02d ", ptm->tm_year + 1900, months[ptm->tm_mon],
551                   ptm->tm_mday);
552           if (ptm->tm_hour)
553             fprintf (fp, "%02d:%02d  ", ptm->tm_hour, ptm->tm_min);
554           else
555             fprintf (fp, "       ");
556         }
557       else
558         fprintf (fp, _("time unknown       "));
559       switch (f->type)
560         {
561         case FT_PLAINFILE:
562           fprintf (fp, _("File        "));
563           break;
564         case FT_DIRECTORY:
565           fprintf (fp, _("Directory   "));
566           break;
567         case FT_SYMLINK:
568           fprintf (fp, _("Link        "));
569           break;
570         default:
571           fprintf (fp, _("Not sure    "));
572           break;
573         }
574       htclfile = html_quote_string (f->name);
575       fprintf (fp, "<a href=\"ftp://%s%s:%hu", upwd, u->host, u->port);
576       if (*u->dir != '/')
577         putc ('/', fp);
578       fprintf (fp, "%s", u->dir);
579       if (*u->dir)
580         putc ('/', fp);
581       fprintf (fp, "%s", htclfile);
582       if (f->type == FT_DIRECTORY)
583         putc ('/', fp);
584       fprintf (fp, "\">%s", htclfile);
585       if (f->type == FT_DIRECTORY)
586         putc ('/', fp);
587       fprintf (fp, "</a> ");
588       if (f->type == FT_PLAINFILE)
589         fprintf (fp, _(" (%s bytes)"), legible (f->size));
590       else if (f->type == FT_SYMLINK)
591         fprintf (fp, "-> %s", f->linkto ? f->linkto : "(nil)");
592       putc ('\n', fp);
593       xfree (htclfile);
594       f = f->next;
595     }
596   fprintf (fp, "</pre>\n</body>\n</html>\n");
597   xfree (upwd);
598   if (!opt.dfp)
599     fclose (fp);
600   else
601     fflush (fp);
602   return FTPOK;
603 }