Git init
[external/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_TOTALSIZE = 0,
65   PL_UNIX_FILETYPE,
66   PL_UNIX_PERMISSION,
67   PL_UNIX_HLINKS,
68   PL_UNIX_USER,
69   PL_UNIX_GROUP,
70   PL_UNIX_SIZE,
71   PL_UNIX_TIME,
72   PL_UNIX_FILENAME,
73   PL_UNIX_SYMLINK
74 } pl_unix_mainstate;
75
76 typedef union {
77   enum {
78     PL_UNIX_TOTALSIZE_INIT = 0,
79     PL_UNIX_TOTALSIZE_READING
80   } total_dirsize;
81
82   enum {
83     PL_UNIX_HLINKS_PRESPACE = 0,
84     PL_UNIX_HLINKS_NUMBER
85   } hlinks;
86
87   enum {
88     PL_UNIX_USER_PRESPACE = 0,
89     PL_UNIX_USER_PARSING
90   } user;
91
92   enum {
93     PL_UNIX_GROUP_PRESPACE = 0,
94     PL_UNIX_GROUP_NAME
95   } group;
96
97   enum {
98     PL_UNIX_SIZE_PRESPACE = 0,
99     PL_UNIX_SIZE_NUMBER
100   } size;
101
102   enum {
103     PL_UNIX_TIME_PREPART1 = 0,
104     PL_UNIX_TIME_PART1,
105     PL_UNIX_TIME_PREPART2,
106     PL_UNIX_TIME_PART2,
107     PL_UNIX_TIME_PREPART3,
108     PL_UNIX_TIME_PART3
109   } time;
110
111   enum {
112     PL_UNIX_FILENAME_PRESPACE = 0,
113     PL_UNIX_FILENAME_NAME,
114     PL_UNIX_FILENAME_WINDOWSEOL
115   } filename;
116
117   enum {
118     PL_UNIX_SYMLINK_PRESPACE = 0,
119     PL_UNIX_SYMLINK_NAME,
120     PL_UNIX_SYMLINK_PRETARGET1,
121     PL_UNIX_SYMLINK_PRETARGET2,
122     PL_UNIX_SYMLINK_PRETARGET3,
123     PL_UNIX_SYMLINK_PRETARGET4,
124     PL_UNIX_SYMLINK_TARGET,
125     PL_UNIX_SYMLINK_WINDOWSEOL
126   } symlink;
127 } pl_unix_substate;
128
129 typedef enum {
130   PL_WINNT_DATE = 0,
131   PL_WINNT_TIME,
132   PL_WINNT_DIRORSIZE,
133   PL_WINNT_FILENAME
134 } pl_winNT_mainstate;
135
136 typedef union {
137   enum {
138     PL_WINNT_TIME_PRESPACE = 0,
139     PL_WINNT_TIME_TIME
140   } time;
141   enum {
142     PL_WINNT_DIRORSIZE_PRESPACE = 0,
143     PL_WINNT_DIRORSIZE_CONTENT
144   } dirorsize;
145   enum {
146     PL_WINNT_FILENAME_PRESPACE = 0,
147     PL_WINNT_FILENAME_CONTENT,
148     PL_WINNT_FILENAME_WINEOL
149   } filename;
150 } pl_winNT_substate;
151
152 /* This struct is used in wildcard downloading - for parsing LIST response */
153 struct ftp_parselist_data {
154   enum {
155     OS_TYPE_UNKNOWN = 0,
156     OS_TYPE_UNIX,
157     OS_TYPE_WIN_NT
158   } os_type;
159
160   union {
161     struct {
162       pl_unix_mainstate main;
163       pl_unix_substate sub;
164     } UNIX;
165
166     struct {
167       pl_winNT_mainstate main;
168       pl_winNT_substate sub;
169     } NT;
170   } state;
171
172   CURLcode error;
173   struct curl_fileinfo *file_data;
174   unsigned int item_length;
175   size_t item_offset;
176   struct {
177     size_t filename;
178     size_t user;
179     size_t group;
180     size_t time;
181     size_t perm;
182     size_t symlink_target;
183   } offsets;
184 };
185
186 struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
187 {
188   return calloc(1, sizeof(struct ftp_parselist_data));
189 }
190
191
192 void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data)
193 {
194   if(*pl_data)
195     free(*pl_data);
196   *pl_data = NULL;
197 }
198
199
200 CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data)
201 {
202   return pl_data->error;
203 }
204
205
206 #define FTP_LP_MALFORMATED_PERM 0x01000000
207
208 static int ftp_pl_get_permission(const char *str)
209 {
210   int permissions = 0;
211   /* USER */
212   if(str[0] == 'r')
213     permissions |= 1 << 8;
214   else if(str[0] != '-')
215     permissions |= FTP_LP_MALFORMATED_PERM;
216   if(str[1] == 'w')
217     permissions |= 1 << 7;
218   else if(str[1] != '-')
219     permissions |= FTP_LP_MALFORMATED_PERM;
220
221   if(str[2] == 'x')
222     permissions |= 1 << 6;
223   else if(str[2] == 's') {
224     permissions |= 1 << 6;
225     permissions |= 1 << 11;
226   }
227   else if(str[2] == 'S')
228     permissions |= 1 << 11;
229   else if(str[2] != '-')
230     permissions |= FTP_LP_MALFORMATED_PERM;
231   /* GROUP */
232   if(str[3] == 'r')
233     permissions |= 1 << 5;
234   else if(str[3] != '-')
235     permissions |= FTP_LP_MALFORMATED_PERM;
236   if(str[4] == 'w')
237     permissions |= 1 << 4;
238   else if(str[4] != '-')
239     permissions |= FTP_LP_MALFORMATED_PERM;
240   if(str[5] == 'x')
241     permissions |= 1 << 3;
242   else if(str[5] == 's') {
243     permissions |= 1 << 3;
244     permissions |= 1 << 10;
245   }
246   else if(str[5] == 'S')
247     permissions |= 1 << 10;
248   else if(str[5] != '-')
249     permissions |= FTP_LP_MALFORMATED_PERM;
250   /* others */
251   if(str[6] == 'r')
252     permissions |= 1 << 2;
253   else if(str[6] != '-')
254     permissions |= FTP_LP_MALFORMATED_PERM;
255   if(str[7] == 'w')
256     permissions |= 1 << 1;
257   else if(str[7] != '-')
258       permissions |= FTP_LP_MALFORMATED_PERM;
259   if(str[8] == 'x')
260     permissions |= 1;
261   else if(str[8] == 't') {
262     permissions |= 1;
263     permissions |= 1 << 9;
264   }
265   else if(str[8] == 'T')
266     permissions |= 1 << 9;
267   else if(str[8] != '-')
268     permissions |= FTP_LP_MALFORMATED_PERM;
269
270   return permissions;
271 }
272
273 static void PL_ERROR(struct connectdata *conn, CURLcode err)
274 {
275   struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
276   struct ftp_parselist_data *parser = tmpdata->parser;
277   if(parser->file_data)
278     Curl_fileinfo_dtor(NULL, parser->file_data);
279   parser->file_data = NULL;
280   parser->error = err;
281 }
282
283 static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *string)
284 {
285   (void)parser;
286   (void)string;
287   /* TODO
288    * There could be possible parse timestamp from server. Leaving unimplemented
289    * for now.
290    * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME flag to
291    * parser->file_data->flags
292    *
293    * Ftp servers are giving usually these formats:
294    *  Apr 11  1998 (unknown time.. set it to 00:00:00?)
295    *  Apr 11 12:21 (unknown year -> set it to NOW() time?)
296    *  08-05-09  02:49PM  (ms-dos format)
297    *  20100421092538 -> for MLST/MLSD response
298    */
299
300   return FALSE;
301 }
302
303 static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
304                                     struct curl_fileinfo *finfo)
305 {
306   curl_fnmatch_callback compare;
307   struct WildcardData *wc = &conn->data->wildcard;
308   struct ftp_wc_tmpdata *tmpdata = wc->tmp;
309   struct curl_llist *llist = wc->filelist;
310   struct ftp_parselist_data *parser = tmpdata->parser;
311   bool add = TRUE;
312
313   /* move finfo pointers to b_data */
314   char *str = finfo->b_data;
315   finfo->filename       = str + parser->offsets.filename;
316   finfo->strings.group  = parser->offsets.group ?
317                           str + parser->offsets.group : NULL;
318   finfo->strings.perm   = parser->offsets.perm ?
319                           str + parser->offsets.perm : NULL;
320   finfo->strings.target = parser->offsets.symlink_target ?
321                           str + parser->offsets.symlink_target : NULL;
322   finfo->strings.time   = str + parser->offsets.time;
323   finfo->strings.user   = parser->offsets.user ?
324                           str + parser->offsets.user : NULL;
325
326   /* get correct fnmatch callback */
327   compare = conn->data->set.fnmatch;
328   if(!compare)
329     compare = Curl_fnmatch;
330
331   /* filter pattern-corresponding filenames */
332   if(compare(conn->data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) {
333     /* discard symlink which is containing multiple " -> " */
334     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
335        (strstr(finfo->strings.target, " -> "))) {
336       add = FALSE;
337     }
338   }
339   else {
340     add = FALSE;
341   }
342
343   if(add) {
344     if(!Curl_llist_insert_next(llist, llist->tail, finfo)) {
345       Curl_fileinfo_dtor(NULL, finfo);
346       tmpdata->parser->file_data = NULL;
347       return CURLE_OUT_OF_MEMORY;
348     }
349   }
350   else {
351     Curl_fileinfo_dtor(NULL, finfo);
352   }
353
354   tmpdata->parser->file_data = NULL;
355   return CURLE_OK;
356 }
357
358 size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
359                           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++] = c;
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         parser->error = CURLE_OUT_OF_MEMORY;
419         PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
420         return bufflen;
421       }
422     }
423
424     switch (parser->os_type) {
425     case OS_TYPE_UNIX:
426       switch (parser->state.UNIX.main) {
427       case PL_UNIX_TOTALSIZE:
428         switch(parser->state.UNIX.sub.total_dirsize) {
429         case PL_UNIX_TOTALSIZE_INIT:
430           if(c == 't') {
431             parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
432             parser->item_length++;
433           }
434           else {
435             parser->state.UNIX.main = PL_UNIX_FILETYPE;
436             /* start FSM again not considering size of directory */
437             finfo->b_used = 0;
438             i--;
439           }
440           break;
441         case PL_UNIX_TOTALSIZE_READING:
442           parser->item_length++;
443           if(c == '\r') {
444             parser->item_length--;
445             finfo->b_used--;
446           }
447           else if(c == '\n') {
448             finfo->b_data[parser->item_length - 1] = 0;
449             if(strncmp("total ", finfo->b_data, 6) == 0) {
450               char *endptr = NULL;
451               /* here we can deal with directory size */
452               curlx_strtoofft(finfo->b_data+6, &endptr, 10);
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             rc = ftp_pl_insert_finfo(conn, finfo);
757             if(rc) {
758               PL_ERROR(conn, rc);
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             rc = ftp_pl_insert_finfo(conn, finfo);
769             if(rc) {
770               PL_ERROR(conn, rc);
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             rc = ftp_pl_insert_finfo(conn, finfo);
865             if(rc) {
866               PL_ERROR(conn, rc);
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             rc = ftp_pl_insert_finfo(conn, finfo);
877             if(rc) {
878               PL_ERROR(conn, rc);
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 + parser->item_offset,
963                                             &endptr, 10);
964               if(!*endptr) {
965                 if(finfo->size == CURL_OFF_T_MAX ||
966                    finfo->size == CURL_OFF_T_MIN) {
967                   if(errno == ERANGE) {
968                     PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
969                     return bufflen;
970                   }
971                 }
972               }
973               else {
974                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
975                 return bufflen;
976               }
977               /* correct file type */
978               parser->file_data->filetype = CURLFILETYPE_FILE;
979             }
980
981             parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
982             parser->item_length = 0;
983             parser->state.NT.main = PL_WINNT_FILENAME;
984             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
985           }
986           break;
987         }
988         break;
989       case PL_WINNT_FILENAME:
990         switch (parser->state.NT.sub.filename) {
991         case PL_WINNT_FILENAME_PRESPACE:
992           if(c != ' ') {
993             parser->item_offset = finfo->b_used -1;
994             parser->item_length = 1;
995             parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
996           }
997           break;
998         case PL_WINNT_FILENAME_CONTENT:
999           parser->item_length++;
1000           if(c == '\r') {
1001             parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
1002             finfo->b_data[finfo->b_used - 1] = 0;
1003           }
1004           else if(c == '\n') {
1005             parser->offsets.filename = parser->item_offset;
1006             finfo->b_data[finfo->b_used - 1] = 0;
1007             parser->offsets.filename = parser->item_offset;
1008             rc = ftp_pl_insert_finfo(conn, finfo);
1009             if(rc) {
1010               PL_ERROR(conn, rc);
1011               return bufflen;
1012             }
1013             parser->state.NT.main = PL_WINNT_DATE;
1014             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1015           }
1016           break;
1017         case PL_WINNT_FILENAME_WINEOL:
1018           if(c == '\n') {
1019             parser->offsets.filename = parser->item_offset;
1020             rc = ftp_pl_insert_finfo(conn, finfo);
1021             if(rc) {
1022               PL_ERROR(conn, rc);
1023               return bufflen;
1024             }
1025             parser->state.NT.main = PL_WINNT_DATE;
1026             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1027           }
1028           else {
1029             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
1030             return bufflen;
1031           }
1032           break;
1033         }
1034         break;
1035       }
1036       break;
1037     default:
1038       return bufflen+1;
1039     }
1040
1041     i++;
1042   }
1043
1044   return bufflen;
1045 }