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