/* ftpparse.c, ftpparse.h: library for parsing FTP LIST responses * * Written by Uwe Ohse, 2002-07-12. * Strongly influences by Daniel J. Bernsteins ftpparse.c. * * placed in the public domain. */ /* * Currently covered: * EPLF. * UNIX ls, with or without gid. * different Windows and DOS FTP servers. * VMS, but not CMS. * NetPresenz (Mac). * NetWare. */ #include /* gmtime, time_t, time() */ #include "ftpparse.h" #include "bailout.h" #include "str.h" #include "case.h" #include "utcdate2tai.h" static int my_byte_equal(const char *s, unsigned int n, const char *t) { unsigned int i; for (i=0;i'9' || p[i]<'0') break; u*=10; u+=p[i]-'0'; } *ul=u; return i; } static unsigned int get_uint64(const char *p, unsigned int len, uint64 *ul) { uint64 u=0; unsigned int i; for (i=0;i'9' || p[i]<'0') break; u*=10; u+=p[i]-'0'; } *ul=u; return i; } /* 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 guess_year(unsigned long month,unsigned long day) { static long this_year; static struct tai yearstart; struct tai x; struct tai now; tai_now(&now); if (!this_year) { struct tai n; tai_now(&n); this_year=1970; while (1) { utcdate2tai(&yearstart,this_year+1,0,1,0,0,0); if (tai_less(&n,&yearstart)) break; this_year++; } } utcdate2tai(&x,this_year,month,day,0,0,0); if (tai_less(&now,&x)) return this_year-1; return this_year; } static int getmod (struct tai *t, const unsigned char *p, unsigned int l) { unsigned int i; unsigned long year,mon,day,hour,min,sec; year=mon=day=hour=min=sec=0; if (l<14) return 0; for (i = 0; i < l; i++) { unsigned int u; if (p[i]<'0' || p[i]>'9') return 0; u = (p[i] - '0'); switch (i) { case 0: case 1: case 2: case 3: year *= 10; year += u; break; case 4: case 5: mon *= 10; mon += u; break; case 6: case 7: day *= 10; day += u; break; case 8: case 9: hour *= 10; hour += u; break; case 10: case 11: min *= 10; min += u; break; case 12: case 13: sec *= 10; sec += u; break; } } utcdate2tai(t,year,mon-1,day,hour,min,sec); return 1; } static const char *months[12] = { "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec" } ; static int get_month(char *buf, unsigned int len) { int i; if (len < 3) return -1; #define CMP(x) \ (months[i][x]==buf[x] || months[i][x]==buf[x]+32) for (i = 0;i < 12;++i) if (CMP(0) && CMP(1) && CMP(2)) return i; return -1; } /* see http://cr.yp.to/ftp/list/eplf.html */ static int parse_eplf(struct ftpparse *f, char *buf, unsigned int len) { unsigned int start,pos; unsigned long ul; if (buf[0]!='+') return 0; start=1; for (pos = 1;pos < len;pos++) { if ('\t'==buf[pos]) { f->name=buf+pos+1; f->namelen=len-pos-1; if (!f->namelen) return 0; /* huh? */ f->format=FTPPARSE_FORMAT_EPLF; return 1; } if (',' != buf[pos]) continue; switch(buf[start]) { case '/': f->flagtrycwd=1; break; case 'r': f->flagtryretr=1; break; case 's': if (pos-start-1==0) return 0; if (get_uint64(buf+start+1,pos-start-1,&f->size) !=pos-start-1) return 0; f->sizetype=FTPPARSE_SIZE_BINARY; break; case 'm': if (pos-start-1==0) return 0; if (get_ulong(buf+start+1,pos-start-1,&ul)!=pos-start-1) return 0; tai_unix(&f->mtime,ul); f->mtimetype = FTPPARSE_MTIME_LOCAL; break; case 'i': /* refuse zero bytes length ids */ if (pos-start-1==0) return 0; f->idtype = FTPPARSE_ID_FULL; f->id=buf+start+1; f->idlen=pos-start-1; break; } start=pos+1; } return 0; } static int scan_time(const char *buf, const unsigned int len, unsigned long *h, unsigned long *m, unsigned long *s, int *type) { /* 11:48:54 */ /* 01:48:54 */ /* 1:48:54 */ /* 11:48 */ /* 11:48PM */ /* 11:48AM */ /* 11:48:54PM */ /* 11:48:54AM */ unsigned int x; unsigned int y; *h=*m=*s=0; x=get_ulong(buf,len,h); if (len==x) return 0; if (!x || x>2) return 0; if (':' != buf[x]) return 0; if (len==++x) return 0; y=get_ulong(buf+x,len-x,m); if (y!=2) return 0; x+=y; if (x!=len && ':' == buf[x]) { if (len==++x) return 0; y=get_ulong(buf+x,len-x,s); if (y!=2) return 0; x+=y; *type=FTPPARSE_MTIME_REMOTESECOND; } else *type=FTPPARSE_MTIME_REMOTEMINUTE; if (x!=len && ('A' == buf[x] || 'P' == buf[x])) { if ('P' == buf [x]) *h+=12; x++; if (len==x) return 0; if ('M' != buf[x]) return 0; x++; if (len==x) return 0; } if (len==x || buf[x]==' ') return x; return 0; } /* 04-27-00 09:09PM licensed */ /* 07-18-00 10:16AM pub */ /* 04-14-00 03:47PM 589 readme.htm */ /* note the mon-day-year! */ static int parse_msdos(struct ftpparse *f, char *buf, unsigned int len) { unsigned int pos,start; unsigned int state; unsigned long mon; unsigned long day; unsigned long year; unsigned long hour; unsigned long min; unsigned long sec; int mtimetype; unsigned int x; uint64 size=0; unsigned int flagtrycwd=0; unsigned int flagtryretr=0; int maxspaces=0; /* to keep leading spaces before dir/file name */ for (state=start=pos=0;pos or length */ if (' ' == buf[pos]) { if (get_uint64(buf+start,pos-start,&size)!=pos-start) { if (pos-start < 5 || !my_byte_equal(buf+start,5,"")) return 0; flagtrycwd=1; maxspaces=10; } else { flagtryretr=1; maxspaces=1; } state++; start=pos; } break; case 7: /* spaces */ if (' ' == buf[pos]) if (--maxspaces) continue; state++; start=pos; /* FALL THROUGH */ case 8: /* file / dir name */ f->name=buf+start; f->namelen=len-pos; f->flagtrycwd=flagtrycwd; f->flagtryretr=flagtryretr; f->mtimetype=mtimetype; if (flagtryretr) { f->size=size; f->sizetype=FTPPARSE_SIZE_BINARY; } if (!fix_year(&year)) return 0; utcdate2tai(&f->mtime,year,mon-1,day,hour,min,sec); return 1; } } return 0; } #define MAXWORDS 10 static unsigned int dosplit(char *buf, int len, char *p[], unsigned int l[]) { unsigned int count=0; int inword=0; int pos; int start; for (pos=start=0;pos2 || day>31) return 0; if (q[x] != '-') return 0; q+=x+1; m-=x+1; mon=get_month(q,m); if (-1==mon) return 0; if (q[3]!='-') return 0; q+=4; if (m<5) return 0; m-=4; x=get_ulong(q,m,&year); if (!x || q[x]!=' ') return 0; if (!fix_year(&year)) return 0; x=scan_time(p[3],l[3],&hour,&min,&sec,&mtimetype); if (x!=l[3]) return 0; f->mtimetype = mtimetype;; utcdate2tai (&f->mtime,year,mon,day,hour,min,sec); for (x=0;x4) if (p[0][x-4]=='.' && p[0][x-3]=='D' && p[0][x-2]=='I' && p[0][x-1]=='R') { x-=4; f->flagtrycwd=1; } if (!f->flagtrycwd) f->flagtryretr=1; f->namelen=x; f->name=p[0]; if (f->name[0]=='[') { /* [dir]file.maybe */ unsigned int y; for (y=1;ynamelen;y++) if (f->name[y]==']') break; if (y!=f->namelen) y++; /* skip ] */ if (y!=f->namelen) { f->name+=y; f->namelen-=y; } } return 1; } static int parse_unix(struct ftpparse *f, char *buf, int len, char *p[], int l[], unsigned int count) { /* the horror ... */ /* this list has been taken from Daniel Bernsteins ftpparse.c */ /* 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" */ /*restructured: */ /* -PERM 1 user group 531 Jan 29 03:26 README */ /* dPERM 2 user group 512 Apr 8 1994 etc */ /* dPERM 2 user 512 Apr 8 1994 etc */ /* lPERM 1 user group 7 Jan 25 00:17 bin -> usr/bin */ /* -PERM 1 user group 1803128 Jul 10 10:18 ls-lR.Z */ /* dPERM 1 user group 0 May 9 19:45 Softlib */ /* -PERM 1 user group 322 Aug 19 1996 message.ftp */ /* d [R----F--] user 512 Jan 16 18:53 login */ /* - [R----F--] user 214059 Oct 20 15:27 cx.exe */ /* -PERM 326 NUMB NUMBER Nov 22 1995 MegaPhone.sit */ /* dPERM folder 2 May 10 1996 network */ /* handled as: */ /* dPERM folder 2 May 10 1996 network */ /* 0 1 2 3 4 5 6 7 8 */ /* note the date system: MON DAY [YEAR|TIME] */ int mon=-1; /* keep gcc quiet */ unsigned long day; unsigned long year; unsigned long hour; unsigned long min; unsigned long sec; uint64 size; int flagtrycwd=0; int flagtryretr=0; unsigned int i; int x; int mtimetype; int may_have_size=0; switch(p[0][0]) { case 'd': flagtrycwd=1; break; case '-': flagtryretr=1; break; case 'l': flagtryretr=flagtrycwd=1; break; } i=3; if (l[1]==6 && my_byte_equal(p[1],l[1],"folder")) i=2; x=get_uint64(p[i],l[i],&size); if (x==l[i]) may_have_size=1; i++; while (iname=p[i]; f->namelen=buf+len-p[i]; /* "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp" */ /* "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 spacy" */ /* but: */ /* "d [R----F--] supervisor 512 Jan 16 18:53 login" */ if (p[0][1]!=' ') { while (f->name[-2]==' ') { f->name--; f->namelen++; } } if (may_have_size) { f->sizetype=FTPPARSE_SIZE_BINARY; f->size=size; } f->flagtryretr=flagtryretr; f->flagtrycwd=flagtrycwd; utcdate2tai (&f->mtime,year,mon,day,hour,min,sec); f->mtimetype=mtimetype; f->format=FTPPARSE_FORMAT_LS; /* for programs dealing with symlinks */ if ('l'==*buf) { unsigned int j; for (j=1;jnamelen-4;j++) /* 1, -4: no empty names, please */ if (f->name[j]==' ' && f->name[j+1]=='-' && f->name[j+2]=='>' && f->name[j+3]==' ') { f->symlink=f->name+j+4; f->symlinklen=f->namelen-j-4; f->namelen=j; break; } } return 1; } static int parse_supertcp(struct ftpparse *f, char *p[], int l[], unsigned int count) { unsigned long mon; unsigned long day; unsigned long year; unsigned long hour; unsigned long min; unsigned long sec; int mtimetype; uint64 size=0; /* optional, dirs */ int x; int dir=0; /* CMT 11-21-94 10:17 */ /* DESIGN1.DOC 11264 05-11-95 14:20 */ if (count<4) return 0; x=scan_time(p[3],l[3],&hour,&min,&sec,&mtimetype); if (x!=l[3]) return 0; x=get_ulong(p[2],l[2],&mon); if (x!=2 || p[2][x]!='-') return 0; x++; x+=get_ulong(p[2]+x,l[2]-x,&day); if (x!=5 || p[2][x]!='-') return 0; x++; x+=get_ulong(p[2]+x,l[2]-x,&year); if ((x!=8 && x!=10) || p[2][x]!=' ') return 0; if (!fix_year(&year)) return 0; if (my_byte_equal(p[1],5,"")) dir=1; else { x=get_uint64(p[1],l[1],&size); if (!x || p[1][x]!=' ') return 0; } f->name=p[0]; f->namelen=l[0]; f->size=size; if (!dir) f->sizetype=FTPPARSE_SIZE_BINARY; utcdate2tai (&f->mtime,year,mon,day,hour,min,sec); f->mtimetype=mtimetype; if (dir) f->flagtrycwd=1; else f->flagtryretr=1; return 1; } /* another bright re-invention of a broken wheel from the people, who * made an art of it. */ static int parse_os2(struct ftpparse *f, char *p[], int l[], unsigned int count) { /* 0 DIR 04-11-95 16:26 ADDRESS * 612 A 07-28-95 16:45 air_tra1.bag * 310992 06-28-94 09:56 INSTALL.EXE */ unsigned long mon; unsigned long day; unsigned long year; unsigned long hour; unsigned long min; unsigned long sec; int mtimetype; uint64 size; int x; unsigned int i; int dir=0; if (count<4) return 0; x=get_uint64(p[0],l[0],&size); if (!x || p[0][x]!=' ') return 0; for (i=1; i1) if (my_byte_equal(p[i-1],3,"DIR")) dir=1; i++; if (i==count) return 0; x=scan_time(p[i],l[i],&hour,&min,&sec,&mtimetype); if (x!=l[i]) return 0; i++; if (i==count) return 0; f->name=p[i]; f->namelen=l[i]; if (dir) { f->flagtrycwd=1; } else { f->flagtryretr=1; f->sizetype=FTPPARSE_SIZE_BINARY; f->size=size; } utcdate2tai (&f->mtime,year,mon,day,hour,min,sec); f->mtimetype=mtimetype; return 1; } #define SETUP() do {\ 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; \ tai_uint(&fp->mtime,0); \ fp->idtype = FTPPARSE_ID_UNKNOWN; \ fp->id = 0; \ fp->idlen = 0; \ fp->format = FTPPARSE_FORMAT_UNKNOWN; \ fp->flagbrokenmlsx=0; \ fp->symlink=0; \ fp->symlinklen=0; \ } while(0) int ftpparse_mlsx (struct ftpparse *fp, char *x, int ll, int is_mlst) { int i; uint64 size; struct tai mtime; int flagtryretr=0; int flagtrycwd=0; char *id=0; int idlen=0; int mtimetype=FTPPARSE_MTIME_UNKNOWN; int sizetype=FTPPARSE_SIZE_UNKNOWN; int flagbrokenmlsx=0; SETUP(); if (is_mlst) if (ll>1) { ll--; x++; } if (ll<2) /* empty facts, space, one-byte-filename */ return 0; for (i=0; iname = x + i; fp->namelen = ll - i; fp->sizetype = sizetype; fp->size=size; fp->mtimetype = mtimetype; fp->mtime=mtime; fp->flagtrycwd=flagtrycwd; fp->flagtryretr=flagtryretr; if (id) { fp->idtype = FTPPARSE_ID_FULL; fp->id=id; fp->idlen=idlen; } fp->flagbrokenmlsx=flagbrokenmlsx; fp->format=FTPPARSE_FORMAT_MLSX; return 1; } static int ftpparse_int(struct ftpparse *fp,char *buf,int len) { unsigned int count; char *p[MAXWORDS]; int l[MAXWORDS]; SETUP(); if (len < 2) /* an empty name in EPLF, with no info, could be 2 chars */ return 0; /* cheap cases first */ switch (*buf) { case '+': if (parse_eplf(fp,buf,len)) return 1; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (parse_msdos(fp,buf,len)) return 1; break; } count=dosplit(buf, len, p,l); switch(*buf) { case 'b': case 'c': case 'd': case 'l': case 'p': case 's': case '-': if (parse_unix(fp,buf,len,p,l,count)) return 1; break; } if (*buf==' ') { switch(p[0][0]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (parse_os2(fp,p,l,count)) return 1; break; } } if (parse_multinet(fp,p,l,count)) return 1; if (parse_supertcp(fp,p,l,count)) return 1; return 0; } int ftpparse(struct ftpparse *fp,char *buf,int len, int eat_leading_spaces) { int x=ftpparse_int(fp,buf,len); if (!x) return x; if (eat_leading_spaces && fp->format!=FTPPARSE_FORMAT_EPLF && fp->format!=FTPPARSE_FORMAT_MLSX) while (fp->namelen > 1 && fp->name[0]==' ') { /* leave at least a " " in the name */ fp->name++; fp->namelen--; } return x; }