640 lines
15 KiB
C
640 lines
15 KiB
C
/* ftpparse.c, ftpparse.h: library for parsing FTP LIST responses
|
|
D. J. Bernstein, djb@pobox.com
|
|
19970712 (doc updated 19970810)
|
|
Commercial use is fine, if you let me know what programs you're using this in.
|
|
|
|
Currently covered:
|
|
EPLF.
|
|
UNIX ls, with or without gid.
|
|
Microsoft FTP Service.
|
|
Windows NT FTP Server.
|
|
VMS.
|
|
WFTPD (DOS).
|
|
NetPresenz (Mac).
|
|
NetWare.
|
|
|
|
Definitely not covered:
|
|
Long VMS filenames, with information split across two lines.
|
|
NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories).
|
|
|
|
Written for maximum portability, but tested only under UNIX so far.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /*
|
|
* HAVE_CONFIG_H
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include "ftpparse.h"
|
|
|
|
static long totai(year, month, mday)
|
|
long year;
|
|
long month;
|
|
long mday;
|
|
{
|
|
/*
|
|
* adapted from datetime_untai()
|
|
*/
|
|
/*
|
|
* about 100x faster than typical mktime()
|
|
*/
|
|
long result;
|
|
if (month >= 2)
|
|
month -= 2;
|
|
else
|
|
{
|
|
month += 10;
|
|
--year;
|
|
}
|
|
result = (mday - 1) * 10 + 5 + 306 * month;
|
|
result /= 10;
|
|
if (result == 365)
|
|
{
|
|
year -= 3;
|
|
result = 1460;
|
|
}
|
|
else
|
|
result += 365 * (year % 4);
|
|
year /= 4;
|
|
result += 1461 * (year % 25);
|
|
year /= 25;
|
|
if (result == 36524)
|
|
{
|
|
year -= 3;
|
|
result = 146096;
|
|
}
|
|
else
|
|
{
|
|
result += 36524 * (year % 4);
|
|
}
|
|
year /= 4;
|
|
result += 146097 * (year - 5);
|
|
result += 11017;
|
|
return result * 86400;
|
|
}
|
|
|
|
static int flagneedbase = 1;
|
|
static time_t base; /*
|
|
* time() value on this OS at the beginning of 1970 TAI
|
|
*/
|
|
static long now; /*
|
|
* current time
|
|
*/
|
|
static int flagneedcurrentyear = 1;
|
|
static long currentyear; /*
|
|
* approximation to current year
|
|
*/
|
|
|
|
static void initbase()
|
|
{
|
|
struct tm *t;
|
|
|
|
if (!flagneedbase)
|
|
return;
|
|
|
|
base = 0;
|
|
t = gmtime(&base);
|
|
base =
|
|
-(totai(t->tm_year + 1900, t->tm_mon, t->tm_mday) +
|
|
t->tm_hour * 3600 + t->tm_min * 60 + t->tm_sec);
|
|
/*
|
|
* time_t is assumed to be measured in TAI seconds.
|
|
*/
|
|
/*
|
|
* base may be slightly off if time_t is measured in UTC seconds.
|
|
*/
|
|
/*
|
|
* Typical software naively claims to use UTC but actually uses TAI.
|
|
*/
|
|
flagneedbase = 0;
|
|
}
|
|
|
|
static void initnow()
|
|
{
|
|
long day;
|
|
long year;
|
|
|
|
initbase();
|
|
now = time((time_t *)0) - base;
|
|
|
|
if (flagneedcurrentyear)
|
|
{
|
|
/*
|
|
* adapted from datetime_tai()
|
|
*/
|
|
day = now / 86400;
|
|
if ((now % 86400) < 0)
|
|
--day;
|
|
day -= 11017;
|
|
year = 5 + day / 146097;
|
|
day = day % 146097;
|
|
if (day < 0)
|
|
{
|
|
day += 146097;
|
|
--year;
|
|
}
|
|
year *= 4;
|
|
if (day == 146096)
|
|
{
|
|
year += 3;
|
|
day = 36524;
|
|
}
|
|
else
|
|
{
|
|
year += day / 36524;
|
|
day %= 36524;
|
|
}
|
|
year *= 25;
|
|
year += day / 1461;
|
|
day %= 1461;
|
|
year *= 4;
|
|
if (day == 1460)
|
|
{
|
|
year += 3;
|
|
day = 365;
|
|
}
|
|
else
|
|
{
|
|
year += day / 365;
|
|
day %= 365;
|
|
}
|
|
day *= 10;
|
|
if ((day + 5) / 306 >= 10)
|
|
++year;
|
|
currentyear = year;
|
|
flagneedcurrentyear = 0;
|
|
}
|
|
}
|
|
|
|
/* UNIX ls does not show the year for dates in the last six months. */
|
|
/* So we have to guess the year. */
|
|
/* Apparently NetWare uses ``twelve months'' instead of ``six months''; ugh. */
|
|
/* Some versions of ls also fail to show the year for future dates. */
|
|
static long guesstai(month, mday)
|
|
long month;
|
|
long mday;
|
|
{
|
|
long year;
|
|
long t;
|
|
|
|
initnow();
|
|
|
|
for (year = currentyear - 1; year < currentyear + 100; ++year)
|
|
{
|
|
t = totai(year, month, mday);
|
|
if (now - t < 350 * 86400)
|
|
return t;
|
|
}
|
|
/* Added by Grendel */
|
|
return 0;
|
|
}
|
|
|
|
static int check(buf, monthname)
|
|
char *buf;
|
|
char *monthname;
|
|
{
|
|
if ((buf[0] != monthname[0]) && (buf[0] != monthname[0] - 32))
|
|
return 0;
|
|
if ((buf[1] != monthname[1]) && (buf[1] != monthname[1] - 32))
|
|
return 0;
|
|
if ((buf[2] != monthname[2]) && (buf[2] != monthname[2] - 32))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static char *months[12] = {
|
|
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct",
|
|
"nov", "dec"
|
|
};
|
|
|
|
static int getmonth(buf, len)
|
|
char *buf;
|
|
int len;
|
|
{
|
|
int i;
|
|
if (len == 3)
|
|
for (i = 0; i < 12; ++i)
|
|
if (check(buf, months[i]))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
static long getlong(buf, len)
|
|
char *buf;
|
|
int len;
|
|
{
|
|
long u = 0;
|
|
while (len-- > 0)
|
|
u = u * 10 + (*buf++ - '0');
|
|
return u;
|
|
}
|
|
|
|
static long long getlonglong(buf, len)
|
|
char *buf;
|
|
int len;
|
|
{
|
|
long long u = 0;
|
|
while (len-- > 0)
|
|
u = u * 10 + (*buf++ - '0');
|
|
return u;
|
|
}
|
|
|
|
int ftpparse(fp, buf, len)
|
|
struct ftpparse *fp;
|
|
char *buf;
|
|
int len;
|
|
{
|
|
int i;
|
|
int j;
|
|
int state;
|
|
off_t size = -1;
|
|
long year;
|
|
long month = -1;
|
|
long mday = -1;
|
|
long hour;
|
|
long minute;
|
|
|
|
fp->name = 0;
|
|
fp->namelen = 0;
|
|
fp->flagtrycwd = 0;
|
|
fp->flagtryretr = 0;
|
|
fp->sizetype = FTPPARSE_SIZE_UNKNOWN;
|
|
fp->size = 0;
|
|
fp->mtimetype = FTPPARSE_MTIME_UNKNOWN;
|
|
fp->mtime = 0;
|
|
fp->idtype = FTPPARSE_ID_UNKNOWN;
|
|
fp->id = 0;
|
|
fp->idlen = 0;
|
|
|
|
if (len < 2) /*
|
|
* an empty name in EPLF, with no info, could be 2 chars
|
|
*/
|
|
return 0;
|
|
|
|
switch (*buf)
|
|
{
|
|
/*
|
|
* see http://pobox.com/~djb/proto/eplf.txt
|
|
*/
|
|
/*
|
|
* "+i8388621.29609,m824255902,/,\tdev"
|
|
*/
|
|
/*
|
|
* "+i8388621.44468,m839956783,r,s10376,\tRFCEPLF"
|
|
*/
|
|
case '+':
|
|
i = 1;
|
|
for (j = 1; j < len; ++j)
|
|
{
|
|
if (buf[j] == 9)
|
|
{
|
|
fp->name = buf + j + 1;
|
|
fp->namelen = len - j - 1;
|
|
return 1;
|
|
}
|
|
if (buf[j] == ',')
|
|
{
|
|
switch (buf[i])
|
|
{
|
|
case '/':
|
|
fp->flagtrycwd = 1;
|
|
break;
|
|
|
|
case 'r':
|
|
fp->flagtryretr = 1;
|
|
break;
|
|
|
|
case 's':
|
|
fp->sizetype = FTPPARSE_SIZE_BINARY;
|
|
fp->size = getlonglong(buf + i + 1, j - i - 1);
|
|
break;
|
|
|
|
case 'm':
|
|
fp->mtimetype = FTPPARSE_MTIME_LOCAL;
|
|
initbase();
|
|
fp->mtime = base + getlong(buf + i + 1, j - i - 1);
|
|
break;
|
|
|
|
case 'i':
|
|
fp->idtype = FTPPARSE_ID_FULL;
|
|
fp->id = buf + i + 1;
|
|
fp->idlen = j - i - 1;
|
|
}
|
|
i = j + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
/*
|
|
* UNIX-style listing, without inum and without blocks
|
|
*/
|
|
/*
|
|
* "-rw-r--r-- 1 root other 531 Jan 29 03:26 README"
|
|
*/
|
|
/*
|
|
* "dr-xr-xr-x 2 root other 512 Apr 8 1994 etc"
|
|
*/
|
|
/*
|
|
* "dr-xr-xr-x 2 root 512 Apr 8 1994 etc"
|
|
*/
|
|
/*
|
|
* "lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin"
|
|
*/
|
|
/*
|
|
* Also produced by Microsoft's FTP servers for Windows:
|
|
*/
|
|
/*
|
|
* "---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z"
|
|
*/
|
|
/*
|
|
* "d--------- 1 owner group 0 May 9 19:45 Softlib"
|
|
*/
|
|
/*
|
|
* Also WFTPD for MSDOS:
|
|
*/
|
|
/*
|
|
* "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp"
|
|
*/
|
|
/*
|
|
* Also NetWare:
|
|
*/
|
|
/*
|
|
* "d [R----F--] supervisor 512 Jan 16 18:53 login"
|
|
*/
|
|
/*
|
|
* "- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe"
|
|
*/
|
|
/*
|
|
* Also NetPresenz for the Mac:
|
|
*/
|
|
/*
|
|
* "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit"
|
|
*/
|
|
/*
|
|
* "drwxrwxr-x folder 2 May 10 1996 network"
|
|
*/
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'l':
|
|
case 'p':
|
|
case 's':
|
|
case '-':
|
|
|
|
if (*buf == 'd')
|
|
fp->flagtrycwd = 1;
|
|
if (*buf == '-')
|
|
fp->flagtryretr = 1;
|
|
if (*buf == 'l')
|
|
fp->flagtrycwd = fp->flagtryretr = 1;
|
|
|
|
state = 1;
|
|
i = 0;
|
|
for (j = 1; j < len; ++j)
|
|
if ((buf[j] == ' ') && (buf[j - 1] != ' '))
|
|
{
|
|
switch (state)
|
|
{
|
|
case 1: /*
|
|
* skipping perm
|
|
*/
|
|
state = 2;
|
|
break;
|
|
|
|
case 2: /*
|
|
* skipping nlink
|
|
*/
|
|
state = 3;
|
|
if ((j - i == 6) && (buf[i] == 'f')) /*
|
|
* for NetPresenz
|
|
*/
|
|
state = 4;
|
|
break;
|
|
|
|
case 3: /*
|
|
* skipping uid
|
|
*/
|
|
state = 4;
|
|
break;
|
|
|
|
case 4: /*
|
|
* getting tentative size
|
|
*/
|
|
size = getlonglong(buf + i, j - i);
|
|
state = 5;
|
|
break;
|
|
|
|
case 5: /*
|
|
* searching for month, otherwise getting tentative size
|
|
*/
|
|
month = getmonth(buf + i, j - i);
|
|
if (month >= 0)
|
|
state = 6;
|
|
else
|
|
size = getlonglong(buf + i, j - i);
|
|
break;
|
|
|
|
case 6: /*
|
|
* have size and month
|
|
*/
|
|
mday = getlong(buf + i, j - i);
|
|
state = 7;
|
|
break;
|
|
|
|
case 7: /*
|
|
* have size, month, mday
|
|
*/
|
|
if ((j - i == 4) && (buf[i + 1] == ':'))
|
|
{
|
|
hour = getlong(buf + i, 1);
|
|
minute = getlong(buf + i + 2, 2);
|
|
fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
|
|
initbase();
|
|
fp->mtime =
|
|
base + guesstai(month,
|
|
mday) + hour * 3600 +
|
|
minute * 60;
|
|
}
|
|
else if ((j - i == 5) && (buf[i + 2] == ':'))
|
|
{
|
|
hour = getlong(buf + i, 2);
|
|
minute = getlong(buf + i + 3, 2);
|
|
fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
|
|
initbase();
|
|
fp->mtime =
|
|
base + guesstai(month,
|
|
mday) + hour * 3600 +
|
|
minute * 60;
|
|
}
|
|
else if (j - i >= 4)
|
|
{
|
|
year = getlong(buf + i, j - i);
|
|
fp->mtimetype = FTPPARSE_MTIME_REMOTEDAY;
|
|
initbase();
|
|
fp->mtime = base + totai(year, month, mday);
|
|
}
|
|
else
|
|
return 0;
|
|
fp->name = buf + j + 1;
|
|
fp->namelen = len - j - 1;
|
|
state = 8;
|
|
break;
|
|
|
|
case 8: /*
|
|
* twiddling thumbs
|
|
*/
|
|
break;
|
|
}
|
|
i = j + 1;
|
|
while ((i < len) && (buf[i] == ' '))
|
|
++i;
|
|
}
|
|
|
|
if (state != 8)
|
|
return 0;
|
|
|
|
fp->size = size;
|
|
fp->sizetype = FTPPARSE_SIZE_ASCII;
|
|
|
|
if (*buf == 'l')
|
|
for (i = 0; i + 3 < fp->namelen; ++i)
|
|
if (fp->name[i] == ' ')
|
|
if (fp->name[i + 1] == '-')
|
|
if (fp->name[i + 2] == '>')
|
|
if (fp->name[i + 3] == ' ')
|
|
{
|
|
fp->namelen = i;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* eliminate extra NetWare spaces
|
|
*/
|
|
if ((buf[1] == ' ') || (buf[1] == '['))
|
|
if (fp->namelen > 3)
|
|
if (fp->name[0] == ' ')
|
|
if (fp->name[1] == ' ')
|
|
if (fp->name[2] == ' ')
|
|
{
|
|
fp->name += 3;
|
|
fp->namelen -= 3;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* MultiNet (some spaces removed from examples)
|
|
*/
|
|
/*
|
|
* "00README.TXT;1 2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)"
|
|
*/
|
|
/*
|
|
* "CORE.DIR;1 1 8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)"
|
|
*/
|
|
/*
|
|
* and non-MutliNet VMS:
|
|
*/
|
|
/*
|
|
* "CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)"
|
|
*/
|
|
for (i = 0; i < len; ++i)
|
|
if (buf[i] == ';')
|
|
break;
|
|
if (i < len)
|
|
{
|
|
fp->name = buf;
|
|
fp->namelen = i;
|
|
if (i > 4)
|
|
if (buf[i - 4] == '.')
|
|
if (buf[i - 3] == 'D')
|
|
if (buf[i - 2] == 'I')
|
|
if (buf[i - 1] == 'R')
|
|
{
|
|
fp->namelen -= 4;
|
|
fp->flagtrycwd = 1;
|
|
}
|
|
if (!fp->flagtrycwd)
|
|
fp->flagtryretr = 1;
|
|
while (buf[i] != ' ')
|
|
if (++i == len)
|
|
return 0;
|
|
while (buf[i] == ' ')
|
|
if (++i == len)
|
|
return 0;
|
|
while (buf[i] != ' ')
|
|
if (++i == len)
|
|
return 0;
|
|
while (buf[i] == ' ')
|
|
if (++i == len)
|
|
return 0;
|
|
j = i;
|
|
while (buf[j] != '-')
|
|
if (++j == len)
|
|
return 0;
|
|
mday = getlong(buf + i, j - i);
|
|
while (buf[j] == '-')
|
|
if (++j == len)
|
|
return 0;
|
|
i = j;
|
|
while (buf[j] != '-')
|
|
if (++j == len)
|
|
return 0;
|
|
month = getmonth(buf + i, j - i);
|
|
if (month < 0)
|
|
return 0;
|
|
while (buf[j] == '-')
|
|
if (++j == len)
|
|
return 0;
|
|
i = j;
|
|
while (buf[j] != ' ')
|
|
if (++j == len)
|
|
return 0;
|
|
year = getlong(buf + i, j - i);
|
|
while (buf[j] == ' ')
|
|
if (++j == len)
|
|
return 0;
|
|
i = j;
|
|
while (buf[j] != ':')
|
|
if (++j == len)
|
|
return 0;
|
|
hour = getlong(buf + i, j - i);
|
|
while (buf[j] == ':')
|
|
if (++j == len)
|
|
return 0;
|
|
i = j;
|
|
while ((buf[j] != ':') && (buf[j] != ' '))
|
|
if (++j == len)
|
|
return 0;
|
|
minute = getlong(buf + i, j - i);
|
|
|
|
fp->mtimetype = FTPPARSE_MTIME_REMOTEMINUTE;
|
|
initbase();
|
|
fp->mtime =
|
|
base + totai(year, month, mday) + hour * 3600 + minute * 60;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Some useless lines, safely ignored:
|
|
*/
|
|
/*
|
|
* "Total of 11 Files, 10966 Blocks." (VMS)
|
|
*/
|
|
/*
|
|
* "total 14786" (UNIX)
|
|
*/
|
|
/*
|
|
* "DISK$ANONFTP:[ANONYMOUS]" (VMS)
|
|
*/
|
|
/*
|
|
* "Directory DISK$PCSA:[ANONYM]" (VMS)
|
|
*/
|
|
|
|
return 0;
|
|
}
|