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