1 /* ftpparse.c, ftpparse.h: library for parsing FTP LIST responses
3 D. J. Bernstein, djb@cr.yp.to
4 http://cr.yp.to/ftpparse.html
6 Commercial use is fine, if you let me know what programs you're using this in.
8 Currently covered formats:
10 UNIX ls, with or without gid.
11 Microsoft FTP Service.
12 Windows NT FTP Server.
19 Definitely not covered:
20 Long VMS filenames, with information split across two lines.
21 NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories).
27 static long totai(long year,long month,long mday)
30 if (month >= 2) month -= 2;
31 else { month += 10; --year; }
32 result = (mday - 1) * 10 + 5 + 306 * month;
34 if (result == 365) { year -= 3; result = 1460; }
35 else result += 365 * (year % 4);
37 result += 1461 * (year % 25);
39 if (result == 36524) { year -= 3; result = 146096; }
40 else { result += 36524 * (year % 4); }
42 result += 146097 * (year - 5);
44 return result * 86400;
47 static int flagneedbase = 1;
48 static time_t base; /* time() value on this OS at the beginning of 1970 TAI */
49 static long now; /* current time */
50 static int flagneedcurrentyear = 1;
51 static long currentyear; /* approximation to current year */
53 static void initbase(void)
56 if (!flagneedbase) return;
60 base = -(totai(t->tm_year + 1900,t->tm_mon,t->tm_mday) + t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec);
61 /* assumes the right time_t, counting seconds. */
62 /* base may be slightly off if time_t counts non-leap seconds. */
66 static void initnow(void)
72 now = time((time_t *) 0) - base;
74 if (flagneedcurrentyear) {
76 if ((now % 86400) < 0) --day;
78 year = 5 + day / 146097;
80 if (day < 0) { day += 146097; --year; }
82 if (day == 146096) { year += 3; day = 36524; }
83 else { year += day / 36524; day %= 36524; }
88 if (day == 1460) { year += 3; day = 365; }
89 else { year += day / 365; day %= 365; }
91 if ((day + 5) / 306 >= 10) ++year;
93 flagneedcurrentyear = 0;
97 /* UNIX ls does not show the year for dates in the last six months. */
98 /* So we have to guess the year. */
99 /* Apparently NetWare uses ``twelve months'' instead of ``six months''; ugh. */
100 /* Some versions of ls also fail to show the year for future dates. */
101 static long guesstai(long month,long mday)
108 for (year = currentyear - 1;year < currentyear + 100;++year) {
109 t = totai(year,month,mday);
110 if (now - t < 350 * 86400)
115 static int check(char *buf,char *monthname)
117 if ((buf[0] != monthname[0]) && (buf[0] != monthname[0] - 32)) return 0;
118 if ((buf[1] != monthname[1]) && (buf[1] != monthname[1] - 32)) return 0;
119 if ((buf[2] != monthname[2]) && (buf[2] != monthname[2] - 32)) return 0;
123 static char *months[12] = {
124 "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"
127 static int getmonth(char *buf,int len)
131 for (i = 0;i < 12;++i)
132 if (check(buf,months[i])) return i;
136 static long getlong(char *buf,int len)
140 u = u * 10 + (*buf++ - '0');
144 int ftpparse(struct ftpparse *fp,char *buf,int len)
160 fp->sizetype = FTPPARSE_SIZE_UNKNOWN;
162 fp->mtimetype = FTPPARSE_MTIME_UNKNOWN;
164 fp->idtype = FTPPARSE_ID_UNKNOWN;
168 if (len < 2) /* an empty name in EPLF, with no info, could be 2 chars */
172 /* see http://pobox.com/~djb/proto/eplf.txt */
173 /* "+i8388621.29609,m824255902,/,\tdev" */
174 /* "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF" */
177 for (j = 1;j < len;++j) {
179 fp->name = buf + j + 1;
180 fp->namelen = len - j - 1;
192 fp->sizetype = FTPPARSE_SIZE_BINARY;
193 fp->size = getlong(buf + i + 1,j - i - 1);
196 fp->mtimetype = FTPPARSE_MTIME_LOCAL;
198 fp->mtime = base + getlong(buf + i + 1,j - i - 1);
201 fp->idtype = FTPPARSE_ID_FULL;
202 fp->id = buf + i + 1;
203 fp->idlen = j - i - 1;
210 /* UNIX-style listing, without inum and without blocks */
211 /* "-rw-r--r-- 1 root other 531 Jan 29 03:26 README" */
212 /* "dr-xr-xr-x 2 root other 512 Apr 8 1994 etc" */
213 /* "dr-xr-xr-x 2 root 512 Apr 8 1994 etc" */
214 /* "lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin" */
215 /* Also produced by Microsoft's FTP servers for Windows: */
216 /* "---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z" */
217 /* "d--------- 1 owner group 0 May 9 19:45 Softlib" */
218 /* Also WFTPD for MSDOS: */
219 /* "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp" */
221 /* "d [R----F--] supervisor 512 Jan 16 18:53 login" */
222 /* "- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe" */
223 /* Also NetPresenz for the Mac: */
224 /* "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit" */
225 /* "drwxrwxr-x folder 2 May 10 1996 network" */
234 if (*buf == 'd') fp->flagtrycwd = 1;
235 if (*buf == '-') fp->flagtryretr = 1;
236 if (*buf == 'l') fp->flagtrycwd = fp->flagtryretr = 1;
240 for (j = 1;j < len;++j)
241 if ((buf[j] == ' ') && (buf[j - 1] != ' ')) {
243 case 1: /* skipping perm */
246 case 2: /* skipping nlink */
248 if ((j - i == 6) && (buf[i] == 'f')) /* for NetPresenz */
251 case 3: /* skipping uid */
254 case 4: /* getting tentative size */
255 size = getlong(buf + i,j - i);
258 case 5: /* searching for month, otherwise getting tentative size */
259 month = getmonth(buf + i,j - i);
263 size = getlong(buf + i,j - i);
265 case 6: /* have size and month */
266 mday = getlong(buf + i,j - i);
269 case 7: /* have size, month, mday */
270 if ((j - i == 4) && (buf[i + 1] == ':')) {
271 hour = getlong(buf + i,1);
272 minute = getlong(buf + i + 2,2);
273 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
275 fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
276 } else if ((j - i == 5) && (buf[i + 2] == ':')) {
277 hour = getlong(buf + i,2);
278 minute = getlong(buf + i + 3,2);
279 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
281 fp->mtime = base + guesstai(month,mday) + hour * 3600 + minute * 60;
283 else if (j - i >= 4) {
284 year = getlong(buf + i,j - i);
285 fp->mtimetype = FTPPARSE_MTIME_REMOTEDAY;
287 fp->mtime = base + totai(year,month,mday);
291 fp->name = buf + j + 1;
292 fp->namelen = len - j - 1;
295 case 8: /* twiddling thumbs */
299 while ((i < len) && (buf[i] == ' ')) ++i;
306 fp->sizetype = FTPPARSE_SIZE_BINARY;
309 for (i = 0;i + 3 < fp->namelen;++i)
310 if (fp->name[i] == ' ')
311 if (fp->name[i + 1] == '-')
312 if (fp->name[i + 2] == '>')
313 if (fp->name[i + 3] == ' ') {
318 /* eliminate extra NetWare spaces */
319 if ((buf[1] == ' ') || (buf[1] == '['))
321 if (fp->name[0] == ' ')
322 if (fp->name[1] == ' ')
323 if (fp->name[2] == ' ') {
331 /* MultiNet (some spaces removed from examples) */
332 /* "00README.TXT;1 2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)" */
333 /* "CORE.DIR;1 1 8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)" */
334 /* and non-MutliNet VMS: */
335 /* "CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)" */
336 for (i = 0;i < len;++i)
343 if (buf[i - 4] == '.')
344 if (buf[i - 3] == 'D')
345 if (buf[i - 2] == 'I')
346 if (buf[i - 1] == 'R') {
352 while (buf[i] != ' ') if (++i == len) return 0;
353 while (buf[i] == ' ') if (++i == len) return 0;
354 while (buf[i] != ' ') if (++i == len) return 0;
355 while (buf[i] == ' ') if (++i == len) return 0;
357 while (buf[j] != '-') if (++j == len) return 0;
358 mday = getlong(buf + i,j - i);
359 while (buf[j] == '-') if (++j == len) return 0;
361 while (buf[j] != '-') if (++j == len) return 0;
362 month = getmonth(buf + i,j - i);
363 if (month < 0) return 0;
364 while (buf[j] == '-') if (++j == len) return 0;
366 while (buf[j] != ' ') if (++j == len) return 0;
367 year = getlong(buf + i,j - i);
368 while (buf[j] == ' ') if (++j == len) return 0;
370 while (buf[j] != ':') if (++j == len) return 0;
371 hour = getlong(buf + i,j - i);
372 while (buf[j] == ':') if (++j == len) return 0;
374 while ((buf[j] != ':') && (buf[j] != ' ')) if (++j == len) return 0;
375 minute = getlong(buf + i,j - i);
377 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
379 fp->mtime = base + totai(year,month,mday) + hour * 3600 + minute * 60;
385 /* 04-27-00 09:09PM <DIR> licensed */
386 /* 07-18-00 10:16AM <DIR> pub */
387 /* 04-14-00 03:47PM 589 readme.htm */
388 if ((*buf >= '0') && (*buf <= '9')) {
391 while (buf[j] != '-') if (++j == len) return 0;
392 month = getlong(buf + i,j - i) - 1;
393 while (buf[j] == '-') if (++j == len) return 0;
395 while (buf[j] != '-') if (++j == len) return 0;
396 mday = getlong(buf + i,j - i);
397 while (buf[j] == '-') if (++j == len) return 0;
399 while (buf[j] != ' ') if (++j == len) return 0;
400 year = getlong(buf + i,j - i);
401 if (year < 50) year += 2000;
402 if (year < 1000) year += 1900;
403 while (buf[j] == ' ') if (++j == len) return 0;
405 while (buf[j] != ':') if (++j == len) return 0;
406 hour = getlong(buf + i,j - i);
407 while (buf[j] == ':') if (++j == len) return 0;
409 while ((buf[j] != 'A') && (buf[j] != 'P')) if (++j == len) return 0;
410 minute = getlong(buf + i,j - i);
411 if (hour == 12) hour = 0;
412 if (buf[j] == 'A') if (++j == len) return 0;
413 if (buf[j] == 'P') { hour += 12; if (++j == len) return 0; }
414 if (buf[j] == 'M') if (++j == len) return 0;
416 while (buf[j] == ' ') if (++j == len) return 0;
419 while (buf[j] != ' ') if (++j == len) return 0;
423 while (buf[j] != ' ') if (++j == len) return 0;
424 fp->size = getlong(buf + i,j - i);
425 fp->sizetype = FTPPARSE_SIZE_BINARY;
428 while (buf[j] == ' ') if (++j == len) return 0;
431 fp->namelen = len - j;
433 fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
435 fp->mtime = base + totai(year,month,mday) + hour * 3600 + minute * 60;
440 /* Some useless lines, safely ignored: */
441 /* "Total of 11 Files, 10966 Blocks." (VMS) */
442 /* "total 14786" (UNIX) */
443 /* "DISK$ANONFTP:[ANONYMOUS]" (VMS) */
444 /* "Directory DISK$PCSA:[ANONYM]" (VMS) */