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