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. */
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.
12 UNIX ls, with or without gid.
13 Microsoft FTP Service.
14 Windows NT FTP Server.
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).
24 Written for maximum portability, but tested only under UNIX so far. */
29 static long totai(year,month,mday)
34 /* adapted from datetime_untai() */
35 /* about 100x faster than typical mktime() */
37 if (month >= 2) month -= 2;
38 else { month += 10; --year; }
39 result = (mday - 1) * 10 + 5 + 306 * month;
41 if (result == 365) { year -= 3; result = 1460; }
42 else result += 365 * (year % 4);
44 result += 1461 * (year % 25);
46 if (result == 36524) { year -= 3; result = 146096; }
47 else { result += 36524 * (year % 4); }
49 result += 146097 * (year - 5);
51 return result * 86400;
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 */
60 static void initbase()
63 if (!flagneedbase) return;
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. */
80 now = time((time_t *) 0) - base;
82 if (flagneedcurrentyear) {
83 /* adapted from datetime_tai() */
85 if ((now % 86400) < 0) --day;
87 year = 5 + day / 146097;
89 if (day < 0) { day += 146097; --year; }
91 if (day == 146096) { year += 3; day = 36524; }
92 else { year += day / 36524; day %= 36524; }
97 if (day == 1460) { year += 3; day = 365; }
98 else { year += day / 365; day %= 365; }
100 if ((day + 5) / 306 >= 10) ++year;
102 flagneedcurrentyear = 0;
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)
119 for (year = currentyear - 1;year < currentyear + 100;++year) {
120 t = totai(year,month,mday);
121 if (now - t < 350 * 86400)
126 static int check(buf,monthname)
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;
136 static char *months[12] = {
137 "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"
140 static int getmonth(buf,len)
146 for (i = 0;i < 12;++i)
147 if (check(buf,months[i])) return i;
151 static long getlong(buf,len)
157 u = u * 10 + (*buf++ - '0');
161 int ftpparse(fp,buf,len)
180 fp->sizetype = FTPPARSE_SIZE_UNKNOWN;
182 fp->mtimetype = FTPPARSE_MTIME_UNKNOWN;
184 fp->idtype = FTPPARSE_ID_UNKNOWN;
188 if (len < 2) /* an empty name in EPLF, with no info, could be 2 chars */
192 /* see http://pobox.com/~djb/proto/eplf.txt */
193 /* "+i8388621.29609,m824255902,/,\tdev" */
194 /* "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF" */
197 for (j = 1;j < len;++j) {
199 fp->name = buf + j + 1;
200 fp->namelen = len - j - 1;
212 fp->sizetype = FTPPARSE_SIZE_BINARY;
213 fp->size = getlong(buf + i + 1,j - i - 1);
216 fp->mtimetype = FTPPARSE_MTIME_LOCAL;
218 fp->mtime = base + getlong(buf + i + 1,j - i - 1);
221 fp->idtype = FTPPARSE_ID_FULL;
222 fp->id = buf + i + 1;
223 fp->idlen = j - i - 1;
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" */
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" */
254 if (*buf == 'd') fp->flagtrycwd = 1;
255 if (*buf == '-') fp->flagtryretr = 1;
256 if (*buf == 'l') fp->flagtrycwd = fp->flagtryretr = 1;
260 for (j = 1;j < len;++j)
261 if ((buf[j] == ' ') && (buf[j - 1] != ' ')) {
263 case 1: /* skipping perm */
266 case 2: /* skipping nlink */
268 if ((j - i == 6) && (buf[i] == 'f')) /* for NetPresenz */
271 case 3: /* skipping uid */
274 case 4: /* getting tentative size */
275 size = getlong(buf + i,j - i);
278 case 5: /* searching for month, otherwise getting tentative size */
279 month = getmonth(buf + i,j - i);
283 size = getlong(buf + i,j - i);
285 case 6: /* have size and month */
286 mday = getlong(buf + i,j - i);
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;
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;
301 fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
303 else if (j - i >= 4) {
304 year = getlong(buf + i,j - i);
305 fp->mtimetype = FTPPARSE_MTIME_REMOTEDAY;
307 fp->mtime = base + totai(year,month,mday);
311 fp->name = buf + j + 1;
312 fp->namelen = len - j - 1;
315 case 8: /* twiddling thumbs */
319 while ((i < len) && (buf[i] == ' ')) ++i;
326 fp->sizetype = FTPPARSE_SIZE_ASCII;
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] == ' ') {
338 /* eliminate extra NetWare spaces */
339 if ((buf[1] == ' ') || (buf[1] == '['))
341 if (fp->name[0] == ' ')
342 if (fp->name[1] == ' ')
343 if (fp->name[2] == ' ') {
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)
363 if (buf[i - 4] == '.')
364 if (buf[i - 3] == 'D')
365 if (buf[i - 2] == 'I')
366 if (buf[i - 1] == 'R') {
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;
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;
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;
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;
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;
394 while ((buf[j] != ':') && (buf[j] != ' ')) if (++j == len) return 0;
395 minute = getlong(buf + i,j - i);
397 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
399 fp->mtime = base + totai(year,month,mday) + hour * 3600 + minute * 60;
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) */