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