Imported Upstream version 7.40.0
[platform/upstream/curl.git] / lib / ftplistparser.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2014, 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 result;
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, pass the leading white
453                  spaces and then the digits */
454               while(ISSPACE(*endptr))
455                 endptr++;
456               while(ISDIGIT(*endptr))
457                 endptr++;
458               if(*endptr != 0) {
459                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
460                 return bufflen;
461               }
462               else {
463                 parser->state.UNIX.main = PL_UNIX_FILETYPE;
464                 finfo->b_used = 0;
465               }
466             }
467             else {
468               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
469               return bufflen;
470             }
471           }
472           break;
473         }
474         break;
475       case PL_UNIX_FILETYPE:
476         switch (c) {
477         case '-':
478           finfo->filetype = CURLFILETYPE_FILE;
479           break;
480         case 'd':
481           finfo->filetype = CURLFILETYPE_DIRECTORY;
482           break;
483         case 'l':
484           finfo->filetype = CURLFILETYPE_SYMLINK;
485           break;
486         case 'p':
487           finfo->filetype = CURLFILETYPE_NAMEDPIPE;
488           break;
489         case 's':
490           finfo->filetype = CURLFILETYPE_SOCKET;
491           break;
492         case 'c':
493           finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
494           break;
495         case 'b':
496           finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
497           break;
498         case 'D':
499           finfo->filetype = CURLFILETYPE_DOOR;
500           break;
501         default:
502           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
503           return bufflen;
504         }
505         parser->state.UNIX.main = PL_UNIX_PERMISSION;
506         parser->item_length = 0;
507         parser->item_offset = 1;
508         break;
509       case PL_UNIX_PERMISSION:
510         parser->item_length++;
511         if(parser->item_length <= 9) {
512           if(!strchr("rwx-tTsS", c)) {
513             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
514             return bufflen;
515           }
516         }
517         else if(parser->item_length == 10) {
518           unsigned int perm;
519           if(c != ' ') {
520             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
521             return bufflen;
522           }
523           finfo->b_data[10] = 0; /* terminate permissions */
524           perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
525           if(perm & FTP_LP_MALFORMATED_PERM) {
526             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
527             return bufflen;
528           }
529           parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM;
530           parser->file_data->perm = perm;
531           parser->offsets.perm = parser->item_offset;
532
533           parser->item_length = 0;
534           parser->state.UNIX.main = PL_UNIX_HLINKS;
535           parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
536         }
537         break;
538       case PL_UNIX_HLINKS:
539         switch(parser->state.UNIX.sub.hlinks) {
540         case PL_UNIX_HLINKS_PRESPACE:
541           if(c != ' ') {
542             if(c >= '0' && c <= '9') {
543               parser->item_offset = finfo->b_used - 1;
544               parser->item_length = 1;
545               parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
546             }
547             else {
548               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
549               return bufflen;
550             }
551           }
552           break;
553         case PL_UNIX_HLINKS_NUMBER:
554           parser->item_length ++;
555           if(c == ' ') {
556             char *p;
557             long int hlinks;
558             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
559             hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
560             if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
561               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
562               parser->file_data->hardlinks = hlinks;
563             }
564             parser->item_length = 0;
565             parser->item_offset = 0;
566             parser->state.UNIX.main = PL_UNIX_USER;
567             parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
568           }
569           else if(c < '0' || c > '9') {
570             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
571             return bufflen;
572           }
573           break;
574         }
575         break;
576       case PL_UNIX_USER:
577         switch(parser->state.UNIX.sub.user) {
578         case PL_UNIX_USER_PRESPACE:
579           if(c != ' ') {
580             parser->item_offset = finfo->b_used - 1;
581             parser->item_length = 1;
582             parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
583           }
584           break;
585         case PL_UNIX_USER_PARSING:
586           parser->item_length++;
587           if(c == ' ') {
588             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
589             parser->offsets.user = parser->item_offset;
590             parser->state.UNIX.main = PL_UNIX_GROUP;
591             parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
592             parser->item_offset = 0;
593             parser->item_length = 0;
594           }
595           break;
596         }
597         break;
598       case PL_UNIX_GROUP:
599         switch(parser->state.UNIX.sub.group) {
600         case PL_UNIX_GROUP_PRESPACE:
601           if(c != ' ') {
602             parser->item_offset = finfo->b_used - 1;
603             parser->item_length = 1;
604             parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
605           }
606           break;
607         case PL_UNIX_GROUP_NAME:
608           parser->item_length++;
609           if(c == ' ') {
610             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
611             parser->offsets.group = parser->item_offset;
612             parser->state.UNIX.main = PL_UNIX_SIZE;
613             parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
614             parser->item_offset = 0;
615             parser->item_length = 0;
616           }
617           break;
618         }
619         break;
620       case PL_UNIX_SIZE:
621         switch(parser->state.UNIX.sub.size) {
622         case PL_UNIX_SIZE_PRESPACE:
623           if(c != ' ') {
624             if(c >= '0' && c <= '9') {
625               parser->item_offset = finfo->b_used - 1;
626               parser->item_length = 1;
627               parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
628             }
629             else {
630               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
631               return bufflen;
632             }
633           }
634           break;
635         case PL_UNIX_SIZE_NUMBER:
636           parser->item_length++;
637           if(c == ' ') {
638             char *p;
639             curl_off_t fsize;
640             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
641             fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10);
642             if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
643                                fsize != CURL_OFF_T_MIN) {
644               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
645               parser->file_data->size = fsize;
646             }
647             parser->item_length = 0;
648             parser->item_offset = 0;
649             parser->state.UNIX.main = PL_UNIX_TIME;
650             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
651           }
652           else if(!ISDIGIT(c)) {
653             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
654             return bufflen;
655           }
656           break;
657         }
658         break;
659       case PL_UNIX_TIME:
660         switch(parser->state.UNIX.sub.time) {
661         case PL_UNIX_TIME_PREPART1:
662           if(c != ' ') {
663             if(ISALNUM(c)) {
664               parser->item_offset = finfo->b_used -1;
665               parser->item_length = 1;
666               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
667             }
668             else {
669               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
670               return bufflen;
671             }
672           }
673           break;
674         case PL_UNIX_TIME_PART1:
675           parser->item_length++;
676           if(c == ' ') {
677             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
678           }
679           else if(!ISALNUM(c) && c != '.') {
680             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
681             return bufflen;
682           }
683           break;
684         case PL_UNIX_TIME_PREPART2:
685           parser->item_length++;
686           if(c != ' ') {
687             if(ISALNUM(c)) {
688               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
689             }
690             else {
691               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
692               return bufflen;
693             }
694           }
695           break;
696         case PL_UNIX_TIME_PART2:
697           parser->item_length++;
698           if(c == ' ') {
699             parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
700           }
701           else if(!ISALNUM(c) && c != '.') {
702             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
703             return bufflen;
704           }
705           break;
706         case PL_UNIX_TIME_PREPART3:
707           parser->item_length++;
708           if(c != ' ') {
709             if(ISALNUM(c)) {
710               parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
711             }
712             else {
713               PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
714               return bufflen;
715             }
716           }
717           break;
718         case PL_UNIX_TIME_PART3:
719           parser->item_length++;
720           if(c == ' ') {
721             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
722             parser->offsets.time = parser->item_offset;
723             if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
724               parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
725             }
726             if(finfo->filetype == CURLFILETYPE_SYMLINK) {
727               parser->state.UNIX.main = PL_UNIX_SYMLINK;
728               parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
729             }
730             else {
731               parser->state.UNIX.main = PL_UNIX_FILENAME;
732               parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
733             }
734           }
735           else if(!ISALNUM(c) && c != '.' && c != ':') {
736             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
737             return bufflen;
738           }
739           break;
740         }
741         break;
742       case PL_UNIX_FILENAME:
743         switch(parser->state.UNIX.sub.filename) {
744         case PL_UNIX_FILENAME_PRESPACE:
745           if(c != ' ') {
746             parser->item_offset = finfo->b_used - 1;
747             parser->item_length = 1;
748             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
749           }
750           break;
751         case PL_UNIX_FILENAME_NAME:
752           parser->item_length++;
753           if(c == '\r') {
754             parser->item_length--;
755             parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
756           }
757           else if(c == '\n') {
758             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
759             parser->offsets.filename = parser->item_offset;
760             parser->state.UNIX.main = PL_UNIX_FILETYPE;
761             result = ftp_pl_insert_finfo(conn, finfo);
762             if(result) {
763               PL_ERROR(conn, result);
764               return bufflen;
765             }
766           }
767           break;
768         case PL_UNIX_FILENAME_WINDOWSEOL:
769           if(c == '\n') {
770             finfo->b_data[parser->item_offset + parser->item_length] = 0;
771             parser->offsets.filename = parser->item_offset;
772             parser->state.UNIX.main = PL_UNIX_FILETYPE;
773             result = ftp_pl_insert_finfo(conn, finfo);
774             if(result) {
775               PL_ERROR(conn, result);
776               return bufflen;
777             }
778           }
779           else {
780             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
781             return bufflen;
782           }
783           break;
784         }
785         break;
786       case PL_UNIX_SYMLINK:
787         switch(parser->state.UNIX.sub.symlink) {
788         case PL_UNIX_SYMLINK_PRESPACE:
789           if(c != ' ') {
790             parser->item_offset = finfo->b_used - 1;
791             parser->item_length = 1;
792             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
793           }
794           break;
795         case PL_UNIX_SYMLINK_NAME:
796           parser->item_length++;
797           if(c == ' ') {
798             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
799           }
800           else if(c == '\r' || c == '\n') {
801             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
802             return bufflen;
803           }
804           break;
805         case PL_UNIX_SYMLINK_PRETARGET1:
806           parser->item_length++;
807           if(c == '-') {
808             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
809           }
810           else if(c == '\r' || c == '\n') {
811             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
812             return bufflen;
813           }
814           else {
815             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
816           }
817           break;
818         case PL_UNIX_SYMLINK_PRETARGET2:
819           parser->item_length++;
820           if(c == '>') {
821             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
822           }
823           else if(c == '\r' || c == '\n') {
824             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
825             return bufflen;
826           }
827           else {
828             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
829           }
830           break;
831         case PL_UNIX_SYMLINK_PRETARGET3:
832           parser->item_length++;
833           if(c == ' ') {
834             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
835             /* now place where is symlink following */
836             finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
837             parser->offsets.filename = parser->item_offset;
838             parser->item_length = 0;
839             parser->item_offset = 0;
840           }
841           else if(c == '\r' || c == '\n') {
842             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
843             return bufflen;
844           }
845           else {
846             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
847           }
848           break;
849         case PL_UNIX_SYMLINK_PRETARGET4:
850           if(c != '\r' && c != '\n') {
851             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
852             parser->item_offset = finfo->b_used - 1;
853             parser->item_length = 1;
854           }
855           else {
856             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
857             return bufflen;
858           }
859           break;
860         case PL_UNIX_SYMLINK_TARGET:
861           parser->item_length ++;
862           if(c == '\r') {
863             parser->item_length --;
864             parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
865           }
866           else if(c == '\n') {
867             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
868             parser->offsets.symlink_target = parser->item_offset;
869             result = ftp_pl_insert_finfo(conn, finfo);
870             if(result) {
871               PL_ERROR(conn, result);
872               return bufflen;
873             }
874             parser->state.UNIX.main = PL_UNIX_FILETYPE;
875           }
876           break;
877         case PL_UNIX_SYMLINK_WINDOWSEOL:
878           if(c == '\n') {
879             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
880             parser->offsets.symlink_target = parser->item_offset;
881             result = ftp_pl_insert_finfo(conn, finfo);
882             if(result) {
883               PL_ERROR(conn, result);
884               return bufflen;
885             }
886             parser->state.UNIX.main = PL_UNIX_FILETYPE;
887           }
888           else {
889             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
890             return bufflen;
891           }
892           break;
893         }
894         break;
895       }
896       break;
897     case OS_TYPE_WIN_NT:
898       switch(parser->state.NT.main) {
899       case PL_WINNT_DATE:
900         parser->item_length++;
901         if(parser->item_length < 9) {
902           if(!strchr("0123456789-", c)) { /* only simple control */
903             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
904             return bufflen;
905           }
906         }
907         else if(parser->item_length == 9) {
908           if(c == ' ') {
909             parser->state.NT.main = PL_WINNT_TIME;
910             parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
911           }
912           else {
913             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
914             return bufflen;
915           }
916         }
917         else {
918           PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
919           return bufflen;
920         }
921         break;
922       case PL_WINNT_TIME:
923         parser->item_length++;
924         switch(parser->state.NT.sub.time) {
925         case PL_WINNT_TIME_PRESPACE:
926           if(!ISSPACE(c)) {
927             parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
928           }
929           break;
930         case PL_WINNT_TIME_TIME:
931           if(c == ' ') {
932             parser->offsets.time = parser->item_offset;
933             finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
934             parser->state.NT.main = PL_WINNT_DIRORSIZE;
935             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
936             parser->item_length = 0;
937           }
938           else if(!strchr("APM0123456789:", c)) {
939             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
940             return bufflen;
941           }
942           break;
943         }
944         break;
945       case PL_WINNT_DIRORSIZE:
946         switch(parser->state.NT.sub.dirorsize) {
947         case PL_WINNT_DIRORSIZE_PRESPACE:
948           if(c == ' ') {
949
950           }
951           else {
952             parser->item_offset = finfo->b_used - 1;
953             parser->item_length = 1;
954             parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
955           }
956           break;
957         case PL_WINNT_DIRORSIZE_CONTENT:
958           parser->item_length ++;
959           if(c == ' ') {
960             finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
961             if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
962               finfo->filetype = CURLFILETYPE_DIRECTORY;
963               finfo->size = 0;
964             }
965             else {
966               char *endptr;
967               finfo->size = curlx_strtoofft(finfo->b_data +
968                                             parser->item_offset,
969                                             &endptr, 10);
970               if(!*endptr) {
971                 if(finfo->size == CURL_OFF_T_MAX ||
972                    finfo->size == CURL_OFF_T_MIN) {
973                   if(errno == ERANGE) {
974                     PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
975                     return bufflen;
976                   }
977                 }
978               }
979               else {
980                 PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
981                 return bufflen;
982               }
983               /* correct file type */
984               parser->file_data->filetype = CURLFILETYPE_FILE;
985             }
986
987             parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
988             parser->item_length = 0;
989             parser->state.NT.main = PL_WINNT_FILENAME;
990             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
991           }
992           break;
993         }
994         break;
995       case PL_WINNT_FILENAME:
996         switch (parser->state.NT.sub.filename) {
997         case PL_WINNT_FILENAME_PRESPACE:
998           if(c != ' ') {
999             parser->item_offset = finfo->b_used -1;
1000             parser->item_length = 1;
1001             parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
1002           }
1003           break;
1004         case PL_WINNT_FILENAME_CONTENT:
1005           parser->item_length++;
1006           if(c == '\r') {
1007             parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
1008             finfo->b_data[finfo->b_used - 1] = 0;
1009           }
1010           else if(c == '\n') {
1011             parser->offsets.filename = parser->item_offset;
1012             finfo->b_data[finfo->b_used - 1] = 0;
1013             parser->offsets.filename = parser->item_offset;
1014             result = ftp_pl_insert_finfo(conn, finfo);
1015             if(result) {
1016               PL_ERROR(conn, result);
1017               return bufflen;
1018             }
1019             parser->state.NT.main = PL_WINNT_DATE;
1020             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1021           }
1022           break;
1023         case PL_WINNT_FILENAME_WINEOL:
1024           if(c == '\n') {
1025             parser->offsets.filename = parser->item_offset;
1026             result = ftp_pl_insert_finfo(conn, finfo);
1027             if(result) {
1028               PL_ERROR(conn, result);
1029               return bufflen;
1030             }
1031             parser->state.NT.main = PL_WINNT_DATE;
1032             parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1033           }
1034           else {
1035             PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
1036             return bufflen;
1037           }
1038           break;
1039         }
1040         break;
1041       }
1042       break;
1043     default:
1044       return bufflen + 1;
1045     }
1046
1047     i++;
1048   }
1049
1050   return bufflen;
1051 }
1052
1053 #endif /* CURL_DISABLE_FTP */