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