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