build: fix circular header inclusion with other packages
[platform/upstream/curl.git] / lib / ftplistparser.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22
23 /**
24  * Now implemented:
25  *
26  * 1) UNIX version 1
27  * drwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog
28  * 2) UNIX version 2
29  * drwxr-xr-x 1 user01 ftp  512 Jan 29 1997  prog
30  * 3) UNIX version 3
31  * drwxr-xr-x 1      1   1  512 Jan 29 23:32 prog
32  * 4) UNIX symlink
33  * lrwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog -> prog2000
34  * 5) DOS style
35  * 01-29-97 11:32PM <DIR> prog
36  */
37
38 #include "curl_setup.h"
39
40 #ifndef CURL_DISABLE_FTP
41
42 #include <curl/curl.h>
43
44 #include "urldata.h"
45 #include "fileinfo.h"
46 #include "llist.h"
47 #include "strtoofft.h"
48 #include "rawstr.h"
49 #include "ftp.h"
50 #include "ftplistparser.h"
51 #include "curl_fnmatch.h"
52
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,
333              finfo->filename) == 0) {
334     /* discard symlink which is containing multiple " -> " */
335     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
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 Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
360                           void *connptr)
361 {
362   size_t bufflen = size*nmemb;
363   struct connectdata *conn = (struct connectdata *)connptr;
364   struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
365   struct ftp_parselist_data *parser = tmpdata->parser;
366   struct curl_fileinfo *finfo;
367   unsigned long i = 0;
368   CURLcode rc;
369
370   if(parser->error) { /* error in previous call */
371     /* scenario:
372      * 1. call => OK..
373      * 2. call => OUT_OF_MEMORY (or other error)
374      * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
375      *    in wc_statemach()
376      */
377     return bufflen;
378   }
379
380   if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
381     /* considering info about FILE response format */
382     parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
383                        OS_TYPE_WIN_NT : OS_TYPE_UNIX;
384   }
385
386   while(i < bufflen) { /* FSM */
387
388     char c = buffer[i];
389     if(!parser->file_data) { /* tmp file data is not allocated yet */
390       parser->file_data = Curl_fileinfo_alloc();
391       if(!parser->file_data) {
392         parser->error = CURLE_OUT_OF_MEMORY;
393         return bufflen;
394       }
395       parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE);
396       if(!parser->file_data->b_data) {
397         PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
398         return bufflen;
399       }
400       parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE;
401       parser->item_offset = 0;
402       parser->item_length = 0;
403     }
404
405     finfo = parser->file_data;
406     finfo->b_data[finfo->b_used++] = c;
407
408     if(finfo->b_used >= finfo->b_size - 1) {
409       /* if it is important, extend buffer space for file data */
410       char *tmp = realloc(finfo->b_data,
411                           finfo->b_size + FTP_BUFFER_ALLOCSIZE);
412       if(tmp) {
413         finfo->b_size += FTP_BUFFER_ALLOCSIZE;
414         finfo->b_data = tmp;
415       }
416       else {
417         Curl_fileinfo_dtor(NULL, parser->file_data);
418         parser->file_data = NULL;
419         parser->error = CURLE_OUT_OF_MEMORY;
420         PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
421         return bufflen;
422       }
423     }
424
425     switch (parser->os_type) {
426     case OS_TYPE_UNIX:
427       switch (parser->state.UNIX.main) {
428       case PL_UNIX_TOTALSIZE:
429         switch(parser->state.UNIX.sub.total_dirsize) {
430         case PL_UNIX_TOTALSIZE_INIT:
431           if(c == 't') {
432             parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
433             parser->item_length++;
434           }
435           else {
436             parser->state.UNIX.main = PL_UNIX_FILETYPE;
437             /* start FSM again not considering size of directory */
438             finfo->b_used = 0;
439             i--;
440           }
441           break;
442         case PL_UNIX_TOTALSIZE_READING:
443           parser->item_length++;
444           if(c == '\r') {
445             parser->item_length--;
446             finfo->b_used--;
447           }
448           else if(c == '\n') {
449             finfo->b_data[parser->item_length - 1] = 0;
450             if(strncmp("total ", finfo->b_data, 6) == 0) {
451               char *endptr = finfo->b_data+6;
452               /* here we can deal with directory size */
453               while(ISSPACE(*endptr))
454                 endptr++;
455               if(*endptr != 0) {
456                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
457                 return bufflen;
458               }
459               else {
460                 parser->state.UNIX.main = PL_UNIX_FILETYPE;
461                 finfo->b_used = 0;
462               }
463             }
464             else {
465               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
466               return bufflen;
467             }
468           }
469           break;
470         }
471         break;
472       case PL_UNIX_FILETYPE:
473         switch (c) {
474         case '-':
475           finfo->filetype = CURLFILETYPE_FILE;
476           break;
477         case 'd':
478           finfo->filetype = CURLFILETYPE_DIRECTORY;
479           break;
480         case 'l':
481           finfo->filetype = CURLFILETYPE_SYMLINK;
482           break;
483         case 'p':
484           finfo->filetype = CURLFILETYPE_NAMEDPIPE;
485           break;
486         case 's':
487           finfo->filetype = CURLFILETYPE_SOCKET;
488           break;
489         case 'c':
490           finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
491           break;
492         case 'b':
493           finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
494           break;
495         case 'D':
496           finfo->filetype = CURLFILETYPE_DOOR;
497           break;
498         default:
499           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
500           return bufflen;
501         }
502         parser->state.UNIX.main = PL_UNIX_PERMISSION;
503         parser->item_length = 0;
504         parser->item_offset = 1;
505         break;
506       case PL_UNIX_PERMISSION:
507         parser->item_length++;
508         if(parser->item_length <= 9) {
509           if(!strchr("rwx-tTsS", c)) {
510             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
511             return bufflen;
512           }
513         }
514         else if(parser->item_length == 10) {
515           unsigned int perm;
516           if(c != ' ') {
517             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
518             return bufflen;
519           }
520           finfo->b_data[10] = 0; /* terminate permissions */
521           perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
522           if(perm & FTP_LP_MALFORMATED_PERM) {
523             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
524             return bufflen;
525           }
526           parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM;
527           parser->file_data->perm = perm;
528           parser->offsets.perm = parser->item_offset;
529
530           parser->item_length = 0;
531           parser->state.UNIX.main = PL_UNIX_HLINKS;
532           parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
533         }
534         break;
535       case PL_UNIX_HLINKS:
536         switch(parser->state.UNIX.sub.hlinks) {
537         case PL_UNIX_HLINKS_PRESPACE:
538           if(c != ' ') {
539             if(c >= '0' && c <= '9') {
540               parser->item_offset = finfo->b_used - 1;
541               parser->item_length = 1;
542               parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
543             }
544             else {
545               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
546               return bufflen;
547             }
548           }
549           break;
550         case PL_UNIX_HLINKS_NUMBER:
551           parser->item_length ++;
552           if(c == ' ') {
553             char *p;
554             long int hlinks;
555             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
556             hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
557             if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
558               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
559               parser->file_data->hardlinks = hlinks;
560             }
561             parser->item_length = 0;
562             parser->item_offset = 0;
563             parser->state.UNIX.main = PL_UNIX_USER;
564             parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
565           }
566           else if(c < '0' || c > '9') {
567             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
568             return bufflen;
569           }
570           break;
571         }
572         break;
573       case PL_UNIX_USER:
574         switch(parser->state.UNIX.sub.user) {
575         case PL_UNIX_USER_PRESPACE:
576           if(c != ' ') {
577             parser->item_offset = finfo->b_used - 1;
578             parser->item_length = 1;
579             parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
580           }
581           break;
582         case PL_UNIX_USER_PARSING:
583           parser->item_length++;
584           if(c == ' ') {
585             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
586             parser->offsets.user = parser->item_offset;
587             parser->state.UNIX.main = PL_UNIX_GROUP;
588             parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
589             parser->item_offset = 0;
590             parser->item_length = 0;
591           }
592           break;
593         }
594         break;
595       case PL_UNIX_GROUP:
596         switch(parser->state.UNIX.sub.group) {
597         case PL_UNIX_GROUP_PRESPACE:
598           if(c != ' ') {
599             parser->item_offset = finfo->b_used - 1;
600             parser->item_length = 1;
601             parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
602           }
603           break;
604         case PL_UNIX_GROUP_NAME:
605           parser->item_length++;
606           if(c == ' ') {
607             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
608             parser->offsets.group = parser->item_offset;
609             parser->state.UNIX.main = PL_UNIX_SIZE;
610             parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
611             parser->item_offset = 0;
612             parser->item_length = 0;
613           }
614           break;
615         }
616         break;
617       case PL_UNIX_SIZE:
618         switch(parser->state.UNIX.sub.size) {
619         case PL_UNIX_SIZE_PRESPACE:
620           if(c != ' ') {
621             if(c >= '0' && c <= '9') {
622               parser->item_offset = finfo->b_used - 1;
623               parser->item_length = 1;
624               parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
625             }
626             else {
627               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
628               return bufflen;
629             }
630           }
631           break;
632         case PL_UNIX_SIZE_NUMBER:
633           parser->item_length++;
634           if(c == ' ') {
635             char *p;
636             curl_off_t fsize;
637             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
638             fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10);
639             if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
640                                fsize != CURL_OFF_T_MIN) {
641               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
642               parser->file_data->size = fsize;
643             }
644             parser->item_length = 0;
645             parser->item_offset = 0;
646             parser->state.UNIX.main = PL_UNIX_TIME;
647             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
648           }
649           else if(!ISDIGIT(c)) {
650             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
651             return bufflen;
652           }
653           break;
654         }
655         break;
656       case PL_UNIX_TIME:
657         switch(parser->state.UNIX.sub.time) {
658         case PL_UNIX_TIME_PREPART1:
659           if(c != ' ') {
660             if(ISALNUM(c)) {
661               parser->item_offset = finfo->b_used -1;
662               parser->item_length = 1;
663               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
664             }
665             else {
666               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
667               return bufflen;
668             }
669           }
670           break;
671         case PL_UNIX_TIME_PART1:
672           parser->item_length++;
673           if(c == ' ') {
674             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
675           }
676           else if(!ISALNUM(c) && c != '.') {
677             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
678             return bufflen;
679           }
680           break;
681         case PL_UNIX_TIME_PREPART2:
682           parser->item_length++;
683           if(c != ' ') {
684             if(ISALNUM(c)) {
685               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
686             }
687             else {
688               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
689               return bufflen;
690             }
691           }
692           break;
693         case PL_UNIX_TIME_PART2:
694           parser->item_length++;
695           if(c == ' ') {
696             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
697           }
698           else if(!ISALNUM(c) && c != '.') {
699             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
700             return bufflen;
701           }
702           break;
703         case PL_UNIX_TIME_PREPART3:
704           parser->item_length++;
705           if(c != ' ') {
706             if(ISALNUM(c)) {
707               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
708             }
709             else {
710               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
711               return bufflen;
712             }
713           }
714           break;
715         case PL_UNIX_TIME_PART3:
716           parser->item_length++;
717           if(c == ' ') {
718             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
719             parser->offsets.time = parser->item_offset;
720             if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
721               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
722             }
723             if(finfo->filetype == CURLFILETYPE_SYMLINK) {
724               parser->state.UNIX.main = PL_UNIX_SYMLINK;
725               parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
726             }
727             else {
728               parser->state.UNIX.main = PL_UNIX_FILENAME;
729               parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
730             }
731           }
732           else if(!ISALNUM(c) && c != '.' && c != ':') {
733             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
734             return bufflen;
735           }
736           break;
737         }
738         break;
739       case PL_UNIX_FILENAME:
740         switch(parser->state.UNIX.sub.filename) {
741         case PL_UNIX_FILENAME_PRESPACE:
742           if(c != ' ') {
743             parser->item_offset = finfo->b_used - 1;
744             parser->item_length = 1;
745             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
746           }
747           break;
748         case PL_UNIX_FILENAME_NAME:
749           parser->item_length++;
750           if(c == '\r') {
751             parser->item_length--;
752             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
753           }
754           else if(c == '\n') {
755             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
756             parser->offsets.filename = parser->item_offset;
757             parser->state.UNIX.main = PL_UNIX_FILETYPE;
758             rc = ftp_pl_insert_finfo(conn, finfo);
759             if(rc) {
760               PL_ERROR(conn, rc);
761               return bufflen;
762             }
763           }
764           break;
765         case PL_UNIX_FILENAME_WINDOWSEOL:
766           if(c == '\n') {
767             finfo->b_data[parser->item_offset + parser->item_length] = 0;
768             parser->offsets.filename = parser->item_offset;
769             parser->state.UNIX.main = PL_UNIX_FILETYPE;
770             rc = ftp_pl_insert_finfo(conn, finfo);
771             if(rc) {
772               PL_ERROR(conn, rc);
773               return bufflen;
774             }
775           }
776           else {
777             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
778             return bufflen;
779           }
780           break;
781         }
782         break;
783       case PL_UNIX_SYMLINK:
784         switch(parser->state.UNIX.sub.symlink) {
785         case PL_UNIX_SYMLINK_PRESPACE:
786           if(c != ' ') {
787             parser->item_offset = finfo->b_used - 1;
788             parser->item_length = 1;
789             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
790           }
791           break;
792         case PL_UNIX_SYMLINK_NAME:
793           parser->item_length++;
794           if(c == ' ') {
795             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
796           }
797           else if(c == '\r' || c == '\n') {
798             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
799             return bufflen;
800           }
801           break;
802         case PL_UNIX_SYMLINK_PRETARGET1:
803           parser->item_length++;
804           if(c == '-') {
805             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
806           }
807           else if(c == '\r' || c == '\n') {
808             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
809             return bufflen;
810           }
811           else {
812             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
813           }
814           break;
815         case PL_UNIX_SYMLINK_PRETARGET2:
816           parser->item_length++;
817           if(c == '>') {
818             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
819           }
820           else if(c == '\r' || c == '\n') {
821             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
822             return bufflen;
823           }
824           else {
825             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
826           }
827           break;
828         case PL_UNIX_SYMLINK_PRETARGET3:
829           parser->item_length++;
830           if(c == ' ') {
831             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
832             /* now place where is symlink following */
833             finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
834             parser->offsets.filename = parser->item_offset;
835             parser->item_length = 0;
836             parser->item_offset = 0;
837           }
838           else if(c == '\r' || c == '\n') {
839             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
840             return bufflen;
841           }
842           else {
843             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
844           }
845           break;
846         case PL_UNIX_SYMLINK_PRETARGET4:
847           if(c != '\r' && c != '\n') {
848             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
849             parser->item_offset = finfo->b_used - 1;
850             parser->item_length = 1;
851           }
852           else {
853             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
854             return bufflen;
855           }
856           break;
857         case PL_UNIX_SYMLINK_TARGET:
858           parser->item_length ++;
859           if(c == '\r') {
860             parser->item_length --;
861             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
862           }
863           else if(c == '\n') {
864             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
865             parser->offsets.symlink_target = parser->item_offset;
866             rc = ftp_pl_insert_finfo(conn, finfo);
867             if(rc) {
868               PL_ERROR(conn, rc);
869               return bufflen;
870             }
871             parser->state.UNIX.main = PL_UNIX_FILETYPE;
872           }
873           break;
874         case PL_UNIX_SYMLINK_WINDOWSEOL:
875           if(c == '\n') {
876             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
877             parser->offsets.symlink_target = parser->item_offset;
878             rc = ftp_pl_insert_finfo(conn, finfo);
879             if(rc) {
880               PL_ERROR(conn, rc);
881               return bufflen;
882             }
883             parser->state.UNIX.main = PL_UNIX_FILETYPE;
884           }
885           else {
886             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
887             return bufflen;
888           }
889           break;
890         }
891         break;
892       }
893       break;
894     case OS_TYPE_WIN_NT:
895       switch(parser->state.NT.main) {
896       case PL_WINNT_DATE:
897         parser->item_length++;
898         if(parser->item_length < 9) {
899           if(!strchr("0123456789-", c)) { /* only simple control */
900             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
901             return bufflen;
902           }
903         }
904         else if(parser->item_length == 9) {
905           if(c == ' ') {
906             parser->state.NT.main = PL_WINNT_TIME;
907             parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
908           }
909           else {
910             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
911             return bufflen;
912           }
913         }
914         else {
915           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
916           return bufflen;
917         }
918         break;
919       case PL_WINNT_TIME:
920         parser->item_length++;
921         switch(parser->state.NT.sub.time) {
922         case PL_WINNT_TIME_PRESPACE:
923           if(!ISSPACE(c)) {
924             parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
925           }
926           break;
927         case PL_WINNT_TIME_TIME:
928           if(c == ' ') {
929             parser->offsets.time = parser->item_offset;
930             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
931             parser->state.NT.main = PL_WINNT_DIRORSIZE;
932             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
933             parser->item_length = 0;
934           }
935           else if(!strchr("APM0123456789:", c)) {
936             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
937             return bufflen;
938           }
939           break;
940         }
941         break;
942       case PL_WINNT_DIRORSIZE:
943         switch(parser->state.NT.sub.dirorsize) {
944         case PL_WINNT_DIRORSIZE_PRESPACE:
945           if(c == ' ') {
946
947           }
948           else {
949             parser->item_offset = finfo->b_used - 1;
950             parser->item_length = 1;
951             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
952           }
953           break;
954         case PL_WINNT_DIRORSIZE_CONTENT:
955           parser->item_length ++;
956           if(c == ' ') {
957             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
958             if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
959               finfo->filetype = CURLFILETYPE_DIRECTORY;
960               finfo->size = 0;
961             }
962             else {
963               char *endptr;
964               finfo->size = curlx_strtoofft(finfo->b_data +
965                                             parser->item_offset,
966                                             &endptr, 10);
967               if(!*endptr) {
968                 if(finfo->size == CURL_OFF_T_MAX ||
969                    finfo->size == CURL_OFF_T_MIN) {
970                   if(errno == ERANGE) {
971                     PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
972                     return bufflen;
973                   }
974                 }
975               }
976               else {
977                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
978                 return bufflen;
979               }
980               /* correct file type */
981               parser->file_data->filetype = CURLFILETYPE_FILE;
982             }
983
984             parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
985             parser->item_length = 0;
986             parser->state.NT.main = PL_WINNT_FILENAME;
987             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
988           }
989           break;
990         }
991         break;
992       case PL_WINNT_FILENAME:
993         switch (parser->state.NT.sub.filename) {
994         case PL_WINNT_FILENAME_PRESPACE:
995           if(c != ' ') {
996             parser->item_offset = finfo->b_used -1;
997             parser->item_length = 1;
998             parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
999           }
1000           break;
1001         case PL_WINNT_FILENAME_CONTENT:
1002           parser->item_length++;
1003           if(c == '\r') {
1004             parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
1005             finfo->b_data[finfo->b_used - 1] = 0;
1006           }
1007           else if(c == '\n') {
1008             parser->offsets.filename = parser->item_offset;
1009             finfo->b_data[finfo->b_used - 1] = 0;
1010             parser->offsets.filename = parser->item_offset;
1011             rc = ftp_pl_insert_finfo(conn, finfo);
1012             if(rc) {
1013               PL_ERROR(conn, rc);
1014               return bufflen;
1015             }
1016             parser->state.NT.main = PL_WINNT_DATE;
1017             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1018           }
1019           break;
1020         case PL_WINNT_FILENAME_WINEOL:
1021           if(c == '\n') {
1022             parser->offsets.filename = parser->item_offset;
1023             rc = ftp_pl_insert_finfo(conn, finfo);
1024             if(rc) {
1025               PL_ERROR(conn, rc);
1026               return bufflen;
1027             }
1028             parser->state.NT.main = PL_WINNT_DATE;
1029             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1030           }
1031           else {
1032             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
1033             return bufflen;
1034           }
1035           break;
1036         }
1037         break;
1038       }
1039       break;
1040     default:
1041       return bufflen+1;
1042     }
1043
1044     i++;
1045   }
1046
1047   return bufflen;
1048 }
1049
1050 #endif /* CURL_DISABLE_FTP */