• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 
23 /**
24  * Now implemented:
25  *
26  * 1) Unix version 1
27  * drwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog
28  * 2) Unix version 2
29  * drwxr-xr-x 1 user01 ftp  512 Jan 29 1997  prog
30  * 3) Unix version 3
31  * drwxr-xr-x 1      1   1  512 Jan 29 23:32 prog
32  * 4) Unix symlink
33  * lrwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog -> prog2000
34  * 5) DOS style
35  * 01-29-97 11:32PM <DIR> prog
36  */
37 
38 #include "curl_setup.h"
39 
40 #ifndef CURL_DISABLE_FTP
41 
42 #include <curl/curl.h>
43 
44 #include "urldata.h"
45 #include "fileinfo.h"
46 #include "llist.h"
47 #include "strtoofft.h"
48 #include "ftp.h"
49 #include "ftplistparser.h"
50 #include "curl_fnmatch.h"
51 #include "curl_memory.h"
52 #include "multiif.h"
53 /* The last #include file should be: */
54 #include "memdebug.h"
55 
56 /* allocs buffer which will contain one line of LIST command response */
57 #define FTP_BUFFER_ALLOCSIZE 160
58 
59 typedef enum {
60   PL_UNIX_TOTALSIZE = 0,
61   PL_UNIX_FILETYPE,
62   PL_UNIX_PERMISSION,
63   PL_UNIX_HLINKS,
64   PL_UNIX_USER,
65   PL_UNIX_GROUP,
66   PL_UNIX_SIZE,
67   PL_UNIX_TIME,
68   PL_UNIX_FILENAME,
69   PL_UNIX_SYMLINK
70 } pl_unix_mainstate;
71 
72 typedef union {
73   enum {
74     PL_UNIX_TOTALSIZE_INIT = 0,
75     PL_UNIX_TOTALSIZE_READING
76   } total_dirsize;
77 
78   enum {
79     PL_UNIX_HLINKS_PRESPACE = 0,
80     PL_UNIX_HLINKS_NUMBER
81   } hlinks;
82 
83   enum {
84     PL_UNIX_USER_PRESPACE = 0,
85     PL_UNIX_USER_PARSING
86   } user;
87 
88   enum {
89     PL_UNIX_GROUP_PRESPACE = 0,
90     PL_UNIX_GROUP_NAME
91   } group;
92 
93   enum {
94     PL_UNIX_SIZE_PRESPACE = 0,
95     PL_UNIX_SIZE_NUMBER
96   } size;
97 
98   enum {
99     PL_UNIX_TIME_PREPART1 = 0,
100     PL_UNIX_TIME_PART1,
101     PL_UNIX_TIME_PREPART2,
102     PL_UNIX_TIME_PART2,
103     PL_UNIX_TIME_PREPART3,
104     PL_UNIX_TIME_PART3
105   } time;
106 
107   enum {
108     PL_UNIX_FILENAME_PRESPACE = 0,
109     PL_UNIX_FILENAME_NAME,
110     PL_UNIX_FILENAME_WINDOWSEOL
111   } filename;
112 
113   enum {
114     PL_UNIX_SYMLINK_PRESPACE = 0,
115     PL_UNIX_SYMLINK_NAME,
116     PL_UNIX_SYMLINK_PRETARGET1,
117     PL_UNIX_SYMLINK_PRETARGET2,
118     PL_UNIX_SYMLINK_PRETARGET3,
119     PL_UNIX_SYMLINK_PRETARGET4,
120     PL_UNIX_SYMLINK_TARGET,
121     PL_UNIX_SYMLINK_WINDOWSEOL
122   } symlink;
123 } pl_unix_substate;
124 
125 typedef enum {
126   PL_WINNT_DATE = 0,
127   PL_WINNT_TIME,
128   PL_WINNT_DIRORSIZE,
129   PL_WINNT_FILENAME
130 } pl_winNT_mainstate;
131 
132 typedef union {
133   enum {
134     PL_WINNT_TIME_PRESPACE = 0,
135     PL_WINNT_TIME_TIME
136   } time;
137   enum {
138     PL_WINNT_DIRORSIZE_PRESPACE = 0,
139     PL_WINNT_DIRORSIZE_CONTENT
140   } dirorsize;
141   enum {
142     PL_WINNT_FILENAME_PRESPACE = 0,
143     PL_WINNT_FILENAME_CONTENT,
144     PL_WINNT_FILENAME_WINEOL
145   } filename;
146 } pl_winNT_substate;
147 
148 /* This struct is used in wildcard downloading - for parsing LIST response */
149 struct ftp_parselist_data {
150   enum {
151     OS_TYPE_UNKNOWN = 0,
152     OS_TYPE_UNIX,
153     OS_TYPE_WIN_NT
154   } os_type;
155 
156   union {
157     struct {
158       pl_unix_mainstate main;
159       pl_unix_substate sub;
160     } UNIX;
161 
162     struct {
163       pl_winNT_mainstate main;
164       pl_winNT_substate sub;
165     } NT;
166   } state;
167 
168   CURLcode error;
169   struct fileinfo *file_data;
170   unsigned int item_length;
171   size_t item_offset;
172   struct {
173     size_t filename;
174     size_t user;
175     size_t group;
176     size_t time;
177     size_t perm;
178     size_t symlink_target;
179   } offsets;
180 };
181 
Curl_ftp_parselist_data_alloc(void)182 struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
183 {
184   return calloc(1, sizeof(struct ftp_parselist_data));
185 }
186 
187 
Curl_ftp_parselist_data_free(struct ftp_parselist_data ** parserp)188 void Curl_ftp_parselist_data_free(struct ftp_parselist_data **parserp)
189 {
190   struct ftp_parselist_data *parser = *parserp;
191   if(parser)
192     Curl_fileinfo_cleanup(parser->file_data);
193   free(parser);
194   *parserp = NULL;
195 }
196 
197 
Curl_ftp_parselist_geterror(struct ftp_parselist_data * pl_data)198 CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data)
199 {
200   return pl_data->error;
201 }
202 
203 
204 #define FTP_LP_MALFORMATED_PERM 0x01000000
205 
ftp_pl_get_permission(const char * str)206 static int ftp_pl_get_permission(const char *str)
207 {
208   int permissions = 0;
209   /* USER */
210   if(str[0] == 'r')
211     permissions |= 1 << 8;
212   else if(str[0] != '-')
213     permissions |= FTP_LP_MALFORMATED_PERM;
214   if(str[1] == 'w')
215     permissions |= 1 << 7;
216   else if(str[1] != '-')
217     permissions |= FTP_LP_MALFORMATED_PERM;
218 
219   if(str[2] == 'x')
220     permissions |= 1 << 6;
221   else if(str[2] == 's') {
222     permissions |= 1 << 6;
223     permissions |= 1 << 11;
224   }
225   else if(str[2] == 'S')
226     permissions |= 1 << 11;
227   else if(str[2] != '-')
228     permissions |= FTP_LP_MALFORMATED_PERM;
229   /* GROUP */
230   if(str[3] == 'r')
231     permissions |= 1 << 5;
232   else if(str[3] != '-')
233     permissions |= FTP_LP_MALFORMATED_PERM;
234   if(str[4] == 'w')
235     permissions |= 1 << 4;
236   else if(str[4] != '-')
237     permissions |= FTP_LP_MALFORMATED_PERM;
238   if(str[5] == 'x')
239     permissions |= 1 << 3;
240   else if(str[5] == 's') {
241     permissions |= 1 << 3;
242     permissions |= 1 << 10;
243   }
244   else if(str[5] == 'S')
245     permissions |= 1 << 10;
246   else if(str[5] != '-')
247     permissions |= FTP_LP_MALFORMATED_PERM;
248   /* others */
249   if(str[6] == 'r')
250     permissions |= 1 << 2;
251   else if(str[6] != '-')
252     permissions |= FTP_LP_MALFORMATED_PERM;
253   if(str[7] == 'w')
254     permissions |= 1 << 1;
255   else if(str[7] != '-')
256       permissions |= FTP_LP_MALFORMATED_PERM;
257   if(str[8] == 'x')
258     permissions |= 1;
259   else if(str[8] == 't') {
260     permissions |= 1;
261     permissions |= 1 << 9;
262   }
263   else if(str[8] == 'T')
264     permissions |= 1 << 9;
265   else if(str[8] != '-')
266     permissions |= FTP_LP_MALFORMATED_PERM;
267 
268   return permissions;
269 }
270 
ftp_pl_insert_finfo(struct connectdata * conn,struct fileinfo * infop)271 static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
272                                     struct fileinfo *infop)
273 {
274   curl_fnmatch_callback compare;
275   struct WildcardData *wc = &conn->data->wildcard;
276   struct ftp_wc *ftpwc = wc->protdata;
277   struct Curl_llist *llist = &wc->filelist;
278   struct ftp_parselist_data *parser = ftpwc->parser;
279   bool add = TRUE;
280   struct curl_fileinfo *finfo = &infop->info;
281 
282   /* move finfo pointers to b_data */
283   char *str = finfo->b_data;
284   finfo->filename       = str + parser->offsets.filename;
285   finfo->strings.group  = parser->offsets.group ?
286                           str + parser->offsets.group : NULL;
287   finfo->strings.perm   = parser->offsets.perm ?
288                           str + parser->offsets.perm : NULL;
289   finfo->strings.target = parser->offsets.symlink_target ?
290                           str + parser->offsets.symlink_target : NULL;
291   finfo->strings.time   = str + parser->offsets.time;
292   finfo->strings.user   = parser->offsets.user ?
293                           str + parser->offsets.user : NULL;
294 
295   /* get correct fnmatch callback */
296   compare = conn->data->set.fnmatch;
297   if(!compare)
298     compare = Curl_fnmatch;
299 
300   /* filter pattern-corresponding filenames */
301   Curl_set_in_callback(conn->data, true);
302   if(compare(conn->data->set.fnmatch_data, wc->pattern,
303              finfo->filename) == 0) {
304     /* discard symlink which is containing multiple " -> " */
305     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
306        (strstr(finfo->strings.target, " -> "))) {
307       add = FALSE;
308     }
309   }
310   else {
311     add = FALSE;
312   }
313   Curl_set_in_callback(conn->data, false);
314 
315   if(add) {
316     Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list);
317   }
318   else {
319     Curl_fileinfo_cleanup(infop);
320   }
321 
322   ftpwc->parser->file_data = NULL;
323   return CURLE_OK;
324 }
325 
Curl_ftp_parselist(char * buffer,size_t size,size_t nmemb,void * connptr)326 size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
327                           void *connptr)
328 {
329   size_t bufflen = size*nmemb;
330   struct connectdata *conn = (struct connectdata *)connptr;
331   struct ftp_wc *ftpwc = conn->data->wildcard.protdata;
332   struct ftp_parselist_data *parser = ftpwc->parser;
333   struct fileinfo *infop;
334   struct curl_fileinfo *finfo;
335   unsigned long i = 0;
336   CURLcode result;
337   size_t retsize = bufflen;
338 
339   if(parser->error) { /* error in previous call */
340     /* scenario:
341      * 1. call => OK..
342      * 2. call => OUT_OF_MEMORY (or other error)
343      * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
344      *    in wc_statemach()
345      */
346     goto fail;
347   }
348 
349   if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
350     /* considering info about FILE response format */
351     parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
352                        OS_TYPE_WIN_NT : OS_TYPE_UNIX;
353   }
354 
355   while(i < bufflen) { /* FSM */
356 
357     char c = buffer[i];
358     if(!parser->file_data) { /* tmp file data is not allocated yet */
359       parser->file_data = Curl_fileinfo_alloc();
360       if(!parser->file_data) {
361         parser->error = CURLE_OUT_OF_MEMORY;
362         goto fail;
363       }
364       parser->file_data->info.b_data = malloc(FTP_BUFFER_ALLOCSIZE);
365       if(!parser->file_data->info.b_data) {
366         parser->error = CURLE_OUT_OF_MEMORY;
367         goto fail;
368       }
369       parser->file_data->info.b_size = FTP_BUFFER_ALLOCSIZE;
370       parser->item_offset = 0;
371       parser->item_length = 0;
372     }
373 
374     infop = parser->file_data;
375     finfo = &infop->info;
376     finfo->b_data[finfo->b_used++] = c;
377 
378     if(finfo->b_used >= finfo->b_size - 1) {
379       /* if it is important, extend buffer space for file data */
380       char *tmp = realloc(finfo->b_data,
381                           finfo->b_size + FTP_BUFFER_ALLOCSIZE);
382       if(tmp) {
383         finfo->b_size += FTP_BUFFER_ALLOCSIZE;
384         finfo->b_data = tmp;
385       }
386       else {
387         Curl_fileinfo_cleanup(parser->file_data);
388         parser->file_data = NULL;
389         parser->error = CURLE_OUT_OF_MEMORY;
390         goto fail;
391       }
392     }
393 
394     switch(parser->os_type) {
395     case OS_TYPE_UNIX:
396       switch(parser->state.UNIX.main) {
397       case PL_UNIX_TOTALSIZE:
398         switch(parser->state.UNIX.sub.total_dirsize) {
399         case PL_UNIX_TOTALSIZE_INIT:
400           if(c == 't') {
401             parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
402             parser->item_length++;
403           }
404           else {
405             parser->state.UNIX.main = PL_UNIX_FILETYPE;
406             /* start FSM again not considering size of directory */
407             finfo->b_used = 0;
408             continue;
409           }
410           break;
411         case PL_UNIX_TOTALSIZE_READING:
412           parser->item_length++;
413           if(c == '\r') {
414             parser->item_length--;
415             finfo->b_used--;
416           }
417           else if(c == '\n') {
418             finfo->b_data[parser->item_length - 1] = 0;
419             if(strncmp("total ", finfo->b_data, 6) == 0) {
420               char *endptr = finfo->b_data + 6;
421               /* here we can deal with directory size, pass the leading
422                  whitespace and then the digits */
423               while(ISSPACE(*endptr))
424                 endptr++;
425               while(ISDIGIT(*endptr))
426                 endptr++;
427               if(*endptr != 0) {
428                 parser->error = CURLE_FTP_BAD_FILE_LIST;
429                 goto fail;
430               }
431               parser->state.UNIX.main = PL_UNIX_FILETYPE;
432               finfo->b_used = 0;
433             }
434             else {
435               parser->error = CURLE_FTP_BAD_FILE_LIST;
436               goto fail;
437             }
438           }
439           break;
440         }
441         break;
442       case PL_UNIX_FILETYPE:
443         switch(c) {
444         case '-':
445           finfo->filetype = CURLFILETYPE_FILE;
446           break;
447         case 'd':
448           finfo->filetype = CURLFILETYPE_DIRECTORY;
449           break;
450         case 'l':
451           finfo->filetype = CURLFILETYPE_SYMLINK;
452           break;
453         case 'p':
454           finfo->filetype = CURLFILETYPE_NAMEDPIPE;
455           break;
456         case 's':
457           finfo->filetype = CURLFILETYPE_SOCKET;
458           break;
459         case 'c':
460           finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
461           break;
462         case 'b':
463           finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
464           break;
465         case 'D':
466           finfo->filetype = CURLFILETYPE_DOOR;
467           break;
468         default:
469           parser->error = CURLE_FTP_BAD_FILE_LIST;
470           goto fail;
471         }
472         parser->state.UNIX.main = PL_UNIX_PERMISSION;
473         parser->item_length = 0;
474         parser->item_offset = 1;
475         break;
476       case PL_UNIX_PERMISSION:
477         parser->item_length++;
478         if(parser->item_length <= 9) {
479           if(!strchr("rwx-tTsS", c)) {
480             parser->error = CURLE_FTP_BAD_FILE_LIST;
481             goto fail;
482           }
483         }
484         else if(parser->item_length == 10) {
485           unsigned int perm;
486           if(c != ' ') {
487             parser->error = CURLE_FTP_BAD_FILE_LIST;
488             goto fail;
489           }
490           finfo->b_data[10] = 0; /* terminate permissions */
491           perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
492           if(perm & FTP_LP_MALFORMATED_PERM) {
493             parser->error = CURLE_FTP_BAD_FILE_LIST;
494             goto fail;
495           }
496           parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM;
497           parser->file_data->info.perm = perm;
498           parser->offsets.perm = parser->item_offset;
499 
500           parser->item_length = 0;
501           parser->state.UNIX.main = PL_UNIX_HLINKS;
502           parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
503         }
504         break;
505       case PL_UNIX_HLINKS:
506         switch(parser->state.UNIX.sub.hlinks) {
507         case PL_UNIX_HLINKS_PRESPACE:
508           if(c != ' ') {
509             if(c >= '0' && c <= '9') {
510               parser->item_offset = finfo->b_used - 1;
511               parser->item_length = 1;
512               parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
513             }
514             else {
515               parser->error = CURLE_FTP_BAD_FILE_LIST;
516               goto fail;
517             }
518           }
519           break;
520         case PL_UNIX_HLINKS_NUMBER:
521           parser->item_length ++;
522           if(c == ' ') {
523             char *p;
524             long int hlinks;
525             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
526             hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
527             if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
528               parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
529               parser->file_data->info.hardlinks = hlinks;
530             }
531             parser->item_length = 0;
532             parser->item_offset = 0;
533             parser->state.UNIX.main = PL_UNIX_USER;
534             parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
535           }
536           else if(c < '0' || c > '9') {
537             parser->error = CURLE_FTP_BAD_FILE_LIST;
538             goto fail;
539           }
540           break;
541         }
542         break;
543       case PL_UNIX_USER:
544         switch(parser->state.UNIX.sub.user) {
545         case PL_UNIX_USER_PRESPACE:
546           if(c != ' ') {
547             parser->item_offset = finfo->b_used - 1;
548             parser->item_length = 1;
549             parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
550           }
551           break;
552         case PL_UNIX_USER_PARSING:
553           parser->item_length++;
554           if(c == ' ') {
555             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
556             parser->offsets.user = parser->item_offset;
557             parser->state.UNIX.main = PL_UNIX_GROUP;
558             parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
559             parser->item_offset = 0;
560             parser->item_length = 0;
561           }
562           break;
563         }
564         break;
565       case PL_UNIX_GROUP:
566         switch(parser->state.UNIX.sub.group) {
567         case PL_UNIX_GROUP_PRESPACE:
568           if(c != ' ') {
569             parser->item_offset = finfo->b_used - 1;
570             parser->item_length = 1;
571             parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
572           }
573           break;
574         case PL_UNIX_GROUP_NAME:
575           parser->item_length++;
576           if(c == ' ') {
577             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
578             parser->offsets.group = parser->item_offset;
579             parser->state.UNIX.main = PL_UNIX_SIZE;
580             parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
581             parser->item_offset = 0;
582             parser->item_length = 0;
583           }
584           break;
585         }
586         break;
587       case PL_UNIX_SIZE:
588         switch(parser->state.UNIX.sub.size) {
589         case PL_UNIX_SIZE_PRESPACE:
590           if(c != ' ') {
591             if(c >= '0' && c <= '9') {
592               parser->item_offset = finfo->b_used - 1;
593               parser->item_length = 1;
594               parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
595             }
596             else {
597               parser->error = CURLE_FTP_BAD_FILE_LIST;
598               goto fail;
599             }
600           }
601           break;
602         case PL_UNIX_SIZE_NUMBER:
603           parser->item_length++;
604           if(c == ' ') {
605             char *p;
606             curl_off_t fsize;
607             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
608             if(!curlx_strtoofft(finfo->b_data + parser->item_offset,
609                                 &p, 10, &fsize)) {
610               if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
611                  fsize != CURL_OFF_T_MIN) {
612                 parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
613                 parser->file_data->info.size = fsize;
614               }
615               parser->item_length = 0;
616               parser->item_offset = 0;
617               parser->state.UNIX.main = PL_UNIX_TIME;
618               parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
619             }
620           }
621           else if(!ISDIGIT(c)) {
622             parser->error = CURLE_FTP_BAD_FILE_LIST;
623             goto fail;
624           }
625           break;
626         }
627         break;
628       case PL_UNIX_TIME:
629         switch(parser->state.UNIX.sub.time) {
630         case PL_UNIX_TIME_PREPART1:
631           if(c != ' ') {
632             if(ISALNUM(c)) {
633               parser->item_offset = finfo->b_used -1;
634               parser->item_length = 1;
635               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
636             }
637             else {
638               parser->error = CURLE_FTP_BAD_FILE_LIST;
639               goto fail;
640             }
641           }
642           break;
643         case PL_UNIX_TIME_PART1:
644           parser->item_length++;
645           if(c == ' ') {
646             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
647           }
648           else if(!ISALNUM(c) && c != '.') {
649             parser->error = CURLE_FTP_BAD_FILE_LIST;
650             goto fail;
651           }
652           break;
653         case PL_UNIX_TIME_PREPART2:
654           parser->item_length++;
655           if(c != ' ') {
656             if(ISALNUM(c)) {
657               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
658             }
659             else {
660               parser->error = CURLE_FTP_BAD_FILE_LIST;
661               goto fail;
662             }
663           }
664           break;
665         case PL_UNIX_TIME_PART2:
666           parser->item_length++;
667           if(c == ' ') {
668             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
669           }
670           else if(!ISALNUM(c) && c != '.') {
671             parser->error = CURLE_FTP_BAD_FILE_LIST;
672             goto fail;
673           }
674           break;
675         case PL_UNIX_TIME_PREPART3:
676           parser->item_length++;
677           if(c != ' ') {
678             if(ISALNUM(c)) {
679               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
680             }
681             else {
682               parser->error = CURLE_FTP_BAD_FILE_LIST;
683               goto fail;
684             }
685           }
686           break;
687         case PL_UNIX_TIME_PART3:
688           parser->item_length++;
689           if(c == ' ') {
690             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
691             parser->offsets.time = parser->item_offset;
692             /*
693               if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
694                 parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
695               }
696             */
697             if(finfo->filetype == CURLFILETYPE_SYMLINK) {
698               parser->state.UNIX.main = PL_UNIX_SYMLINK;
699               parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
700             }
701             else {
702               parser->state.UNIX.main = PL_UNIX_FILENAME;
703               parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
704             }
705           }
706           else if(!ISALNUM(c) && c != '.' && c != ':') {
707             parser->error = CURLE_FTP_BAD_FILE_LIST;
708             goto fail;
709           }
710           break;
711         }
712         break;
713       case PL_UNIX_FILENAME:
714         switch(parser->state.UNIX.sub.filename) {
715         case PL_UNIX_FILENAME_PRESPACE:
716           if(c != ' ') {
717             parser->item_offset = finfo->b_used - 1;
718             parser->item_length = 1;
719             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
720           }
721           break;
722         case PL_UNIX_FILENAME_NAME:
723           parser->item_length++;
724           if(c == '\r') {
725             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
726           }
727           else if(c == '\n') {
728             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
729             parser->offsets.filename = parser->item_offset;
730             parser->state.UNIX.main = PL_UNIX_FILETYPE;
731             result = ftp_pl_insert_finfo(conn, infop);
732             if(result) {
733               parser->error = result;
734               goto fail;
735             }
736           }
737           break;
738         case PL_UNIX_FILENAME_WINDOWSEOL:
739           if(c == '\n') {
740             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
741             parser->offsets.filename = parser->item_offset;
742             parser->state.UNIX.main = PL_UNIX_FILETYPE;
743             result = ftp_pl_insert_finfo(conn, infop);
744             if(result) {
745               parser->error = result;
746               goto fail;
747             }
748           }
749           else {
750             parser->error = CURLE_FTP_BAD_FILE_LIST;
751             goto fail;
752           }
753           break;
754         }
755         break;
756       case PL_UNIX_SYMLINK:
757         switch(parser->state.UNIX.sub.symlink) {
758         case PL_UNIX_SYMLINK_PRESPACE:
759           if(c != ' ') {
760             parser->item_offset = finfo->b_used - 1;
761             parser->item_length = 1;
762             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
763           }
764           break;
765         case PL_UNIX_SYMLINK_NAME:
766           parser->item_length++;
767           if(c == ' ') {
768             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
769           }
770           else if(c == '\r' || c == '\n') {
771             parser->error = CURLE_FTP_BAD_FILE_LIST;
772             goto fail;
773           }
774           break;
775         case PL_UNIX_SYMLINK_PRETARGET1:
776           parser->item_length++;
777           if(c == '-') {
778             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
779           }
780           else if(c == '\r' || c == '\n') {
781             parser->error = CURLE_FTP_BAD_FILE_LIST;
782             goto fail;
783           }
784           else {
785             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
786           }
787           break;
788         case PL_UNIX_SYMLINK_PRETARGET2:
789           parser->item_length++;
790           if(c == '>') {
791             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
792           }
793           else if(c == '\r' || c == '\n') {
794             parser->error = CURLE_FTP_BAD_FILE_LIST;
795             goto fail;
796           }
797           else {
798             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
799           }
800           break;
801         case PL_UNIX_SYMLINK_PRETARGET3:
802           parser->item_length++;
803           if(c == ' ') {
804             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
805             /* now place where is symlink following */
806             finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
807             parser->offsets.filename = parser->item_offset;
808             parser->item_length = 0;
809             parser->item_offset = 0;
810           }
811           else if(c == '\r' || c == '\n') {
812             parser->error = CURLE_FTP_BAD_FILE_LIST;
813             goto fail;
814           }
815           else {
816             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
817           }
818           break;
819         case PL_UNIX_SYMLINK_PRETARGET4:
820           if(c != '\r' && c != '\n') {
821             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
822             parser->item_offset = finfo->b_used - 1;
823             parser->item_length = 1;
824           }
825           else {
826             parser->error = CURLE_FTP_BAD_FILE_LIST;
827             goto fail;
828           }
829           break;
830         case PL_UNIX_SYMLINK_TARGET:
831           parser->item_length++;
832           if(c == '\r') {
833             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
834           }
835           else if(c == '\n') {
836             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
837             parser->offsets.symlink_target = parser->item_offset;
838             result = ftp_pl_insert_finfo(conn, infop);
839             if(result) {
840               parser->error = result;
841               goto fail;
842             }
843             parser->state.UNIX.main = PL_UNIX_FILETYPE;
844           }
845           break;
846         case PL_UNIX_SYMLINK_WINDOWSEOL:
847           if(c == '\n') {
848             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
849             parser->offsets.symlink_target = parser->item_offset;
850             result = ftp_pl_insert_finfo(conn, infop);
851             if(result) {
852               parser->error = result;
853               goto fail;
854             }
855             parser->state.UNIX.main = PL_UNIX_FILETYPE;
856           }
857           else {
858             parser->error = CURLE_FTP_BAD_FILE_LIST;
859             goto fail;
860           }
861           break;
862         }
863         break;
864       }
865       break;
866     case OS_TYPE_WIN_NT:
867       switch(parser->state.NT.main) {
868       case PL_WINNT_DATE:
869         parser->item_length++;
870         if(parser->item_length < 9) {
871           if(!strchr("0123456789-", c)) { /* only simple control */
872             parser->error = CURLE_FTP_BAD_FILE_LIST;
873             goto fail;
874           }
875         }
876         else if(parser->item_length == 9) {
877           if(c == ' ') {
878             parser->state.NT.main = PL_WINNT_TIME;
879             parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
880           }
881           else {
882             parser->error = CURLE_FTP_BAD_FILE_LIST;
883             goto fail;
884           }
885         }
886         else {
887           parser->error = CURLE_FTP_BAD_FILE_LIST;
888           goto fail;
889         }
890         break;
891       case PL_WINNT_TIME:
892         parser->item_length++;
893         switch(parser->state.NT.sub.time) {
894         case PL_WINNT_TIME_PRESPACE:
895           if(!ISSPACE(c)) {
896             parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
897           }
898           break;
899         case PL_WINNT_TIME_TIME:
900           if(c == ' ') {
901             parser->offsets.time = parser->item_offset;
902             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
903             parser->state.NT.main = PL_WINNT_DIRORSIZE;
904             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
905             parser->item_length = 0;
906           }
907           else if(!strchr("APM0123456789:", c)) {
908             parser->error = CURLE_FTP_BAD_FILE_LIST;
909             goto fail;
910           }
911           break;
912         }
913         break;
914       case PL_WINNT_DIRORSIZE:
915         switch(parser->state.NT.sub.dirorsize) {
916         case PL_WINNT_DIRORSIZE_PRESPACE:
917           if(c != ' ') {
918             parser->item_offset = finfo->b_used - 1;
919             parser->item_length = 1;
920             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
921           }
922           break;
923         case PL_WINNT_DIRORSIZE_CONTENT:
924           parser->item_length ++;
925           if(c == ' ') {
926             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
927             if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
928               finfo->filetype = CURLFILETYPE_DIRECTORY;
929               finfo->size = 0;
930             }
931             else {
932               char *endptr;
933               if(curlx_strtoofft(finfo->b_data +
934                                  parser->item_offset,
935                                  &endptr, 10, &finfo->size)) {
936                 parser->error = CURLE_FTP_BAD_FILE_LIST;
937                 goto fail;
938               }
939               /* correct file type */
940               parser->file_data->info.filetype = CURLFILETYPE_FILE;
941             }
942 
943             parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
944             parser->item_length = 0;
945             parser->state.NT.main = PL_WINNT_FILENAME;
946             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
947           }
948           break;
949         }
950         break;
951       case PL_WINNT_FILENAME:
952         switch(parser->state.NT.sub.filename) {
953         case PL_WINNT_FILENAME_PRESPACE:
954           if(c != ' ') {
955             parser->item_offset = finfo->b_used -1;
956             parser->item_length = 1;
957             parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
958           }
959           break;
960         case PL_WINNT_FILENAME_CONTENT:
961           parser->item_length++;
962           if(c == '\r') {
963             parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
964             finfo->b_data[finfo->b_used - 1] = 0;
965           }
966           else if(c == '\n') {
967             parser->offsets.filename = parser->item_offset;
968             finfo->b_data[finfo->b_used - 1] = 0;
969             parser->offsets.filename = parser->item_offset;
970             result = ftp_pl_insert_finfo(conn, infop);
971             if(result) {
972               parser->error = result;
973               goto fail;
974             }
975             parser->state.NT.main = PL_WINNT_DATE;
976             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
977           }
978           break;
979         case PL_WINNT_FILENAME_WINEOL:
980           if(c == '\n') {
981             parser->offsets.filename = parser->item_offset;
982             result = ftp_pl_insert_finfo(conn, infop);
983             if(result) {
984               parser->error = result;
985               goto fail;
986             }
987             parser->state.NT.main = PL_WINNT_DATE;
988             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
989           }
990           else {
991             parser->error = CURLE_FTP_BAD_FILE_LIST;
992             goto fail;
993           }
994           break;
995         }
996         break;
997       }
998       break;
999     default:
1000       retsize = bufflen + 1;
1001       goto fail;
1002     }
1003 
1004     i++;
1005   }
1006   return retsize;
1007 
1008 fail:
1009 
1010   /* Clean up any allocated memory. */
1011   if(parser->file_data) {
1012     Curl_fileinfo_cleanup(parser->file_data);
1013     parser->file_data = NULL;
1014   }
1015 
1016   return retsize;
1017 }
1018 
1019 #endif /* CURL_DISABLE_FTP */
1020