]> sjero.net Git - wget/blob - src/ftpparse.c
[svn] Committed Jan's ftpparse patch with Hrvoje's modifications.
[wget] / src / ftpparse.c
1 /* This file is not part of Wget proper, but is included here with the
2    permission of the author.  If you wish to use Wget without
3    ftpparse, undefine HAVE_FTPPARSE in ftp-ls.c.  */
4
5 /* ftpparse.c, ftpparse.h: library for parsing FTP LIST responses
6 D. J. Bernstein, djb@pobox.com
7 19970712 (doc updated 19970810)
8 Commercial use is fine, if you let me know what programs you're using this in.
9
10 Currently covered:
11 EPLF.
12 UNIX ls, with or without gid.
13 Microsoft FTP Service.
14 Windows NT FTP Server.
15 VMS.
16 WFTPD (DOS).
17 NetPresenz (Mac).
18 NetWare.
19
20 Definitely not covered: 
21 Long VMS filenames, with information split across two lines.
22 NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories).
23
24 Written for maximum portability, but tested only under UNIX so far.  */
25
26 #include <time.h>
27 #include "ftpparse.h"
28
29 static long totai(year,month,mday)
30 long year;
31 long month;
32 long mday;
33 {
34   /* adapted from datetime_untai() */
35   /* about 100x faster than typical mktime() */
36   long result;
37   if (month >= 2) month -= 2;
38   else { month += 10; --year; }
39   result = (mday - 1) * 10 + 5 + 306 * month;
40   result /= 10;
41   if (result == 365) { year -= 3; result = 1460; }
42   else result += 365 * (year % 4);
43   year /= 4;
44   result += 1461 * (year % 25);
45   year /= 25;
46   if (result == 36524) { year -= 3; result = 146096; }
47   else { result += 36524 * (year % 4); }
48   year /= 4;
49   result += 146097 * (year - 5);
50   result += 11017;
51   return result * 86400;
52 }
53
54 static int flagneedbase = 1;
55 static time_t base; /* time() value on this OS at the beginning of 1970 TAI */
56 static long now; /* current time */
57 static int flagneedcurrentyear = 1;
58 static long currentyear; /* approximation to current year */
59
60 static void initbase()
61 {
62   struct tm *t;
63   if (!flagneedbase) return;
64
65   base = 0;
66   t = gmtime(&base);
67   base = -(totai(t->tm_year + 1900,t->tm_mon,t->tm_mday) + t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec);
68   /* time_t is assumed to be measured in TAI seconds. */
69   /* base may be slightly off if time_t is measured in UTC seconds. */
70   /* Typical software naively claims to use UTC but actually uses TAI. */
71   flagneedbase = 0;
72 }
73
74 static void initnow()
75 {
76   long day;
77   long year;
78
79   initbase();
80   now = time((time_t *) 0) - base;
81
82   if (flagneedcurrentyear) {
83     /* adapted from datetime_tai() */
84     day = now / 86400;
85     if ((now % 86400) < 0) --day;
86     day -= 11017;
87     year = 5 + day / 146097;
88     day = day % 146097;
89     if (day < 0) { day += 146097; --year; }
90     year *= 4;
91     if (day == 146096) { year += 3; day = 36524; }
92     else { year += day / 36524; day %= 36524; }
93     year *= 25;
94     year += day / 1461;
95     day %= 1461;
96     year *= 4;
97     if (day == 1460) { year += 3; day = 365; }
98     else { year += day / 365; day %= 365; }
99     day *= 10;
100     if ((day + 5) / 306 >= 10) ++year;
101     currentyear = year;
102     flagneedcurrentyear = 0;
103   }
104 }
105
106 /* UNIX ls does not show the year for dates in the last six months. */
107 /* So we have to guess the year. */
108 /* Apparently NetWare uses ``twelve months'' instead of ``six months''; ugh. */
109 /* Some versions of ls also fail to show the year for future dates. */
110 static long guesstai(month,mday)
111 long month;
112 long mday;
113 {
114   long year;
115   long t;
116
117   initnow();
118
119   for (year = currentyear - 1;year < currentyear + 100;++year) {
120     t = totai(year,month,mday);
121     if (now - t < 350 * 86400)
122       return t;
123   }
124 }
125
126 static int check(buf,monthname)
127 char *buf;
128 char *monthname;
129 {
130   if ((buf[0] != monthname[0]) && (buf[0] != monthname[0] - 32)) return 0;
131   if ((buf[1] != monthname[1]) && (buf[1] != monthname[1] - 32)) return 0;
132   if ((buf[2] != monthname[2]) && (buf[2] != monthname[2] - 32)) return 0;
133   return 1;
134 }
135
136 static char *months[12] = {
137   "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"
138 } ;
139
140 static int getmonth(buf,len)
141 char *buf;
142 int len;
143 {
144   int i;
145   if (len == 3)
146     for (i = 0;i < 12;++i)
147       if (check(buf,months[i])) return i;
148   return -1;
149 }
150
151 static long getlong(buf,len)
152 char *buf;
153 int len;
154 {
155   long u = 0;
156   while (len-- > 0)
157     u = u * 10 + (*buf++ - '0');
158   return u;
159 }
160
161 int ftpparse(fp,buf,len)
162 struct ftpparse *fp;
163 char *buf;
164 int len;
165 {
166   int i;
167   int j;
168   int state;
169   long size;
170   long year;
171   long month;
172   long mday;
173   long hour;
174   long minute;
175
176   fp->name = 0;
177   fp->namelen = 0;
178   fp->flagtrycwd = 0;
179   fp->flagtryretr = 0;
180   fp->sizetype = FTPPARSE_SIZE_UNKNOWN;
181   fp->size = 0;
182   fp->mtimetype = FTPPARSE_MTIME_UNKNOWN;
183   fp->mtime = 0;
184   fp->idtype = FTPPARSE_ID_UNKNOWN;
185   fp->id = 0;
186   fp->idlen = 0;
187
188   if (len < 2) /* an empty name in EPLF, with no info, could be 2 chars */
189     return 0;
190
191   switch(*buf) {
192     /* see http://pobox.com/~djb/proto/eplf.txt */
193     /* "+i8388621.29609,m824255902,/,\tdev" */
194     /* "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF" */
195     case '+':
196       i = 1;
197       for (j = 1;j < len;++j) {
198         if (buf[j] == 9) {
199           fp->name = buf + j + 1;
200           fp->namelen = len - j - 1;
201           return 1;
202         }
203         if (buf[j] == ',') {
204           switch(buf[i]) {
205             case '/':
206               fp->flagtrycwd = 1;
207               break;
208             case 'r':
209               fp->flagtryretr = 1;
210               break;
211             case 's':
212               fp->sizetype = FTPPARSE_SIZE_BINARY;
213               fp->size = getlong(buf + i + 1,j - i - 1);
214               break;
215             case 'm':
216               fp->mtimetype = FTPPARSE_MTIME_LOCAL;
217               initbase();
218               fp->mtime = base + getlong(buf + i + 1,j - i - 1);
219               break;
220             case 'i':
221               fp->idtype = FTPPARSE_ID_FULL;
222               fp->id = buf + i + 1;
223               fp->idlen = j - i - 1;
224           }
225           i = j + 1;
226         }
227       }
228       return 0;
229     
230     /* UNIX-style listing, without inum and without blocks */
231     /* "-rw-r--r--   1 root     other        531 Jan 29 03:26 README" */
232     /* "dr-xr-xr-x   2 root     other        512 Apr  8  1994 etc" */
233     /* "dr-xr-xr-x   2 root     512 Apr  8  1994 etc" */
234     /* "lrwxrwxrwx   1 root     other          7 Jan 25 00:17 bin -> usr/bin" */
235     /* Also produced by Microsoft's FTP servers for Windows: */
236     /* "----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z" */
237     /* "d---------   1 owner    group               0 May  9 19:45 Softlib" */
238     /* Also WFTPD for MSDOS: */
239     /* "-rwxrwxrwx   1 noone    nogroup      322 Aug 19  1996 message.ftp" */
240     /* Also NetWare: */
241     /* "d [R----F--] supervisor            512       Jan 16 18:53    login" */
242     /* "- [R----F--] rhesus             214059       Oct 20 15:27    cx.exe" */
243     /* Also NetPresenz for the Mac: */
244     /* "-------r--         326  1391972  1392298 Nov 22  1995 MegaPhone.sit" */
245     /* "drwxrwxr-x               folder        2 May 10  1996 network" */
246     case 'b':
247     case 'c':
248     case 'd':
249     case 'l':
250     case 'p':
251     case 's':
252     case '-':
253
254       if (*buf == 'd') fp->flagtrycwd = 1;
255       if (*buf == '-') fp->flagtryretr = 1;
256       if (*buf == 'l') fp->flagtrycwd = fp->flagtryretr = 1;
257
258       state = 1;
259       i = 0;
260       for (j = 1;j < len;++j)
261         if ((buf[j] == ' ') && (buf[j - 1] != ' ')) {
262           switch(state) {
263             case 1: /* skipping perm */
264               state = 2;
265               break;
266             case 2: /* skipping nlink */
267               state = 3;
268               if ((j - i == 6) && (buf[i] == 'f')) /* for NetPresenz */
269                 state = 4;
270               break;
271             case 3: /* skipping uid */
272               state = 4;
273               break;
274             case 4: /* getting tentative size */
275               size = getlong(buf + i,j - i);
276               state = 5;
277               break;
278             case 5: /* searching for month, otherwise getting tentative size */
279               month = getmonth(buf + i,j - i);
280               if (month >= 0)
281                 state = 6;
282               else
283                 size = getlong(buf + i,j - i);
284               break;
285             case 6: /* have size and month */
286               mday = getlong(buf + i,j - i);
287               state = 7;
288               break;
289             case 7: /* have size, month, mday */
290               if ((j - i == 4) && (buf[i + 1] == ':')) {
291                 hour = getlong(buf + i,1);
292                 minute = getlong(buf + i + 2,2);
293                 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
294                 initbase();
295                 fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
296               } else if ((j - i == 5) && (buf[i + 2] == ':')) {
297                 hour = getlong(buf + i,2);
298                 minute = getlong(buf + i + 3,2);
299                 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
300                 initbase();
301                 fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
302               }
303               else if (j - i >= 4) {
304                 year = getlong(buf + i,j - i);
305                 fp->mtimetype = FTPPARSE_MTIME_REMOTEDAY;
306                 initbase();
307                 fp->mtime = base + totai(year,month,mday);
308               }
309               else
310                 return 0;
311               fp->name = buf + j + 1;
312               fp->namelen = len - j - 1;
313               state = 8;
314               break;
315             case 8: /* twiddling thumbs */
316               break;
317           }
318           i = j + 1;
319           while ((i < len) && (buf[i] == ' ')) ++i;
320         }
321
322       if (state != 8)
323         return 0;
324
325       fp->size = size;
326       fp->sizetype = FTPPARSE_SIZE_ASCII;
327
328       if (*buf == 'l')
329         for (i = 0;i + 3 < fp->namelen;++i)
330           if (fp->name[i] == ' ')
331             if (fp->name[i + 1] == '-')
332               if (fp->name[i + 2] == '>')
333                 if (fp->name[i + 3] == ' ') {
334                   fp->namelen = i;
335                   break;
336                 }
337
338       /* eliminate extra NetWare spaces */
339       if ((buf[1] == ' ') || (buf[1] == '['))
340         if (fp->namelen > 3)
341           if (fp->name[0] == ' ')
342             if (fp->name[1] == ' ')
343               if (fp->name[2] == ' ') {
344                 fp->name += 3;
345                 fp->namelen -= 3;
346               }
347
348       return 1;
349   }
350
351   /* MultiNet (some spaces removed from examples) */
352   /* "00README.TXT;1      2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)" */
353   /* "CORE.DIR;1          1  8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)" */
354   /* and non-MutliNet VMS: */
355   /* "CII-MANUAL.TEX;1  213/216  29-JAN-1996 03:33:12  [ANONYMOU,ANONYMOUS]   (RWED,RWED,,)" */
356   for (i = 0;i < len;++i)
357     if (buf[i] == ';')
358       break;
359   if (i < len) {
360     fp->name = buf;
361     fp->namelen = i;
362     if (i > 4)
363       if (buf[i - 4] == '.')
364         if (buf[i - 3] == 'D')
365           if (buf[i - 2] == 'I')
366             if (buf[i - 1] == 'R') {
367               fp->namelen -= 4;
368               fp->flagtrycwd = 1;
369             }
370     if (!fp->flagtrycwd)
371       fp->flagtryretr = 1;
372     while (buf[i] != ' ') if (++i == len) return 0;
373     while (buf[i] == ' ') if (++i == len) return 0;
374     while (buf[i] != ' ') if (++i == len) return 0;
375     while (buf[i] == ' ') if (++i == len) return 0;
376     j = i;
377     while (buf[j] != '-') if (++j == len) return 0;
378     mday = getlong(buf + i,j - i);
379     while (buf[j] == '-') if (++j == len) return 0;
380     i = j;
381     while (buf[j] != '-') if (++j == len) return 0;
382     month = getmonth(buf + i,j - i);
383     if (month < 0) return 0;
384     while (buf[j] == '-') if (++j == len) return 0;
385     i = j;
386     while (buf[j] != ' ') if (++j == len) return 0;
387     year = getlong(buf + i,j - i);
388     while (buf[j] == ' ') if (++j == len) return 0;
389     i = j;
390     while (buf[j] != ':') if (++j == len) return 0;
391     hour = getlong(buf + i,j - i);
392     while (buf[j] == ':') if (++j == len) return 0;
393     i = j;
394     while ((buf[j] != ':') && (buf[j] != ' ')) if (++j == len) return 0;
395     minute = getlong(buf + i,j - i);
396
397     fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
398     initbase();
399     fp->mtime = base + totai(year,month,mday) + hour * 3600 + minute * 60;
400
401     return 1;
402   }
403
404   /* Some useless lines, safely ignored: */
405   /* "Total of 11 Files, 10966 Blocks." (VMS) */
406   /* "total 14786" (UNIX) */
407   /* "DISK$ANONFTP:[ANONYMOUS]" (VMS) */
408   /* "Directory DISK$PCSA:[ANONYM]" (VMS) */
409
410   return 0;
411 }