]> sjero.net Git - wget/blob - src/ftp-ls.c
[svn] Initial revision
[wget] / src / ftp-ls.c
1 /* Parsing FTP `ls' output.
2    Copyright (C) 1995, 1996, 1997 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
40 /* Converts symbolic permissions to number-style ones, e.g. string
41    rwxr-xr-x to 755.  For now, it knows nothing of
42    setuid/setgid/sticky.  ACLs are ignored.  */
43 static int
44 symperms (const char *s)
45 {
46   int perms = 0, i;
47
48   if (strlen (s) < 9)
49     return 0;
50   for (i = 0; i < 3; i++, s += 3)
51     {
52       perms <<= 3;
53       perms += (((s[0] == 'r') << 2) + ((s[1] == 'w') << 1) +
54                 (s[2] == 'x' || s[2] == 's'));
55     }
56   return perms;
57 }
58
59
60 /* Convert the Un*x-ish style directory listing stored in FILE to a
61    linked list of fileinfo (system-independent) entries.  The contents
62    of FILE are considered to be produced by the standard Unix `ls -la'
63    output (whatever that might be).  BSD (no group) and SYSV (with
64    group) listings are handled.
65
66    The time stamps are stored in a separate variable, time_t
67    compatible (I hope).  The timezones are ignored.  */
68 static struct fileinfo *
69 ftp_parse_unix_ls (const char *file)
70 {
71   FILE *fp;
72   static const char *months[] = {
73     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
75   };
76   int next, len, i, error, ignore;
77   int year, month, day;         /* for time analysis */
78   int hour, min, sec;
79   struct tm timestruct, *tnow;
80   time_t timenow;
81
82   char *line, *tok;             /* tokenizer */
83   struct fileinfo *dir, *l, cur; /* list creation */
84
85   fp = fopen (file, "rb");
86   if (!fp)
87     {
88       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
89       return NULL;
90     }
91   dir = l = NULL;
92
93   /* Line loop to end of file: */
94   while ((line = read_whole_line (fp)))
95     {
96       DEBUGP (("%s\n", line));
97       len = strlen (line);
98       /* Destroy <CR> if there is one.  */
99       if (len && line[len - 1] == '\r')
100         line[--len] = '\0';
101
102       /* Skip if total...  */
103       if (!strncasecmp (line, "total", 5))
104         {
105           free (line);
106           continue;
107         }
108       /* Get the first token (permissions).  */
109       tok = strtok (line, " ");
110       if (!tok)
111         {
112           free (line);
113           continue;
114         }
115
116       cur.name = NULL;
117       cur.linkto = NULL;
118
119       /* Decide whether we deal with a file or a directory.  */
120       switch (*tok)
121         {
122         case '-':
123           cur.type = FT_PLAINFILE;
124           DEBUGP (("PLAINFILE; "));
125           break;
126         case 'd':
127           cur.type = FT_DIRECTORY;
128           DEBUGP (("DIRECTORY; "));
129           break;
130         case 'l':
131           cur.type = FT_SYMLINK;
132           DEBUGP (("SYMLINK; "));
133           break;
134         default:
135           cur.type = FT_UNKNOWN;
136           DEBUGP (("UNKOWN; "));
137           break;
138         }
139
140       cur.perms = symperms (tok + 1);
141       DEBUGP (("perms %0o; ", cur.perms));
142
143       error = ignore = 0;       /* Errnoeous and ignoring entries are
144                                    treated equally for now.  */
145       year = hour = min = sec = 0; /* Silence the compiler.  */
146       month = day = 0;
147       next = -1;
148       /* While there are tokens on the line, parse them.  Next is the
149          number of tokens left until the filename.
150
151          Use the month-name token as the "anchor" (the place where the
152          position wrt the file name is "known").  When a month name is
153          encountered, `next' is set to 5.  Also, the preceding
154          characters are parsed to get the file size.
155
156          This tactic is quite dubious when it comes to
157          internationalization issues (non-English month names), but it
158          works for now.  */
159       while ((tok = strtok (NULL, " ")))
160         {
161           --next;
162           if (next < 0)         /* a month name was not encountered */
163             {
164               for (i = 0; i < 12; i++)
165                 if (!strcmp (tok, months[i]))
166                   break;
167               /* If we got a month, it means the token before it is the
168                  size, and the filename is three tokens away.  */
169               if (i != 12)
170                 {
171                   char *t = tok - 2;
172                   long mul = 1;
173
174                   for (cur.size = 0; t > line && ISDIGIT (*t); mul *= 10, t--)
175                     cur.size += mul * (*t - '0');
176                   if (t == line)
177                     {
178                       /* Something is seriously wrong.  */
179                       error = 1;
180                       break;
181                     }
182                   month = i;
183                   next = 5;
184                   DEBUGP (("month: %s; ", months[month]));
185                 }
186             }
187           else if (next == 4)   /* days */
188             {
189               if (tok[1])       /* two-digit... */
190                 day = 10 * (*tok - '0') + tok[1] - '0';
191               else              /* ...or one-digit */
192                 day = *tok - '0';
193               DEBUGP (("day: %d; ", day));
194             }
195           else if (next == 3)
196             {
197               /* This ought to be either the time, or the year.  Let's
198                  be flexible!
199
200                  If we have a number x, it's a year.  If we have x:y,
201                  it's hours and minutes.  If we have x:y:z, z are
202                  seconds.  */
203               year = 0;
204               min = hour = sec = 0;
205               /* We must deal with digits.  */
206               if (ISDIGIT (*tok))
207                 {
208                   /* Suppose it's year.  */
209                   for (; ISDIGIT (*tok); tok++)
210                     year = (*tok - '0') + 10 * year;
211                   if (*tok == ':')
212                     {
213                       /* This means these were hours!  */
214                       hour = year;
215                       year = 0;
216                       ++tok;
217                       /* Get the minutes...  */
218                       for (; ISDIGIT (*tok); tok++)
219                         min = (*tok - '0') + 10 * min;
220                       if (*tok == ':')
221                         {
222                           /* ...and the seconds.  */
223                           ++tok;
224                           for (; ISDIGIT (*tok); tok++)
225                             sec = (*tok - '0') + 10 * sec;
226                         }
227                     }
228                 }
229               if (year)
230                 DEBUGP (("year: %d (no tm); ", year));
231               else
232                 DEBUGP (("time: %02d:%02d:%02d (no yr); ", hour, min, sec));
233             }
234           else if (next == 2)    /* The file name */
235             {
236               int fnlen;
237               char *p;
238
239               /* Since the file name may contain a SPC, it is possible
240                  for strtok to handle it wrong.  */
241               fnlen = strlen (tok);
242               if (fnlen < len - (tok - line))
243                 {
244                   /* So we have a SPC in the file name.  Restore the
245                      original.  */
246                   tok[fnlen] = ' ';
247                   /* If the file is a symbolic link, it should have a
248                      ` -> ' somewhere.  */
249                   if (cur.type == FT_SYMLINK)
250                     {
251                       p = strstr (tok, " -> ");
252                       if (!p)
253                         {
254                           error = 1;
255                           break;
256                         }
257                       cur.linkto = xstrdup (p + 4);
258                       DEBUGP (("link to: %s\n", cur.linkto));
259                       /* And separate it from the file name.  */
260                       *p = '\0';
261                     }
262                 }
263               /* If we have the filename, add it to the list of files or
264                  directories.  */
265               /* "." and ".." are an exception!  */
266               if (!strcmp (tok, ".") || !strcmp (tok, ".."))
267                 {
268                   DEBUGP (("\nIgnoring `.' and `..'; "));
269                   ignore = 1;
270                   break;
271                 }
272               /* Some FTP sites choose to have ls -F as their default
273                  LIST output, which marks the symlinks with a trailing
274                  `@', directory names with a trailing `/' and
275                  executables with a trailing `*'.  This is no problem
276                  unless encountering a symbolic link ending with `@',
277                  or an executable ending with `*' on a server without
278                  default -F output.  I believe these cases are very
279                  rare.  */
280               fnlen = strlen (tok); /* re-calculate `fnlen' */
281               cur.name = (char *)xmalloc (fnlen + 1);
282               memcpy (cur.name, tok, fnlen + 1);
283               if (fnlen)
284                 {
285                   if (cur.type == FT_DIRECTORY && cur.name[fnlen - 1] == '/')
286                     {
287                       cur.name[fnlen - 1] = '\0';
288                       DEBUGP (("trailing `/' on dir.\n"));
289                     }
290                   else if (cur.type == FT_SYMLINK && cur.name[fnlen - 1] == '@')
291                     {
292                       cur.name[fnlen - 1] = '\0';
293                       DEBUGP (("trailing `@' on link.\n"));
294                     }
295                   else if (cur.type == FT_PLAINFILE
296                            && (cur.perms & 0111)
297                            && cur.name[fnlen - 1] == '*')
298                     {
299                       cur.name[fnlen - 1] = '\0';
300                       DEBUGP (("trailing `*' on exec.\n"));
301                     }
302                 } /* if (fnlen) */
303               else
304                 error = 1;
305               break;
306             }
307           else
308             abort ();
309         } /* while */
310
311       if (!cur.name || (cur.type == FT_SYMLINK && !cur.linkto))
312         error = 1;
313
314       DEBUGP (("\n"));
315
316       if (error || ignore)
317         {
318           DEBUGP (("Skipping.\n"));
319           FREE_MAYBE (cur.name);
320           FREE_MAYBE (cur.linkto);
321           free (line);
322           continue;
323         }
324
325       if (!dir)
326         {
327           l = dir = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
328           memcpy (l, &cur, sizeof (cur));
329           l->prev = l->next = NULL;
330         }
331       else
332         {
333           cur.prev = l;
334           l->next = (struct fileinfo *)xmalloc (sizeof (struct fileinfo));
335           l = l->next;
336           memcpy (l, &cur, sizeof (cur));
337           l->next = NULL;
338         }
339       /* Get the current time.  */
340       timenow = time (NULL);
341       tnow = localtime (&timenow);
342       /* Build the time-stamp (the idea by zaga@fly.cc.fer.hr).  */
343       timestruct.tm_sec   = sec;
344       timestruct.tm_min   = min;
345       timestruct.tm_hour  = hour;
346       timestruct.tm_mday  = day;
347       timestruct.tm_mon   = month;
348       if (year == 0)
349         {
350           /* Some listings will not specify the year if it is "obvious"
351              that the file was from the previous year.  E.g. if today
352              is 97-01-12, and you see a file of Dec 15th, its year is
353              1996, not 1997.  Thanks to Vladimir Volovich for
354              mentioning this!  */
355           if (month > tnow->tm_mon)
356             timestruct.tm_year = tnow->tm_year - 1;
357           else
358             timestruct.tm_year = tnow->tm_year;
359         }
360       else
361         timestruct.tm_year = year;
362       if (timestruct.tm_year >= 1900)
363         timestruct.tm_year -= 1900;
364       timestruct.tm_wday  = 0;
365       timestruct.tm_yday  = 0;
366       timestruct.tm_isdst = -1;
367       l->tstamp = mktime (&timestruct); /* store the time-stamp */
368
369       free (line);
370     }
371
372   fclose (fp);
373   return dir;
374 }
375
376 /* This function is just a stub.  It should actually accept some kind
377    of information what system it is running on -- e.g. FPL_UNIX,
378    FPL_DOS, FPL_NT, FPL_VMS, etc. and a "guess-me" value, like
379    FPL_GUESS.  Then it would call the appropriate parsers to fill up
380    fileinfos.
381
382    Since we currently support only the Unix FTP servers, this function
383    simply returns the result of ftp_parse_unix_ls().  */
384 struct fileinfo *
385 ftp_parse_ls (const char *file)
386 {
387   return ftp_parse_unix_ls (file);
388 }