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