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