Imported Upstream version 7.59.0
[platform/upstream/curl.git] / lib / ftplistparser.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2017, 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
182 struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
183 {
184   return calloc(1, sizeof(struct ftp_parselist_data));
185 }
186
187
188 void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data)
189 {
190   free(*pl_data);
191   *pl_data = NULL;
192 }
193
194
195 CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data)
196 {
197   return pl_data->error;
198 }
199
200
201 #define FTP_LP_MALFORMATED_PERM 0x01000000
202
203 static int ftp_pl_get_permission(const char *str)
204 {
205   int permissions = 0;
206   /* USER */
207   if(str[0] == 'r')
208     permissions |= 1 << 8;
209   else if(str[0] != '-')
210     permissions |= FTP_LP_MALFORMATED_PERM;
211   if(str[1] == 'w')
212     permissions |= 1 << 7;
213   else if(str[1] != '-')
214     permissions |= FTP_LP_MALFORMATED_PERM;
215
216   if(str[2] == 'x')
217     permissions |= 1 << 6;
218   else if(str[2] == 's') {
219     permissions |= 1 << 6;
220     permissions |= 1 << 11;
221   }
222   else if(str[2] == 'S')
223     permissions |= 1 << 11;
224   else if(str[2] != '-')
225     permissions |= FTP_LP_MALFORMATED_PERM;
226   /* GROUP */
227   if(str[3] == 'r')
228     permissions |= 1 << 5;
229   else if(str[3] != '-')
230     permissions |= FTP_LP_MALFORMATED_PERM;
231   if(str[4] == 'w')
232     permissions |= 1 << 4;
233   else if(str[4] != '-')
234     permissions |= FTP_LP_MALFORMATED_PERM;
235   if(str[5] == 'x')
236     permissions |= 1 << 3;
237   else if(str[5] == 's') {
238     permissions |= 1 << 3;
239     permissions |= 1 << 10;
240   }
241   else if(str[5] == 'S')
242     permissions |= 1 << 10;
243   else if(str[5] != '-')
244     permissions |= FTP_LP_MALFORMATED_PERM;
245   /* others */
246   if(str[6] == 'r')
247     permissions |= 1 << 2;
248   else if(str[6] != '-')
249     permissions |= FTP_LP_MALFORMATED_PERM;
250   if(str[7] == 'w')
251     permissions |= 1 << 1;
252   else if(str[7] != '-')
253       permissions |= FTP_LP_MALFORMATED_PERM;
254   if(str[8] == 'x')
255     permissions |= 1;
256   else if(str[8] == 't') {
257     permissions |= 1;
258     permissions |= 1 << 9;
259   }
260   else if(str[8] == 'T')
261     permissions |= 1 << 9;
262   else if(str[8] != '-')
263     permissions |= FTP_LP_MALFORMATED_PERM;
264
265   return permissions;
266 }
267
268 static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
269                                     struct fileinfo *infop)
270 {
271   curl_fnmatch_callback compare;
272   struct WildcardData *wc = &conn->data->wildcard;
273   struct ftp_wc_tmpdata *tmpdata = wc->tmp;
274   struct curl_llist *llist = &wc->filelist;
275   struct ftp_parselist_data *parser = tmpdata->parser;
276   bool add = TRUE;
277   struct curl_fileinfo *finfo = &infop->info;
278
279   /* move finfo pointers to b_data */
280   char *str = finfo->b_data;
281   finfo->filename       = str + parser->offsets.filename;
282   finfo->strings.group  = parser->offsets.group ?
283                           str + parser->offsets.group : NULL;
284   finfo->strings.perm   = parser->offsets.perm ?
285                           str + parser->offsets.perm : NULL;
286   finfo->strings.target = parser->offsets.symlink_target ?
287                           str + parser->offsets.symlink_target : NULL;
288   finfo->strings.time   = str + parser->offsets.time;
289   finfo->strings.user   = parser->offsets.user ?
290                           str + parser->offsets.user : NULL;
291
292   /* get correct fnmatch callback */
293   compare = conn->data->set.fnmatch;
294   if(!compare)
295     compare = Curl_fnmatch;
296
297   /* filter pattern-corresponding filenames */
298   Curl_set_in_callback(conn->data, true);
299   if(compare(conn->data->set.fnmatch_data, wc->pattern,
300              finfo->filename) == 0) {
301     /* discard symlink which is containing multiple " -> " */
302     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
303        (strstr(finfo->strings.target, " -> "))) {
304       add = FALSE;
305     }
306   }
307   else {
308     add = FALSE;
309   }
310   Curl_set_in_callback(conn->data, false);
311
312   if(add) {
313     Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list);
314   }
315   else {
316     Curl_fileinfo_dtor(NULL, finfo);
317   }
318
319   tmpdata->parser->file_data = NULL;
320   return CURLE_OK;
321 }
322
323 size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
324                           void *connptr)
325 {
326   size_t bufflen = size*nmemb;
327   struct connectdata *conn = (struct connectdata *)connptr;
328   struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
329   struct ftp_parselist_data *parser = tmpdata->parser;
330   struct fileinfo *infop;
331   struct curl_fileinfo *finfo;
332   unsigned long i = 0;
333   CURLcode result;
334   size_t retsize = bufflen;
335
336   if(parser->error) { /* error in previous call */
337     /* scenario:
338      * 1. call => OK..
339      * 2. call => OUT_OF_MEMORY (or other error)
340      * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
341      *    in wc_statemach()
342      */
343     goto fail;
344   }
345
346   if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
347     /* considering info about FILE response format */
348     parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
349                        OS_TYPE_WIN_NT : OS_TYPE_UNIX;
350   }
351
352   while(i < bufflen) { /* FSM */
353
354     char c = buffer[i];
355     if(!parser->file_data) { /* tmp file data is not allocated yet */
356       parser->file_data = Curl_fileinfo_alloc();
357       if(!parser->file_data) {
358         parser->error = CURLE_OUT_OF_MEMORY;
359         goto fail;
360       }
361       parser->file_data->info.b_data = malloc(FTP_BUFFER_ALLOCSIZE);
362       if(!parser->file_data->info.b_data) {
363         parser->error = CURLE_OUT_OF_MEMORY;
364         goto fail;
365       }
366       parser->file_data->info.b_size = FTP_BUFFER_ALLOCSIZE;
367       parser->item_offset = 0;
368       parser->item_length = 0;
369     }
370
371     infop = parser->file_data;
372     finfo = &infop->info;
373     finfo->b_data[finfo->b_used++] = c;
374
375     if(finfo->b_used >= finfo->b_size - 1) {
376       /* if it is important, extend buffer space for file data */
377       char *tmp = realloc(finfo->b_data,
378                           finfo->b_size + FTP_BUFFER_ALLOCSIZE);
379       if(tmp) {
380         finfo->b_size += FTP_BUFFER_ALLOCSIZE;
381         finfo->b_data = tmp;
382       }
383       else {
384         Curl_fileinfo_dtor(NULL, parser->file_data);
385         parser->file_data = NULL;
386         parser->error = CURLE_OUT_OF_MEMORY;
387         goto fail;
388       }
389     }
390
391     switch(parser->os_type) {
392     case OS_TYPE_UNIX:
393       switch(parser->state.UNIX.main) {
394       case PL_UNIX_TOTALSIZE:
395         switch(parser->state.UNIX.sub.total_dirsize) {
396         case PL_UNIX_TOTALSIZE_INIT:
397           if(c == 't') {
398             parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
399             parser->item_length++;
400           }
401           else {
402             parser->state.UNIX.main = PL_UNIX_FILETYPE;
403             /* start FSM again not considering size of directory */
404             finfo->b_used = 0;
405             i--;
406           }
407           break;
408         case PL_UNIX_TOTALSIZE_READING:
409           parser->item_length++;
410           if(c == '\r') {
411             parser->item_length--;
412             finfo->b_used--;
413           }
414           else if(c == '\n') {
415             finfo->b_data[parser->item_length - 1] = 0;
416             if(strncmp("total ", finfo->b_data, 6) == 0) {
417               char *endptr = finfo->b_data + 6;
418               /* here we can deal with directory size, pass the leading white
419                  spaces and then the digits */
420               while(ISSPACE(*endptr))
421                 endptr++;
422               while(ISDIGIT(*endptr))
423                 endptr++;
424               if(*endptr != 0) {
425                 parser->error = CURLE_FTP_BAD_FILE_LIST;
426                 goto fail;
427               }
428               parser->state.UNIX.main = PL_UNIX_FILETYPE;
429               finfo->b_used = 0;
430             }
431             else {
432               parser->error = CURLE_FTP_BAD_FILE_LIST;
433               goto fail;
434             }
435           }
436           break;
437         }
438         break;
439       case PL_UNIX_FILETYPE:
440         switch(c) {
441         case '-':
442           finfo->filetype = CURLFILETYPE_FILE;
443           break;
444         case 'd':
445           finfo->filetype = CURLFILETYPE_DIRECTORY;
446           break;
447         case 'l':
448           finfo->filetype = CURLFILETYPE_SYMLINK;
449           break;
450         case 'p':
451           finfo->filetype = CURLFILETYPE_NAMEDPIPE;
452           break;
453         case 's':
454           finfo->filetype = CURLFILETYPE_SOCKET;
455           break;
456         case 'c':
457           finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
458           break;
459         case 'b':
460           finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
461           break;
462         case 'D':
463           finfo->filetype = CURLFILETYPE_DOOR;
464           break;
465         default:
466           parser->error = CURLE_FTP_BAD_FILE_LIST;
467           goto fail;
468         }
469         parser->state.UNIX.main = PL_UNIX_PERMISSION;
470         parser->item_length = 0;
471         parser->item_offset = 1;
472         break;
473       case PL_UNIX_PERMISSION:
474         parser->item_length++;
475         if(parser->item_length <= 9) {
476           if(!strchr("rwx-tTsS", c)) {
477             parser->error = CURLE_FTP_BAD_FILE_LIST;
478             goto fail;
479           }
480         }
481         else if(parser->item_length == 10) {
482           unsigned int perm;
483           if(c != ' ') {
484             parser->error = CURLE_FTP_BAD_FILE_LIST;
485             goto fail;
486           }
487           finfo->b_data[10] = 0; /* terminate permissions */
488           perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
489           if(perm & FTP_LP_MALFORMATED_PERM) {
490             parser->error = CURLE_FTP_BAD_FILE_LIST;
491             goto fail;
492           }
493           parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM;
494           parser->file_data->info.perm = perm;
495           parser->offsets.perm = parser->item_offset;
496
497           parser->item_length = 0;
498           parser->state.UNIX.main = PL_UNIX_HLINKS;
499           parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
500         }
501         break;
502       case PL_UNIX_HLINKS:
503         switch(parser->state.UNIX.sub.hlinks) {
504         case PL_UNIX_HLINKS_PRESPACE:
505           if(c != ' ') {
506             if(c >= '0' && c <= '9') {
507               parser->item_offset = finfo->b_used - 1;
508               parser->item_length = 1;
509               parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
510             }
511             else {
512               parser->error = CURLE_FTP_BAD_FILE_LIST;
513               goto fail;
514             }
515           }
516           break;
517         case PL_UNIX_HLINKS_NUMBER:
518           parser->item_length ++;
519           if(c == ' ') {
520             char *p;
521             long int hlinks;
522             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
523             hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
524             if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
525               parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
526               parser->file_data->info.hardlinks = hlinks;
527             }
528             parser->item_length = 0;
529             parser->item_offset = 0;
530             parser->state.UNIX.main = PL_UNIX_USER;
531             parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
532           }
533           else if(c < '0' || c > '9') {
534             parser->error = CURLE_FTP_BAD_FILE_LIST;
535             goto fail;
536           }
537           break;
538         }
539         break;
540       case PL_UNIX_USER:
541         switch(parser->state.UNIX.sub.user) {
542         case PL_UNIX_USER_PRESPACE:
543           if(c != ' ') {
544             parser->item_offset = finfo->b_used - 1;
545             parser->item_length = 1;
546             parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
547           }
548           break;
549         case PL_UNIX_USER_PARSING:
550           parser->item_length++;
551           if(c == ' ') {
552             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
553             parser->offsets.user = parser->item_offset;
554             parser->state.UNIX.main = PL_UNIX_GROUP;
555             parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
556             parser->item_offset = 0;
557             parser->item_length = 0;
558           }
559           break;
560         }
561         break;
562       case PL_UNIX_GROUP:
563         switch(parser->state.UNIX.sub.group) {
564         case PL_UNIX_GROUP_PRESPACE:
565           if(c != ' ') {
566             parser->item_offset = finfo->b_used - 1;
567             parser->item_length = 1;
568             parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
569           }
570           break;
571         case PL_UNIX_GROUP_NAME:
572           parser->item_length++;
573           if(c == ' ') {
574             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
575             parser->offsets.group = parser->item_offset;
576             parser->state.UNIX.main = PL_UNIX_SIZE;
577             parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
578             parser->item_offset = 0;
579             parser->item_length = 0;
580           }
581           break;
582         }
583         break;
584       case PL_UNIX_SIZE:
585         switch(parser->state.UNIX.sub.size) {
586         case PL_UNIX_SIZE_PRESPACE:
587           if(c != ' ') {
588             if(c >= '0' && c <= '9') {
589               parser->item_offset = finfo->b_used - 1;
590               parser->item_length = 1;
591               parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
592             }
593             else {
594               parser->error = CURLE_FTP_BAD_FILE_LIST;
595               goto fail;
596             }
597           }
598           break;
599         case PL_UNIX_SIZE_NUMBER:
600           parser->item_length++;
601           if(c == ' ') {
602             char *p;
603             curl_off_t fsize;
604             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
605             if(!curlx_strtoofft(finfo->b_data + parser->item_offset,
606                                 &p, 10, &fsize)) {
607               if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
608                  fsize != CURL_OFF_T_MIN) {
609                 parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
610                 parser->file_data->info.size = fsize;
611               }
612               parser->item_length = 0;
613               parser->item_offset = 0;
614               parser->state.UNIX.main = PL_UNIX_TIME;
615               parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
616             }
617           }
618           else if(!ISDIGIT(c)) {
619             parser->error = CURLE_FTP_BAD_FILE_LIST;
620             goto fail;
621           }
622           break;
623         }
624         break;
625       case PL_UNIX_TIME:
626         switch(parser->state.UNIX.sub.time) {
627         case PL_UNIX_TIME_PREPART1:
628           if(c != ' ') {
629             if(ISALNUM(c)) {
630               parser->item_offset = finfo->b_used -1;
631               parser->item_length = 1;
632               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
633             }
634             else {
635               parser->error = CURLE_FTP_BAD_FILE_LIST;
636               goto fail;
637             }
638           }
639           break;
640         case PL_UNIX_TIME_PART1:
641           parser->item_length++;
642           if(c == ' ') {
643             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
644           }
645           else if(!ISALNUM(c) && c != '.') {
646             parser->error = CURLE_FTP_BAD_FILE_LIST;
647             goto fail;
648           }
649           break;
650         case PL_UNIX_TIME_PREPART2:
651           parser->item_length++;
652           if(c != ' ') {
653             if(ISALNUM(c)) {
654               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
655             }
656             else {
657               parser->error = CURLE_FTP_BAD_FILE_LIST;
658               goto fail;
659             }
660           }
661           break;
662         case PL_UNIX_TIME_PART2:
663           parser->item_length++;
664           if(c == ' ') {
665             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
666           }
667           else if(!ISALNUM(c) && c != '.') {
668             parser->error = CURLE_FTP_BAD_FILE_LIST;
669             goto fail;
670           }
671           break;
672         case PL_UNIX_TIME_PREPART3:
673           parser->item_length++;
674           if(c != ' ') {
675             if(ISALNUM(c)) {
676               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
677             }
678             else {
679               parser->error = CURLE_FTP_BAD_FILE_LIST;
680               goto fail;
681             }
682           }
683           break;
684         case PL_UNIX_TIME_PART3:
685           parser->item_length++;
686           if(c == ' ') {
687             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
688             parser->offsets.time = parser->item_offset;
689             /*
690               if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
691                 parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
692               }
693             */
694             if(finfo->filetype == CURLFILETYPE_SYMLINK) {
695               parser->state.UNIX.main = PL_UNIX_SYMLINK;
696               parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
697             }
698             else {
699               parser->state.UNIX.main = PL_UNIX_FILENAME;
700               parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
701             }
702           }
703           else if(!ISALNUM(c) && c != '.' && c != ':') {
704             parser->error = CURLE_FTP_BAD_FILE_LIST;
705             goto fail;
706           }
707           break;
708         }
709         break;
710       case PL_UNIX_FILENAME:
711         switch(parser->state.UNIX.sub.filename) {
712         case PL_UNIX_FILENAME_PRESPACE:
713           if(c != ' ') {
714             parser->item_offset = finfo->b_used - 1;
715             parser->item_length = 1;
716             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
717           }
718           break;
719         case PL_UNIX_FILENAME_NAME:
720           parser->item_length++;
721           if(c == '\r') {
722             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
723           }
724           else if(c == '\n') {
725             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
726             parser->offsets.filename = parser->item_offset;
727             parser->state.UNIX.main = PL_UNIX_FILETYPE;
728             result = ftp_pl_insert_finfo(conn, infop);
729             if(result) {
730               parser->error = result;
731               goto fail;
732             }
733           }
734           break;
735         case PL_UNIX_FILENAME_WINDOWSEOL:
736           if(c == '\n') {
737             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
738             parser->offsets.filename = parser->item_offset;
739             parser->state.UNIX.main = PL_UNIX_FILETYPE;
740             result = ftp_pl_insert_finfo(conn, infop);
741             if(result) {
742               parser->error = result;
743               goto fail;
744             }
745           }
746           else {
747             parser->error = CURLE_FTP_BAD_FILE_LIST;
748             goto fail;
749           }
750           break;
751         }
752         break;
753       case PL_UNIX_SYMLINK:
754         switch(parser->state.UNIX.sub.symlink) {
755         case PL_UNIX_SYMLINK_PRESPACE:
756           if(c != ' ') {
757             parser->item_offset = finfo->b_used - 1;
758             parser->item_length = 1;
759             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
760           }
761           break;
762         case PL_UNIX_SYMLINK_NAME:
763           parser->item_length++;
764           if(c == ' ') {
765             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
766           }
767           else if(c == '\r' || c == '\n') {
768             parser->error = CURLE_FTP_BAD_FILE_LIST;
769             goto fail;
770           }
771           break;
772         case PL_UNIX_SYMLINK_PRETARGET1:
773           parser->item_length++;
774           if(c == '-') {
775             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
776           }
777           else if(c == '\r' || c == '\n') {
778             parser->error = CURLE_FTP_BAD_FILE_LIST;
779             goto fail;
780           }
781           else {
782             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
783           }
784           break;
785         case PL_UNIX_SYMLINK_PRETARGET2:
786           parser->item_length++;
787           if(c == '>') {
788             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
789           }
790           else if(c == '\r' || c == '\n') {
791             parser->error = CURLE_FTP_BAD_FILE_LIST;
792             goto fail;
793           }
794           else {
795             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
796           }
797           break;
798         case PL_UNIX_SYMLINK_PRETARGET3:
799           parser->item_length++;
800           if(c == ' ') {
801             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
802             /* now place where is symlink following */
803             finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
804             parser->offsets.filename = parser->item_offset;
805             parser->item_length = 0;
806             parser->item_offset = 0;
807           }
808           else if(c == '\r' || c == '\n') {
809             parser->error = CURLE_FTP_BAD_FILE_LIST;
810             goto fail;
811           }
812           else {
813             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
814           }
815           break;
816         case PL_UNIX_SYMLINK_PRETARGET4:
817           if(c != '\r' && c != '\n') {
818             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
819             parser->item_offset = finfo->b_used - 1;
820             parser->item_length = 1;
821           }
822           else {
823             parser->error = CURLE_FTP_BAD_FILE_LIST;
824             goto fail;
825           }
826           break;
827         case PL_UNIX_SYMLINK_TARGET:
828           parser->item_length++;
829           if(c == '\r') {
830             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
831           }
832           else if(c == '\n') {
833             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
834             parser->offsets.symlink_target = parser->item_offset;
835             result = ftp_pl_insert_finfo(conn, infop);
836             if(result) {
837               parser->error = result;
838               goto fail;
839             }
840             parser->state.UNIX.main = PL_UNIX_FILETYPE;
841           }
842           break;
843         case PL_UNIX_SYMLINK_WINDOWSEOL:
844           if(c == '\n') {
845             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
846             parser->offsets.symlink_target = parser->item_offset;
847             result = ftp_pl_insert_finfo(conn, infop);
848             if(result) {
849               parser->error = result;
850               goto fail;
851             }
852             parser->state.UNIX.main = PL_UNIX_FILETYPE;
853           }
854           else {
855             parser->error = CURLE_FTP_BAD_FILE_LIST;
856             goto fail;
857           }
858           break;
859         }
860         break;
861       }
862       break;
863     case OS_TYPE_WIN_NT:
864       switch(parser->state.NT.main) {
865       case PL_WINNT_DATE:
866         parser->item_length++;
867         if(parser->item_length < 9) {
868           if(!strchr("0123456789-", c)) { /* only simple control */
869             parser->error = CURLE_FTP_BAD_FILE_LIST;
870             goto fail;
871           }
872         }
873         else if(parser->item_length == 9) {
874           if(c == ' ') {
875             parser->state.NT.main = PL_WINNT_TIME;
876             parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
877           }
878           else {
879             parser->error = CURLE_FTP_BAD_FILE_LIST;
880             goto fail;
881           }
882         }
883         else {
884           parser->error = CURLE_FTP_BAD_FILE_LIST;
885           goto fail;
886         }
887         break;
888       case PL_WINNT_TIME:
889         parser->item_length++;
890         switch(parser->state.NT.sub.time) {
891         case PL_WINNT_TIME_PRESPACE:
892           if(!ISSPACE(c)) {
893             parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
894           }
895           break;
896         case PL_WINNT_TIME_TIME:
897           if(c == ' ') {
898             parser->offsets.time = parser->item_offset;
899             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
900             parser->state.NT.main = PL_WINNT_DIRORSIZE;
901             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
902             parser->item_length = 0;
903           }
904           else if(!strchr("APM0123456789:", c)) {
905             parser->error = CURLE_FTP_BAD_FILE_LIST;
906             goto fail;
907           }
908           break;
909         }
910         break;
911       case PL_WINNT_DIRORSIZE:
912         switch(parser->state.NT.sub.dirorsize) {
913         case PL_WINNT_DIRORSIZE_PRESPACE:
914           if(c == ' ') {
915
916           }
917           else {
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
1007 fail:
1008
1009   /* Clean up any allocated memory. */
1010   if(parser->file_data) {
1011     Curl_fileinfo_dtor(NULL, parser->file_data);
1012     parser->file_data = NULL;
1013   }
1014
1015   return retsize;
1016 }
1017
1018 #endif /* CURL_DISABLE_FTP */