Imported Upstream version 7.44.0
[platform/upstream/curl.git] / lib / ftplistparser.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, 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 http://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 "rawstr.h"
49 #include "ftp.h"
50 #include "ftplistparser.h"
51 #include "curl_fnmatch.h"
52 #include "curl_memory.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 curl_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 void PL_ERROR(struct connectdata *conn, CURLcode err)
269 {
270   struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
271   struct ftp_parselist_data *parser = tmpdata->parser;
272   if(parser->file_data)
273     Curl_fileinfo_dtor(NULL, parser->file_data);
274   parser->file_data = NULL;
275   parser->error = err;
276 }
277
278 static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *string)
279 {
280   (void)parser;
281   (void)string;
282   /* TODO
283    * There could be possible parse timestamp from server. Leaving unimplemented
284    * for now.
285    * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME flag to
286    * parser->file_data->flags
287    *
288    * Ftp servers are giving usually these formats:
289    *  Apr 11  1998 (unknown time.. set it to 00:00:00?)
290    *  Apr 11 12:21 (unknown year -> set it to NOW() time?)
291    *  08-05-09  02:49PM  (ms-dos format)
292    *  20100421092538 -> for MLST/MLSD response
293    */
294
295   return FALSE;
296 }
297
298 static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
299                                     struct curl_fileinfo *finfo)
300 {
301   curl_fnmatch_callback compare;
302   struct WildcardData *wc = &conn->data->wildcard;
303   struct ftp_wc_tmpdata *tmpdata = wc->tmp;
304   struct curl_llist *llist = wc->filelist;
305   struct ftp_parselist_data *parser = tmpdata->parser;
306   bool add = TRUE;
307
308   /* move finfo pointers to b_data */
309   char *str = finfo->b_data;
310   finfo->filename       = str + parser->offsets.filename;
311   finfo->strings.group  = parser->offsets.group ?
312                           str + parser->offsets.group : NULL;
313   finfo->strings.perm   = parser->offsets.perm ?
314                           str + parser->offsets.perm : NULL;
315   finfo->strings.target = parser->offsets.symlink_target ?
316                           str + parser->offsets.symlink_target : NULL;
317   finfo->strings.time   = str + parser->offsets.time;
318   finfo->strings.user   = parser->offsets.user ?
319                           str + parser->offsets.user : NULL;
320
321   /* get correct fnmatch callback */
322   compare = conn->data->set.fnmatch;
323   if(!compare)
324     compare = Curl_fnmatch;
325
326   /* filter pattern-corresponding filenames */
327   if(compare(conn->data->set.fnmatch_data, wc->pattern,
328              finfo->filename) == 0) {
329     /* discard symlink which is containing multiple " -> " */
330     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
331        (strstr(finfo->strings.target, " -> "))) {
332       add = FALSE;
333     }
334   }
335   else {
336     add = FALSE;
337   }
338
339   if(add) {
340     if(!Curl_llist_insert_next(llist, llist->tail, finfo)) {
341       Curl_fileinfo_dtor(NULL, finfo);
342       tmpdata->parser->file_data = NULL;
343       return CURLE_OUT_OF_MEMORY;
344     }
345   }
346   else {
347     Curl_fileinfo_dtor(NULL, finfo);
348   }
349
350   tmpdata->parser->file_data = NULL;
351   return CURLE_OK;
352 }
353
354 size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
355                           void *connptr)
356 {
357   size_t bufflen = size*nmemb;
358   struct connectdata *conn = (struct connectdata *)connptr;
359   struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
360   struct ftp_parselist_data *parser = tmpdata->parser;
361   struct curl_fileinfo *finfo;
362   unsigned long i = 0;
363   CURLcode result;
364
365   if(parser->error) { /* error in previous call */
366     /* scenario:
367      * 1. call => OK..
368      * 2. call => OUT_OF_MEMORY (or other error)
369      * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
370      *    in wc_statemach()
371      */
372     return bufflen;
373   }
374
375   if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
376     /* considering info about FILE response format */
377     parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
378                        OS_TYPE_WIN_NT : OS_TYPE_UNIX;
379   }
380
381   while(i < bufflen) { /* FSM */
382
383     char c = buffer[i];
384     if(!parser->file_data) { /* tmp file data is not allocated yet */
385       parser->file_data = Curl_fileinfo_alloc();
386       if(!parser->file_data) {
387         parser->error = CURLE_OUT_OF_MEMORY;
388         return bufflen;
389       }
390       parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE);
391       if(!parser->file_data->b_data) {
392         PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
393         return bufflen;
394       }
395       parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE;
396       parser->item_offset = 0;
397       parser->item_length = 0;
398     }
399
400     finfo = parser->file_data;
401     finfo->b_data[finfo->b_used++] = c;
402
403     if(finfo->b_used >= finfo->b_size - 1) {
404       /* if it is important, extend buffer space for file data */
405       char *tmp = realloc(finfo->b_data,
406                           finfo->b_size + FTP_BUFFER_ALLOCSIZE);
407       if(tmp) {
408         finfo->b_size += FTP_BUFFER_ALLOCSIZE;
409         finfo->b_data = tmp;
410       }
411       else {
412         Curl_fileinfo_dtor(NULL, parser->file_data);
413         parser->file_data = NULL;
414         parser->error = CURLE_OUT_OF_MEMORY;
415         PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
416         return bufflen;
417       }
418     }
419
420     switch (parser->os_type) {
421     case OS_TYPE_UNIX:
422       switch (parser->state.UNIX.main) {
423       case PL_UNIX_TOTALSIZE:
424         switch(parser->state.UNIX.sub.total_dirsize) {
425         case PL_UNIX_TOTALSIZE_INIT:
426           if(c == 't') {
427             parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
428             parser->item_length++;
429           }
430           else {
431             parser->state.UNIX.main = PL_UNIX_FILETYPE;
432             /* start FSM again not considering size of directory */
433             finfo->b_used = 0;
434             i--;
435           }
436           break;
437         case PL_UNIX_TOTALSIZE_READING:
438           parser->item_length++;
439           if(c == '\r') {
440             parser->item_length--;
441             finfo->b_used--;
442           }
443           else if(c == '\n') {
444             finfo->b_data[parser->item_length - 1] = 0;
445             if(strncmp("total ", finfo->b_data, 6) == 0) {
446               char *endptr = finfo->b_data+6;
447               /* here we can deal with directory size, pass the leading white
448                  spaces and then the digits */
449               while(ISSPACE(*endptr))
450                 endptr++;
451               while(ISDIGIT(*endptr))
452                 endptr++;
453               if(*endptr != 0) {
454                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
455                 return bufflen;
456               }
457               else {
458                 parser->state.UNIX.main = PL_UNIX_FILETYPE;
459                 finfo->b_used = 0;
460               }
461             }
462             else {
463               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
464               return bufflen;
465             }
466           }
467           break;
468         }
469         break;
470       case PL_UNIX_FILETYPE:
471         switch (c) {
472         case '-':
473           finfo->filetype = CURLFILETYPE_FILE;
474           break;
475         case 'd':
476           finfo->filetype = CURLFILETYPE_DIRECTORY;
477           break;
478         case 'l':
479           finfo->filetype = CURLFILETYPE_SYMLINK;
480           break;
481         case 'p':
482           finfo->filetype = CURLFILETYPE_NAMEDPIPE;
483           break;
484         case 's':
485           finfo->filetype = CURLFILETYPE_SOCKET;
486           break;
487         case 'c':
488           finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
489           break;
490         case 'b':
491           finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
492           break;
493         case 'D':
494           finfo->filetype = CURLFILETYPE_DOOR;
495           break;
496         default:
497           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
498           return bufflen;
499         }
500         parser->state.UNIX.main = PL_UNIX_PERMISSION;
501         parser->item_length = 0;
502         parser->item_offset = 1;
503         break;
504       case PL_UNIX_PERMISSION:
505         parser->item_length++;
506         if(parser->item_length <= 9) {
507           if(!strchr("rwx-tTsS", c)) {
508             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
509             return bufflen;
510           }
511         }
512         else if(parser->item_length == 10) {
513           unsigned int perm;
514           if(c != ' ') {
515             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
516             return bufflen;
517           }
518           finfo->b_data[10] = 0; /* terminate permissions */
519           perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
520           if(perm & FTP_LP_MALFORMATED_PERM) {
521             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
522             return bufflen;
523           }
524           parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM;
525           parser->file_data->perm = perm;
526           parser->offsets.perm = parser->item_offset;
527
528           parser->item_length = 0;
529           parser->state.UNIX.main = PL_UNIX_HLINKS;
530           parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
531         }
532         break;
533       case PL_UNIX_HLINKS:
534         switch(parser->state.UNIX.sub.hlinks) {
535         case PL_UNIX_HLINKS_PRESPACE:
536           if(c != ' ') {
537             if(c >= '0' && c <= '9') {
538               parser->item_offset = finfo->b_used - 1;
539               parser->item_length = 1;
540               parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
541             }
542             else {
543               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
544               return bufflen;
545             }
546           }
547           break;
548         case PL_UNIX_HLINKS_NUMBER:
549           parser->item_length ++;
550           if(c == ' ') {
551             char *p;
552             long int hlinks;
553             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
554             hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
555             if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
556               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
557               parser->file_data->hardlinks = hlinks;
558             }
559             parser->item_length = 0;
560             parser->item_offset = 0;
561             parser->state.UNIX.main = PL_UNIX_USER;
562             parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
563           }
564           else if(c < '0' || c > '9') {
565             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
566             return bufflen;
567           }
568           break;
569         }
570         break;
571       case PL_UNIX_USER:
572         switch(parser->state.UNIX.sub.user) {
573         case PL_UNIX_USER_PRESPACE:
574           if(c != ' ') {
575             parser->item_offset = finfo->b_used - 1;
576             parser->item_length = 1;
577             parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
578           }
579           break;
580         case PL_UNIX_USER_PARSING:
581           parser->item_length++;
582           if(c == ' ') {
583             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
584             parser->offsets.user = parser->item_offset;
585             parser->state.UNIX.main = PL_UNIX_GROUP;
586             parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
587             parser->item_offset = 0;
588             parser->item_length = 0;
589           }
590           break;
591         }
592         break;
593       case PL_UNIX_GROUP:
594         switch(parser->state.UNIX.sub.group) {
595         case PL_UNIX_GROUP_PRESPACE:
596           if(c != ' ') {
597             parser->item_offset = finfo->b_used - 1;
598             parser->item_length = 1;
599             parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
600           }
601           break;
602         case PL_UNIX_GROUP_NAME:
603           parser->item_length++;
604           if(c == ' ') {
605             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
606             parser->offsets.group = parser->item_offset;
607             parser->state.UNIX.main = PL_UNIX_SIZE;
608             parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
609             parser->item_offset = 0;
610             parser->item_length = 0;
611           }
612           break;
613         }
614         break;
615       case PL_UNIX_SIZE:
616         switch(parser->state.UNIX.sub.size) {
617         case PL_UNIX_SIZE_PRESPACE:
618           if(c != ' ') {
619             if(c >= '0' && c <= '9') {
620               parser->item_offset = finfo->b_used - 1;
621               parser->item_length = 1;
622               parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
623             }
624             else {
625               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
626               return bufflen;
627             }
628           }
629           break;
630         case PL_UNIX_SIZE_NUMBER:
631           parser->item_length++;
632           if(c == ' ') {
633             char *p;
634             curl_off_t fsize;
635             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
636             fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10);
637             if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
638                                fsize != CURL_OFF_T_MIN) {
639               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
640               parser->file_data->size = fsize;
641             }
642             parser->item_length = 0;
643             parser->item_offset = 0;
644             parser->state.UNIX.main = PL_UNIX_TIME;
645             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
646           }
647           else if(!ISDIGIT(c)) {
648             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
649             return bufflen;
650           }
651           break;
652         }
653         break;
654       case PL_UNIX_TIME:
655         switch(parser->state.UNIX.sub.time) {
656         case PL_UNIX_TIME_PREPART1:
657           if(c != ' ') {
658             if(ISALNUM(c)) {
659               parser->item_offset = finfo->b_used -1;
660               parser->item_length = 1;
661               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
662             }
663             else {
664               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
665               return bufflen;
666             }
667           }
668           break;
669         case PL_UNIX_TIME_PART1:
670           parser->item_length++;
671           if(c == ' ') {
672             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
673           }
674           else if(!ISALNUM(c) && c != '.') {
675             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
676             return bufflen;
677           }
678           break;
679         case PL_UNIX_TIME_PREPART2:
680           parser->item_length++;
681           if(c != ' ') {
682             if(ISALNUM(c)) {
683               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
684             }
685             else {
686               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
687               return bufflen;
688             }
689           }
690           break;
691         case PL_UNIX_TIME_PART2:
692           parser->item_length++;
693           if(c == ' ') {
694             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
695           }
696           else if(!ISALNUM(c) && c != '.') {
697             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
698             return bufflen;
699           }
700           break;
701         case PL_UNIX_TIME_PREPART3:
702           parser->item_length++;
703           if(c != ' ') {
704             if(ISALNUM(c)) {
705               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
706             }
707             else {
708               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
709               return bufflen;
710             }
711           }
712           break;
713         case PL_UNIX_TIME_PART3:
714           parser->item_length++;
715           if(c == ' ') {
716             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
717             parser->offsets.time = parser->item_offset;
718             if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
719               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
720             }
721             if(finfo->filetype == CURLFILETYPE_SYMLINK) {
722               parser->state.UNIX.main = PL_UNIX_SYMLINK;
723               parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
724             }
725             else {
726               parser->state.UNIX.main = PL_UNIX_FILENAME;
727               parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
728             }
729           }
730           else if(!ISALNUM(c) && c != '.' && c != ':') {
731             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
732             return bufflen;
733           }
734           break;
735         }
736         break;
737       case PL_UNIX_FILENAME:
738         switch(parser->state.UNIX.sub.filename) {
739         case PL_UNIX_FILENAME_PRESPACE:
740           if(c != ' ') {
741             parser->item_offset = finfo->b_used - 1;
742             parser->item_length = 1;
743             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
744           }
745           break;
746         case PL_UNIX_FILENAME_NAME:
747           parser->item_length++;
748           if(c == '\r') {
749             parser->item_length--;
750             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
751           }
752           else if(c == '\n') {
753             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
754             parser->offsets.filename = parser->item_offset;
755             parser->state.UNIX.main = PL_UNIX_FILETYPE;
756             result = ftp_pl_insert_finfo(conn, finfo);
757             if(result) {
758               PL_ERROR(conn, result);
759               return bufflen;
760             }
761           }
762           break;
763         case PL_UNIX_FILENAME_WINDOWSEOL:
764           if(c == '\n') {
765             finfo->b_data[parser->item_offset + parser->item_length] = 0;
766             parser->offsets.filename = parser->item_offset;
767             parser->state.UNIX.main = PL_UNIX_FILETYPE;
768             result = ftp_pl_insert_finfo(conn, finfo);
769             if(result) {
770               PL_ERROR(conn, result);
771               return bufflen;
772             }
773           }
774           else {
775             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
776             return bufflen;
777           }
778           break;
779         }
780         break;
781       case PL_UNIX_SYMLINK:
782         switch(parser->state.UNIX.sub.symlink) {
783         case PL_UNIX_SYMLINK_PRESPACE:
784           if(c != ' ') {
785             parser->item_offset = finfo->b_used - 1;
786             parser->item_length = 1;
787             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
788           }
789           break;
790         case PL_UNIX_SYMLINK_NAME:
791           parser->item_length++;
792           if(c == ' ') {
793             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
794           }
795           else if(c == '\r' || c == '\n') {
796             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
797             return bufflen;
798           }
799           break;
800         case PL_UNIX_SYMLINK_PRETARGET1:
801           parser->item_length++;
802           if(c == '-') {
803             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
804           }
805           else if(c == '\r' || c == '\n') {
806             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
807             return bufflen;
808           }
809           else {
810             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
811           }
812           break;
813         case PL_UNIX_SYMLINK_PRETARGET2:
814           parser->item_length++;
815           if(c == '>') {
816             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
817           }
818           else if(c == '\r' || c == '\n') {
819             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
820             return bufflen;
821           }
822           else {
823             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
824           }
825           break;
826         case PL_UNIX_SYMLINK_PRETARGET3:
827           parser->item_length++;
828           if(c == ' ') {
829             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
830             /* now place where is symlink following */
831             finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
832             parser->offsets.filename = parser->item_offset;
833             parser->item_length = 0;
834             parser->item_offset = 0;
835           }
836           else if(c == '\r' || c == '\n') {
837             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
838             return bufflen;
839           }
840           else {
841             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
842           }
843           break;
844         case PL_UNIX_SYMLINK_PRETARGET4:
845           if(c != '\r' && c != '\n') {
846             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
847             parser->item_offset = finfo->b_used - 1;
848             parser->item_length = 1;
849           }
850           else {
851             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
852             return bufflen;
853           }
854           break;
855         case PL_UNIX_SYMLINK_TARGET:
856           parser->item_length ++;
857           if(c == '\r') {
858             parser->item_length --;
859             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
860           }
861           else if(c == '\n') {
862             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
863             parser->offsets.symlink_target = parser->item_offset;
864             result = ftp_pl_insert_finfo(conn, finfo);
865             if(result) {
866               PL_ERROR(conn, result);
867               return bufflen;
868             }
869             parser->state.UNIX.main = PL_UNIX_FILETYPE;
870           }
871           break;
872         case PL_UNIX_SYMLINK_WINDOWSEOL:
873           if(c == '\n') {
874             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
875             parser->offsets.symlink_target = parser->item_offset;
876             result = ftp_pl_insert_finfo(conn, finfo);
877             if(result) {
878               PL_ERROR(conn, result);
879               return bufflen;
880             }
881             parser->state.UNIX.main = PL_UNIX_FILETYPE;
882           }
883           else {
884             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
885             return bufflen;
886           }
887           break;
888         }
889         break;
890       }
891       break;
892     case OS_TYPE_WIN_NT:
893       switch(parser->state.NT.main) {
894       case PL_WINNT_DATE:
895         parser->item_length++;
896         if(parser->item_length < 9) {
897           if(!strchr("0123456789-", c)) { /* only simple control */
898             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
899             return bufflen;
900           }
901         }
902         else if(parser->item_length == 9) {
903           if(c == ' ') {
904             parser->state.NT.main = PL_WINNT_TIME;
905             parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
906           }
907           else {
908             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
909             return bufflen;
910           }
911         }
912         else {
913           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
914           return bufflen;
915         }
916         break;
917       case PL_WINNT_TIME:
918         parser->item_length++;
919         switch(parser->state.NT.sub.time) {
920         case PL_WINNT_TIME_PRESPACE:
921           if(!ISSPACE(c)) {
922             parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
923           }
924           break;
925         case PL_WINNT_TIME_TIME:
926           if(c == ' ') {
927             parser->offsets.time = parser->item_offset;
928             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
929             parser->state.NT.main = PL_WINNT_DIRORSIZE;
930             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
931             parser->item_length = 0;
932           }
933           else if(!strchr("APM0123456789:", c)) {
934             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
935             return bufflen;
936           }
937           break;
938         }
939         break;
940       case PL_WINNT_DIRORSIZE:
941         switch(parser->state.NT.sub.dirorsize) {
942         case PL_WINNT_DIRORSIZE_PRESPACE:
943           if(c == ' ') {
944
945           }
946           else {
947             parser->item_offset = finfo->b_used - 1;
948             parser->item_length = 1;
949             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
950           }
951           break;
952         case PL_WINNT_DIRORSIZE_CONTENT:
953           parser->item_length ++;
954           if(c == ' ') {
955             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
956             if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
957               finfo->filetype = CURLFILETYPE_DIRECTORY;
958               finfo->size = 0;
959             }
960             else {
961               char *endptr;
962               finfo->size = curlx_strtoofft(finfo->b_data +
963                                             parser->item_offset,
964                                             &endptr, 10);
965               if(!*endptr) {
966                 if(finfo->size == CURL_OFF_T_MAX ||
967                    finfo->size == CURL_OFF_T_MIN) {
968                   if(errno == ERANGE) {
969                     PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
970                     return bufflen;
971                   }
972                 }
973               }
974               else {
975                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
976                 return bufflen;
977               }
978               /* correct file type */
979               parser->file_data->filetype = CURLFILETYPE_FILE;
980             }
981
982             parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
983             parser->item_length = 0;
984             parser->state.NT.main = PL_WINNT_FILENAME;
985             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
986           }
987           break;
988         }
989         break;
990       case PL_WINNT_FILENAME:
991         switch (parser->state.NT.sub.filename) {
992         case PL_WINNT_FILENAME_PRESPACE:
993           if(c != ' ') {
994             parser->item_offset = finfo->b_used -1;
995             parser->item_length = 1;
996             parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
997           }
998           break;
999         case PL_WINNT_FILENAME_CONTENT:
1000           parser->item_length++;
1001           if(c == '\r') {
1002             parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
1003             finfo->b_data[finfo->b_used - 1] = 0;
1004           }
1005           else if(c == '\n') {
1006             parser->offsets.filename = parser->item_offset;
1007             finfo->b_data[finfo->b_used - 1] = 0;
1008             parser->offsets.filename = parser->item_offset;
1009             result = ftp_pl_insert_finfo(conn, finfo);
1010             if(result) {
1011               PL_ERROR(conn, result);
1012               return bufflen;
1013             }
1014             parser->state.NT.main = PL_WINNT_DATE;
1015             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1016           }
1017           break;
1018         case PL_WINNT_FILENAME_WINEOL:
1019           if(c == '\n') {
1020             parser->offsets.filename = parser->item_offset;
1021             result = ftp_pl_insert_finfo(conn, finfo);
1022             if(result) {
1023               PL_ERROR(conn, result);
1024               return bufflen;
1025             }
1026             parser->state.NT.main = PL_WINNT_DATE;
1027             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1028           }
1029           else {
1030             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
1031             return bufflen;
1032           }
1033           break;
1034         }
1035         break;
1036       }
1037       break;
1038     default:
1039       return bufflen + 1;
1040     }
1041
1042     i++;
1043   }
1044
1045   return bufflen;
1046 }
1047
1048 #endif /* CURL_DISABLE_FTP */