1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2016 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 #include "FtpListInfo.h"
22 #include "FileSet.h"
23 #include <assert.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include "xstring.h"
27 #include <ctype.h>
28 #include "misc.h"
29 #include "ftpclass.h"
30 #include "ascii_ctype.h"
31 
32 #define number_of_parsers 7
33 
Parse(const char * buf,int len)34 FileSet *FtpListInfo::Parse(const char *buf,int len)
35 {
36    if(mode==FA::LONG_LIST || mode==FA::MP_LIST)
37    {
38       if(len==0 && mode==FA::LONG_LIST
39       && !ResMgr::QueryBool("ftp:list-empty-ok",session->GetHostName()))
40       {
41 	 mode=FA::LIST;
42 	 return 0;
43       }
44       int err;
45       FileSet *set=session->ParseLongList(buf,len,&err);
46       if(!set || err>0)
47       {
48 	 if(mode==FA::MP_LIST)
49 	    mode=FA::LONG_LIST;
50 	 else
51 	    mode=FA::LIST;
52       }
53       return set;
54    }
55    else
56    {
57       return ParseShortList(buf,len);
58    }
59 }
60 
ParseLongList(const char * buf,int len,int * err_ret) const61 FileSet *Ftp::ParseLongList(const char *buf,int len,int *err_ret) const
62 {
63    if(err_ret)
64       *err_ret=0;
65 
66    int err[number_of_parsers];
67    FileSet *set[number_of_parsers];
68    int i;
69    for(i=0; i<number_of_parsers; i++)
70    {
71       err[i]=0;
72       set[i]=new FileSet;
73    }
74 
75    xstring line;
76    xstring tmp_line;
77 
78    FtpLineParser guessed_parser=0;
79    FileSet **the_set=0;
80    int *the_err=0;
81    int *best_err1=&err[0];
82    int *best_err2=&err[1];
83 
84    const char *tz=Query("timezone",hostname);
85 
86    for(;;)
87    {
88       const char *nl=(char*)memchr(buf,'\n',len);
89       if(!nl)
90 	 break;
91       line.nset(buf,nl-buf);
92       line.chomp('\r');
93       if(line.length()==0)
94       {
95 	 len-=nl+1-buf;
96 	 buf=nl+1;
97 	 continue;
98       }
99 
100       len-=nl+1-buf;
101       buf=nl+1;
102 
103       if(!guessed_parser)
104       {
105 	 for(i=0; i<number_of_parsers; i++)
106 	 {
107 	    tmp_line.set(line);	 // parser can clobber the line - work on a copy
108 	    FileInfo *info=(*line_parsers[i])(tmp_line.get_non_const(),&err[i],tz);
109 	    if(info && info->name.length()>1)
110 	       info->name.chomp('/');
111 	    if(info && !strchr(info->name,'/'))
112 	       set[i]->Add(info);
113 	    else
114 	       delete info;
115 
116 	    if(*best_err1>err[i])
117 	       best_err1=&err[i];
118 	    if(*best_err2>err[i] && best_err1!=&err[i])
119 	       best_err2=&err[i];
120 	    if(*best_err1>16)
121 	       goto leave; // too many errors with best parser.
122 	 }
123 	 if(*best_err2 > (*best_err1+1)*16)
124 	 {
125 	    i=best_err1-err;
126 	    guessed_parser=line_parsers[i];
127 	    the_set=&set[i];
128 	    the_err=&err[i];
129 	 }
130       }
131       else
132       {
133 	 FileInfo *info=(*guessed_parser)(line.get_non_const(),the_err,tz);
134 	 if(info && info->name.length()>1)
135 	    info->name.chomp('/');
136 	 if(info && !strchr(info->name,'/'))
137 	    (*the_set)->Add(info);
138 	 else
139 	    delete info;
140       }
141    }
142    if(!the_set)
143    {
144       i=best_err1-err;
145       the_set=&set[i];
146       the_err=&err[i];
147    }
148 leave:
149    for(i=0; i<number_of_parsers; i++)
150       if(&set[i]!=the_set)
151 	 delete set[i];
152    if(err_ret && the_err)
153       *err_ret=*the_err;
154    return the_set?*the_set:0;
155 }
156 
ParseShortList(const char * buf,int len)157 FileSet *FtpListInfo::ParseShortList(const char *buf,int len)
158 {
159    FileSet *set=new FileSet;
160    char *line=0;
161    int line_alloc=0;
162    int line_len;
163    for(;;)
164    {
165       // workaround for some ftp servers
166       if(len>=2 && buf[0]=='.' && buf[1]=='/')
167       {
168 	 buf+=2;
169 	 len-=2;
170       }
171 #if 0 // not possible here
172       if(len>=2 && buf[0]=='/' && buf[1]=='/')
173       {
174 	 buf++;
175 	 len--;
176       }
177 #endif
178 
179       const char *nl=(const char*)memchr(buf,'\n',len);
180       if(!nl)
181 	 break;
182       line_len=nl-buf;
183       if(line_len>0 && buf[line_len-1]=='\r')
184 	 line_len--;
185       FileInfo::type type=FileInfo::UNKNOWN;
186       const char *slash=(const char*)memchr(buf,'/',line_len);
187       if(slash)
188       {
189 	 type=FileInfo::DIRECTORY;
190 	 line_len=slash-buf;
191       }
192       if(line_len==0)
193       {
194 	 len-=nl+1-buf;
195 	 buf=nl+1;
196 	 continue;
197       }
198       if(line_alloc<line_len+1)
199 	 line=string_alloca(line_alloc=line_len+128);
200       memcpy(line,buf,line_len);
201       line[line_len]=0;
202 
203       len-=nl+1-buf;
204       buf=nl+1;
205 
206       if(!strchr(line,'/'))
207       {
208 	 FileInfo *fi=new FileInfo(line);
209 	 if(type!=fi->UNKNOWN)
210 	    fi->SetType(type);
211 	 set->Add(fi);
212       }
213    }
214    return set;
215 }
216 
217 static
ParseFtpLongList_UNIX(char * line,int * err,const char * tz)218 FileInfo *ParseFtpLongList_UNIX(char *line,int *err,const char *tz)
219 {
220    int	 tmp;
221    if(sscanf(line,"total %d",&tmp)==1)
222       return 0;
223    if(!strncasecmp(line,"Status of ",10))
224       return 0;	  // STAT output.
225    if(strchr("bcpsD",line[0])) // block, char, pipe, socket, Door.
226       return 0;
227 
228    FileInfo *fi=FileInfo::parse_ls_line(line,tz);
229    if(!fi)
230    {
231       (*err)++;
232       return 0;
233    }
234    return fi;
235 }
236 
237 #define FIRST_TOKEN strtok(line," \t")
238 #define NEXT_TOKEN  strtok(NULL," \t")
239 #define ERR do{(*err)++;delete fi;return(0);}while(0)
240 
241 /*
242 07-13-98  09:06PM       <DIR>          aix
243 07-13-98  09:06PM       <DIR>          hpux
244 07-13-98  09:06PM       <DIR>          linux
245 07-13-98  09:06PM       <DIR>          ncr
246 07-13-98  09:06PM       <DIR>          solaris
247 03-18-98  06:01AM              2109440 nlxb318e.tar
248 07-02-98  11:17AM                13844 Whatsnew.txt
249 */
250 static
ParseFtpLongList_NT(char * line,int * err,const char * tz)251 FileInfo *ParseFtpLongList_NT(char *line,int *err,const char *tz)
252 {
253    char *t = FIRST_TOKEN;
254    FileInfo *fi=0;
255    if(t==0)
256       ERR;
257    int month,day,year;
258    if(sscanf(t,"%2d-%2d-%2d",&month,&day,&year)!=3)
259       ERR;
260    if(year>=70)
261       year+=1900;
262    else
263       year+=2000;
264 
265    t = NEXT_TOKEN;
266    if(t==0)
267       ERR;
268    int hour,minute;
269    char am='A'; // AM/PM is optional
270    if(sscanf(t,"%2d:%2d%c",&hour,&minute,&am)<2)
271       ERR;
272    t = NEXT_TOKEN;
273    if(t==0)
274       ERR;
275 
276    if(am=='P') // PM - after noon
277    {
278       hour+=12;
279       if(hour==24)
280 	 hour=0;
281    }
282    struct tm tms;
283    tms.tm_sec=30;	   /* seconds after the minute [0, 61]  */
284    tms.tm_min=minute;      /* minutes after the hour [0, 59] */
285    tms.tm_hour=hour;	   /* hour since midnight [0, 23] */
286    tms.tm_mday=day;	   /* day of the month [1, 31] */
287    tms.tm_mon=month-1;     /* months since January [0, 11] */
288    tms.tm_year=year-1900;  /* years since 1900 */
289    tms.tm_isdst=-1;
290 
291    fi=new FileInfo();
292    fi->SetDate(mktime_from_tz(&tms,tz),30);
293 
294    long long size;
295    if(!strcmp(t,"<DIR>"))
296       fi->SetType(fi->DIRECTORY);
297    else
298    {
299       fi->SetType(fi->NORMAL);
300       if(sscanf(t,"%lld",&size)!=1)
301 	 ERR;
302       fi->SetSize(size);
303    }
304 
305    t=strtok(NULL,"");
306    if(t==0)
307       ERR;
308    while(*t==' ')
309       t++;
310    if(*t==0)
311       ERR;
312    fi->SetName(t);
313 
314    return fi;
315 }
316 
317 /*
318 ASUSER          8192 04/26/05 13:54:16 *DIR       dir/
319 ASUSER          8192 04/26/05 13:57:34 *DIR       dir1/
320 ASUSER        365255 02/28/01 15:41:40 *STMF      readme.txt
321 ASUSER       8489625 03/18/03 09:37:00 *STMF      saved.zip
322 ASUSER        365255 02/28/01 15:41:40 *STMF      unist.old
323 */
324 static
ParseFtpLongList_AS400(char * line,int * err,const char * tz)325 FileInfo *ParseFtpLongList_AS400(char *line,int *err,const char *tz)
326 {
327    char *t = FIRST_TOKEN;
328    FileInfo *fi=0;
329    if(t==0)
330       ERR;
331    char *user=t;
332 
333    t = NEXT_TOKEN;
334    if(t==0)
335       ERR;
336    long long size;
337    if(sscanf(t,"%lld",&size)!=1)
338       ERR;
339 
340    t = NEXT_TOKEN;
341    if(t==0)
342       ERR;
343    int month,day,year;
344    if(sscanf(t,"%2d/%2d/%2d",&month,&day,&year)!=3)
345       ERR;
346    if(year>=70)
347       year+=1900;
348    else
349       year+=2000;
350 
351    t = NEXT_TOKEN;
352    if(t==0)
353       ERR;
354    int hour,minute,second;
355    if(sscanf(t,"%2d:%2d:%2d",&hour,&minute,&second)!=3)
356       ERR;
357    t = NEXT_TOKEN;
358    if(t==0)
359       ERR;
360 
361    struct tm tms;
362    tms.tm_sec=second;	   /* seconds after the minute [0, 61]  */
363    tms.tm_min=minute;      /* minutes after the hour [0, 59] */
364    tms.tm_hour=hour;	   /* hour since midnight [0, 23] */
365    tms.tm_mday=day;	   /* day of the month [1, 31] */
366    tms.tm_mon=month-1;     /* months since January [0, 11] */
367    tms.tm_year=year-1900;  /* years since 1900 */
368    tms.tm_isdst=-1;
369    time_t mtime=mktime_from_tz(&tms,tz);
370 
371    t = NEXT_TOKEN;
372    if(t==0)
373       ERR;
374    FileInfo::type type=FileInfo::UNKNOWN;
375    if(!strcmp(t,"*DIR"))
376       type=FileInfo::DIRECTORY;
377    else
378       type=FileInfo::NORMAL;
379 
380    t=strtok(NULL,"");
381    if(t==0)
382       ERR;
383    while(*t==' ')
384       t++;
385    if(*t==0)
386       ERR;
387    char *slash=strchr(t,'/');
388    if(slash)
389    {
390       if(slash==t)
391 	 return 0;
392       *slash=0;
393       type=FileInfo::DIRECTORY;
394       if(slash[1])
395       {
396 	 fi=new FileInfo(t);
397 	 fi->SetType(type);
398 	 return fi;
399       }
400    }
401    fi=new FileInfo(t);
402    fi->SetType(type);
403    fi->SetSize(size);
404    fi->SetDate(mtime,0);
405    fi->SetUser(user);
406    return fi;
407 }
408 
409 /*
410 +i774.71425,m951188401,/,	users
411 +i774.49602,m917883130,r,s79126,	jgr_www2.exe
412 
413 starts with +
414 comma separated
415 first character of field is type:
416  i - ?
417  m - modification time
418  / - means directory
419  r - means plain file
420  s - size
421  up - permissions in octal
422  \t - file name follows.
423 */
ParseFtpLongList_EPLF(char * line,int * err,const char *)424 FileInfo *ParseFtpLongList_EPLF(char *line,int *err,const char *)
425 {
426    int len=strlen(line);
427    const char *b=line;
428    FileInfo *fi=0;
429 
430    if(len<2 || b[0]!='+')
431       ERR;
432 
433    const char *name=0;
434    int name_len=0;
435    off_t size=NO_SIZE;
436    time_t date=NO_DATE;
437    long date_l;
438    long long size_ll;
439    bool dir=false;
440    bool type_known=false;
441    int perms=-1;
442 
443    const char *scan=b+1;
444    int scan_len=len-1;
445    while(scan && scan_len>0)
446    {
447       switch(*scan)
448       {
449 	 case '\t':  // the rest is file name.
450 	    name=scan+1;
451 	    name_len=scan_len-1;
452 	    scan=0;
453 	    break;
454 	 case 's':
455 	    if(1 != sscanf(scan+1,"%lld",&size_ll))
456 	       break;
457 	    size = size_ll;
458 	    break;
459 	 case 'm':
460 	    if(1 != sscanf(scan+1,"%ld",&date_l))
461 	       break;
462 	    date = date_l;
463 	    break;
464 	 case '/':
465 	    dir=true;
466 	    type_known=true;
467 	    break;
468 	 case 'r':
469 	    dir=false;
470 	    type_known=true;
471 	    break;
472 	 case 'i':
473 	    break;
474 	 case 'u':
475 	    if(scan[1]=='p')  // permissions.
476 	       if(sscanf(scan+2,"%o",&perms)!=1)
477 		  perms=-1;
478 	    break;
479 	 default:
480 	    name=0;
481 	    scan=0;
482 	    break;
483       }
484       if(scan==0 || scan_len==0)
485 	 break;
486       const char *comma=find_char(scan,scan_len,',');
487       if(comma)
488       {
489 	 scan_len-=comma+1-scan;
490 	 scan=comma+1;
491       }
492       else
493 	 break;
494    }
495    if(name==0 || !type_known)
496       ERR;
497 
498    fi=new FileInfo(xstring::get_tmp(name,name_len));
499    if(size!=NO_SIZE)
500       fi->SetSize(size);
501    if(date!=NO_DATE)
502       fi->SetDate(date,0);
503    if(type_known)
504    {
505       if(dir)
506 	 fi->SetType(fi->DIRECTORY);
507       else
508 	 fi->SetType(fi->NORMAL);
509    }
510    if(perms!=-1)
511       fi->SetMode(perms);
512 
513    return fi;
514 }
515 
516 /*
517                  0          DIR  06-27-96  11:57  PROTOCOL
518                169               11-29-94  09:20  SYSLEVEL.MPT
519 */
520 static
ParseFtpLongList_OS2(char * line,int * err,const char * tz)521 FileInfo *ParseFtpLongList_OS2(char *line,int *err,const char *tz)
522 {
523    FileInfo *fi=0;
524 
525    char *t = FIRST_TOKEN;
526    if(t==0)
527       ERR;
528 
529    long long size;
530    if(sscanf(t,"%lld",&size)!=1)
531       ERR;
532    fi=new FileInfo;
533    fi->SetSize(size);
534 
535    t = NEXT_TOKEN;
536    if(t==0)
537       ERR;
538    fi->SetType(fi->NORMAL);
539    if(!strcmp(t,"DIR"))
540    {
541       fi->SetType(fi->DIRECTORY);
542       t = NEXT_TOKEN;
543       if(t==0)
544 	 ERR;
545    }
546    int month,day,year;
547    if(sscanf(t,"%2d-%2d-%2d",&month,&day,&year)!=3)
548       ERR;
549    if(year>=70)
550       year+=1900;
551    else
552       year+=2000;
553 
554    t = NEXT_TOKEN;
555    if(t==0)
556       ERR;
557    int hour,minute;
558    if(sscanf(t,"%2d:%2d",&hour,&minute)!=3)
559       ERR;
560 
561    struct tm tms;
562    tms.tm_sec=30;	   /* seconds after the minute [0, 61]  */
563    tms.tm_min=minute;      /* minutes after the hour [0, 59] */
564    tms.tm_hour=hour;	   /* hour since midnight [0, 23] */
565    tms.tm_mday=day;	   /* day of the month [1, 31] */
566    tms.tm_mon=month-1;     /* months since January [0, 11] */
567    tms.tm_year=year-1900;  /* years since 1900 */
568    tms.tm_isdst=-1;
569    fi->SetDate(mktime_from_tz(&tms,tz),30);
570 
571    t=strtok(NULL,"");
572    if(t==0)
573       ERR;
574    while(*t==' ')
575       t++;
576    if(*t==0)
577       ERR;
578    fi->SetName(t);
579 
580    return fi;
581 }
582 
583 static
ParseFtpLongList_MacWebStar(char * line,int * err,const char * tz)584 FileInfo *ParseFtpLongList_MacWebStar(char *line,int *err,const char *tz)
585 {
586    FileInfo *fi=0;
587 
588    char *t = FIRST_TOKEN;
589    if(t==0)
590       ERR;
591 
592    fi=new FileInfo;
593    switch(t[0])
594    {
595    case('l'):  // symlink
596       fi->SetType(fi->SYMLINK);
597       break;
598    case('d'):  // directory
599       fi->SetType(fi->DIRECTORY);
600       break;
601    case('-'):  // plain file
602       fi->SetType(fi->NORMAL);
603       break;
604    case('b'): // block
605    case('c'): // char
606    case('p'): // pipe
607    case('s'): // sock
608       return 0;  // ignore
609    default:
610       ERR;
611    }
612    mode_t mode=parse_perms(t+1);
613    if(mode==(mode_t)-1)
614       ERR;
615    // permissions are meaningless here.
616 
617    // "folder" or 0
618    t = NEXT_TOKEN;
619    if(!t)
620       ERR;
621 
622    if(strcmp(t,"folder"))
623    {
624       // size?
625       t = NEXT_TOKEN;
626       if(!t)
627 	 ERR;
628       // size
629       t = NEXT_TOKEN;
630       if(!t)
631 	 ERR;
632       if(isdigit((unsigned char)*t))
633       {
634 	 long long size;
635 	 if(sscanf(t,"%lld",&size)==1)
636 	    fi->SetSize(size);
637       }
638       else
639 	 ERR;
640    }
641    else
642    {
643       // ??
644       t = NEXT_TOKEN;
645       if(!t)
646 	 ERR;
647    }
648 
649    // month
650    t = NEXT_TOKEN;
651    if(!t)
652       ERR;
653 
654    struct tm date;
655    memset(&date,0,sizeof(date));
656 
657    date.tm_mon=parse_month(t);
658    if(date.tm_mon==-1)
659       ERR;
660 
661    const char *day_of_month = NEXT_TOKEN;
662    if(!day_of_month)
663       ERR;
664    date.tm_mday=atoi(day_of_month);
665 
666    // time or year
667    t = NEXT_TOKEN;
668    if(!t)
669       ERR;
670    if(parse_year_or_time(t,&date.tm_year,&date.tm_hour,&date.tm_min)==-1)
671       ERR;
672 
673    date.tm_isdst=-1;
674    date.tm_sec=30;
675    int prec=30;
676 
677    if(date.tm_year==-1)
678       date.tm_year=guess_year(date.tm_mon,date.tm_mday,date.tm_hour,date.tm_min) - 1900;
679    else
680    {
681       date.tm_hour=12;
682       prec=12*60*60;
683    }
684 
685    fi->SetDate(mktime_from_tz(&date,tz),prec);
686 
687    char *name=strtok(NULL,"");
688    if(!name)
689       ERR;
690 
691    // no symlinks on Mac, but anyway.
692    if(fi->filetype==fi->SYMLINK)
693    {
694       char *arrow=name;
695       while((arrow=strstr(arrow," -> "))!=0)
696       {
697 	 if(arrow!=name && arrow[4]!=0)
698 	 {
699 	    *arrow=0;
700 	    fi->SetSymlink(arrow+4);
701 	    break;
702 	 }
703 	 arrow++;
704       }
705    }
706    fi->SetName(name);
707 
708    return fi;
709 }
710 
711 /*
712 Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
713 Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
714 Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
715 Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
716 Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
717 modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
718 modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
719 modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
720 */
ParseFtpLongList_MLSD(char * line,int * err,const char *)721 FileInfo *ParseFtpLongList_MLSD(char *line,int *err,const char *)
722 {
723    FileInfo *fi=0;
724 
725    const char *name=0;
726    off_t size=NO_SIZE;
727    time_t date=NO_DATE;
728    const char *owner=0;
729    const char *group=0;
730    FileInfo::type type=FileInfo::UNKNOWN;
731    int perms=-1;
732 
733    char *space=strstr(line,"; ");
734    if(space) {
735       name=space+2;
736       *space=0;
737    } else {
738       /* NcFTPd does not put a semicolon after last fact, workaround it. */
739       space=strchr(line,' ');
740       if(!space)
741 	 ERR;
742       name=space+1;
743       *space=0;
744    }
745 
746    for(char *tok=strtok(line,";"); tok; tok=strtok(0,";"))
747    {
748       if(!strcasecmp(tok,"Type=cdir")
749       || !strcasecmp(tok,"Type=pdir")
750       || !strcasecmp(tok,"Type=dir"))
751       {
752 	 type=FileInfo::DIRECTORY;
753 	 continue;
754       }
755       if(!strcasecmp(tok,"Type=file"))
756       {
757 	 type=FileInfo::NORMAL;
758 	 continue;
759       }
760       if(!strcasecmp(tok,"Type=OS.unix=symlink"))
761       {
762 	 type=FileInfo::SYMLINK;
763 	 continue;
764       }
765       if(!strncasecmp(tok,"Modify=",7))
766       {
767 	 date=Ftp::ConvertFtpDate(tok+7);
768 	 continue;
769       }
770       if(!strncasecmp(tok,"Size=",5))
771       {
772 	 long long size_ll;
773 	 if(sscanf(tok+5,"%lld",&size_ll)==1)
774 	    size=size_ll;
775 	 continue;
776       }
777       if(!strncasecmp(tok,"Perm=",5))
778       {
779 	 perms=0;
780 	 for(tok+=5; *tok; tok++)
781 	 {
782 	    switch(to_ascii_lower(*tok))
783 	    {
784 	    case 'e': perms|=0111; break;
785 	    case 'l': perms|=0444; break;
786 	    case 'r': perms|=0444; break;
787 	    case 'c': perms|=0200; break;
788 	    case 'w': perms|=0200; break;
789 	    }
790 	 }
791 	 continue;
792       }
793       if(!strncasecmp(tok,"UNIX.mode=",10))
794       {
795 	 if(sscanf(tok+10,"%o",&perms)!=1)
796 	    perms=-1;
797 	 continue;
798       }
799       if(!strncasecmp(tok,"UNIX.owner=",11))
800       {
801 	 owner=tok+11;
802 	 continue;
803       }
804       if(!strncasecmp(tok,"UNIX.group=",11))
805       {
806 	 group=tok+11;
807 	 continue;
808       }
809       if(!strncasecmp(tok,"UNIX.uid=",9))
810       {
811 	 if(!owner)
812 	    owner=tok+9;
813 	 continue;
814       }
815       if(!strncasecmp(tok,"UNIX.gid=",9))
816       {
817 	 if(!group)
818 	    group=tok+9;
819 	 continue;
820       }
821    }
822    if(name==0 || !*name || type==FileInfo::UNKNOWN)
823       ERR;
824 
825    fi=new FileInfo(name);
826    if(size!=NO_SIZE)
827       fi->SetSize(size);
828    if(date!=NO_DATE)
829       fi->SetDate(date,0);
830    fi->SetType(type);
831    if(perms!=-1)
832       fi->SetMode(perms);
833    if(owner)
834       fi->SetUser(owner);
835    if(group)
836       fi->SetGroup(group);
837 
838    return fi;
839 }
840 
841 Ftp::FtpLineParser Ftp::line_parsers[number_of_parsers]={
842    ParseFtpLongList_UNIX,
843    ParseFtpLongList_NT,
844    ParseFtpLongList_EPLF,
845    ParseFtpLongList_MLSD,
846    ParseFtpLongList_AS400,
847    ParseFtpLongList_OS2,
848    ParseFtpLongList_MacWebStar,
849 };
850