1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2017, 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
58typedef 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
71typedef 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
124typedef enum {
125  PL_WINNT_DATE = 0,
126  PL_WINNT_TIME,
127  PL_WINNT_DIRORSIZE,
128  PL_WINNT_FILENAME
129} pl_winNT_mainstate;
130
131typedef 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 */
148struct 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 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
181struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
182{
183  return calloc(1, sizeof(struct ftp_parselist_data));
184}
185
186
187void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data)
188{
189  free(*pl_data);
190  *pl_data = NULL;
191}
192
193
194CURLcode 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
202static 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
267static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
268                                    struct fileinfo *infop)
269{
270  curl_fnmatch_callback compare;
271  struct WildcardData *wc = &conn->data->wildcard;
272  struct ftp_wc_tmpdata *tmpdata = wc->tmp;
273  struct curl_llist *llist = &wc->filelist;
274  struct ftp_parselist_data *parser = tmpdata->parser;
275  bool add = TRUE;
276  struct curl_fileinfo *finfo = &infop->info;
277
278  /* move finfo pointers to b_data */
279  char *str = finfo->b_data;
280  finfo->filename       = str + parser->offsets.filename;
281  finfo->strings.group  = parser->offsets.group ?
282                          str + parser->offsets.group : NULL;
283  finfo->strings.perm   = parser->offsets.perm ?
284                          str + parser->offsets.perm : NULL;
285  finfo->strings.target = parser->offsets.symlink_target ?
286                          str + parser->offsets.symlink_target : NULL;
287  finfo->strings.time   = str + parser->offsets.time;
288  finfo->strings.user   = parser->offsets.user ?
289                          str + parser->offsets.user : NULL;
290
291  /* get correct fnmatch callback */
292  compare = conn->data->set.fnmatch;
293  if(!compare)
294    compare = Curl_fnmatch;
295
296  /* filter pattern-corresponding filenames */
297  if(compare(conn->data->set.fnmatch_data, wc->pattern,
298             finfo->filename) == 0) {
299    /* discard symlink which is containing multiple " -> " */
300    if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
301       (strstr(finfo->strings.target, " -> "))) {
302      add = FALSE;
303    }
304  }
305  else {
306    add = FALSE;
307  }
308
309  if(add) {
310    Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list);
311  }
312  else {
313    Curl_fileinfo_dtor(NULL, finfo);
314  }
315
316  tmpdata->parser->file_data = NULL;
317  return CURLE_OK;
318}
319
320size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
321                          void *connptr)
322{
323  size_t bufflen = size*nmemb;
324  struct connectdata *conn = (struct connectdata *)connptr;
325  struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
326  struct ftp_parselist_data *parser = tmpdata->parser;
327  struct fileinfo *infop;
328  struct curl_fileinfo *finfo;
329  unsigned long i = 0;
330  CURLcode result;
331  size_t retsize = bufflen;
332
333  if(parser->error) { /* error in previous call */
334    /* scenario:
335     * 1. call => OK..
336     * 2. call => OUT_OF_MEMORY (or other error)
337     * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
338     *    in wc_statemach()
339     */
340    goto fail;
341  }
342
343  if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
344    /* considering info about FILE response format */
345    parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
346                       OS_TYPE_WIN_NT : OS_TYPE_UNIX;
347  }
348
349  while(i < bufflen) { /* FSM */
350
351    char c = buffer[i];
352    if(!parser->file_data) { /* tmp file data is not allocated yet */
353      parser->file_data = Curl_fileinfo_alloc();
354      if(!parser->file_data) {
355        parser->error = CURLE_OUT_OF_MEMORY;
356        goto fail;
357      }
358      parser->file_data->info.b_data = malloc(FTP_BUFFER_ALLOCSIZE);
359      if(!parser->file_data->info.b_data) {
360        parser->error = CURLE_OUT_OF_MEMORY;
361        goto fail;
362      }
363      parser->file_data->info.b_size = FTP_BUFFER_ALLOCSIZE;
364      parser->item_offset = 0;
365      parser->item_length = 0;
366    }
367
368    infop = parser->file_data;
369    finfo = &infop->info;
370    finfo->b_data[finfo->b_used++] = c;
371
372    if(finfo->b_used >= finfo->b_size - 1) {
373      /* if it is important, extend buffer space for file data */
374      char *tmp = realloc(finfo->b_data,
375                          finfo->b_size + FTP_BUFFER_ALLOCSIZE);
376      if(tmp) {
377        finfo->b_size += FTP_BUFFER_ALLOCSIZE;
378        finfo->b_data = tmp;
379      }
380      else {
381        Curl_fileinfo_dtor(NULL, parser->file_data);
382        parser->file_data = NULL;
383        parser->error = CURLE_OUT_OF_MEMORY;
384        goto fail;
385      }
386    }
387
388    switch(parser->os_type) {
389    case OS_TYPE_UNIX:
390      switch(parser->state.UNIX.main) {
391      case PL_UNIX_TOTALSIZE:
392        switch(parser->state.UNIX.sub.total_dirsize) {
393        case PL_UNIX_TOTALSIZE_INIT:
394          if(c == 't') {
395            parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
396            parser->item_length++;
397          }
398          else {
399            parser->state.UNIX.main = PL_UNIX_FILETYPE;
400            /* start FSM again not considering size of directory */
401            finfo->b_used = 0;
402            i--;
403          }
404          break;
405        case PL_UNIX_TOTALSIZE_READING:
406          parser->item_length++;
407          if(c == '\r') {
408            parser->item_length--;
409            finfo->b_used--;
410          }
411          else if(c == '\n') {
412            finfo->b_data[parser->item_length - 1] = 0;
413            if(strncmp("total ", finfo->b_data, 6) == 0) {
414              char *endptr = finfo->b_data + 6;
415              /* here we can deal with directory size, pass the leading white
416                 spaces and then the digits */
417              while(ISSPACE(*endptr))
418                endptr++;
419              while(ISDIGIT(*endptr))
420                endptr++;
421              if(*endptr != 0) {
422                parser->error = CURLE_FTP_BAD_FILE_LIST;
423                goto fail;
424              }
425              parser->state.UNIX.main = PL_UNIX_FILETYPE;
426              finfo->b_used = 0;
427            }
428            else {
429              parser->error = CURLE_FTP_BAD_FILE_LIST;
430              goto fail;
431            }
432          }
433          break;
434        }
435        break;
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          parser->error = CURLE_FTP_BAD_FILE_LIST;
464          goto fail;
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            parser->error = CURLE_FTP_BAD_FILE_LIST;
475            goto fail;
476          }
477        }
478        else if(parser->item_length == 10) {
479          unsigned int perm;
480          if(c != ' ') {
481            parser->error = CURLE_FTP_BAD_FILE_LIST;
482            goto fail;
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            parser->error = CURLE_FTP_BAD_FILE_LIST;
488            goto fail;
489          }
490          parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM;
491          parser->file_data->info.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              parser->error = CURLE_FTP_BAD_FILE_LIST;
510              goto fail;
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->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
523              parser->file_data->info.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            parser->error = CURLE_FTP_BAD_FILE_LIST;
532            goto fail;
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              parser->error = CURLE_FTP_BAD_FILE_LIST;
592              goto fail;
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            if(!curlx_strtoofft(finfo->b_data + parser->item_offset,
603                                &p, 10, &fsize)) {
604              if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
605                 fsize != CURL_OFF_T_MIN) {
606                parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
607                parser->file_data->info.size = fsize;
608              }
609              parser->item_length = 0;
610              parser->item_offset = 0;
611              parser->state.UNIX.main = PL_UNIX_TIME;
612              parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
613            }
614          }
615          else if(!ISDIGIT(c)) {
616            parser->error = CURLE_FTP_BAD_FILE_LIST;
617            goto fail;
618          }
619          break;
620        }
621        break;
622      case PL_UNIX_TIME:
623        switch(parser->state.UNIX.sub.time) {
624        case PL_UNIX_TIME_PREPART1:
625          if(c != ' ') {
626            if(ISALNUM(c)) {
627              parser->item_offset = finfo->b_used -1;
628              parser->item_length = 1;
629              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
630            }
631            else {
632              parser->error = CURLE_FTP_BAD_FILE_LIST;
633              goto fail;
634            }
635          }
636          break;
637        case PL_UNIX_TIME_PART1:
638          parser->item_length++;
639          if(c == ' ') {
640            parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
641          }
642          else if(!ISALNUM(c) && c != '.') {
643            parser->error = CURLE_FTP_BAD_FILE_LIST;
644            goto fail;
645          }
646          break;
647        case PL_UNIX_TIME_PREPART2:
648          parser->item_length++;
649          if(c != ' ') {
650            if(ISALNUM(c)) {
651              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
652            }
653            else {
654              parser->error = CURLE_FTP_BAD_FILE_LIST;
655              goto fail;
656            }
657          }
658          break;
659        case PL_UNIX_TIME_PART2:
660          parser->item_length++;
661          if(c == ' ') {
662            parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
663          }
664          else if(!ISALNUM(c) && c != '.') {
665            parser->error = CURLE_FTP_BAD_FILE_LIST;
666            goto fail;
667          }
668          break;
669        case PL_UNIX_TIME_PREPART3:
670          parser->item_length++;
671          if(c != ' ') {
672            if(ISALNUM(c)) {
673              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
674            }
675            else {
676              parser->error = CURLE_FTP_BAD_FILE_LIST;
677              goto fail;
678            }
679          }
680          break;
681        case PL_UNIX_TIME_PART3:
682          parser->item_length++;
683          if(c == ' ') {
684            finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
685            parser->offsets.time = parser->item_offset;
686            /*
687              if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
688                parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
689              }
690            */
691            if(finfo->filetype == CURLFILETYPE_SYMLINK) {
692              parser->state.UNIX.main = PL_UNIX_SYMLINK;
693              parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
694            }
695            else {
696              parser->state.UNIX.main = PL_UNIX_FILENAME;
697              parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
698            }
699          }
700          else if(!ISALNUM(c) && c != '.' && c != ':') {
701            parser->error = CURLE_FTP_BAD_FILE_LIST;
702            goto fail;
703          }
704          break;
705        }
706        break;
707      case PL_UNIX_FILENAME:
708        switch(parser->state.UNIX.sub.filename) {
709        case PL_UNIX_FILENAME_PRESPACE:
710          if(c != ' ') {
711            parser->item_offset = finfo->b_used - 1;
712            parser->item_length = 1;
713            parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
714          }
715          break;
716        case PL_UNIX_FILENAME_NAME:
717          parser->item_length++;
718          if(c == '\r') {
719            parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
720          }
721          else if(c == '\n') {
722            finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
723            parser->offsets.filename = parser->item_offset;
724            parser->state.UNIX.main = PL_UNIX_FILETYPE;
725            result = ftp_pl_insert_finfo(conn, infop);
726            if(result) {
727              parser->error = result;
728              goto fail;
729            }
730          }
731          break;
732        case PL_UNIX_FILENAME_WINDOWSEOL:
733          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, infop);
738            if(result) {
739              parser->error = result;
740              goto fail;
741            }
742          }
743          else {
744            parser->error = CURLE_FTP_BAD_FILE_LIST;
745            goto fail;
746          }
747          break;
748        }
749        break;
750      case PL_UNIX_SYMLINK:
751        switch(parser->state.UNIX.sub.symlink) {
752        case PL_UNIX_SYMLINK_PRESPACE:
753          if(c != ' ') {
754            parser->item_offset = finfo->b_used - 1;
755            parser->item_length = 1;
756            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
757          }
758          break;
759        case PL_UNIX_SYMLINK_NAME:
760          parser->item_length++;
761          if(c == ' ') {
762            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
763          }
764          else if(c == '\r' || c == '\n') {
765            parser->error = CURLE_FTP_BAD_FILE_LIST;
766            goto fail;
767          }
768          break;
769        case PL_UNIX_SYMLINK_PRETARGET1:
770          parser->item_length++;
771          if(c == '-') {
772            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
773          }
774          else if(c == '\r' || c == '\n') {
775            parser->error = CURLE_FTP_BAD_FILE_LIST;
776            goto fail;
777          }
778          else {
779            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
780          }
781          break;
782        case PL_UNIX_SYMLINK_PRETARGET2:
783          parser->item_length++;
784          if(c == '>') {
785            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
786          }
787          else if(c == '\r' || c == '\n') {
788            parser->error = CURLE_FTP_BAD_FILE_LIST;
789            goto fail;
790          }
791          else {
792            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
793          }
794          break;
795        case PL_UNIX_SYMLINK_PRETARGET3:
796          parser->item_length++;
797          if(c == ' ') {
798            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
799            /* now place where is symlink following */
800            finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
801            parser->offsets.filename = parser->item_offset;
802            parser->item_length = 0;
803            parser->item_offset = 0;
804          }
805          else if(c == '\r' || c == '\n') {
806            parser->error = CURLE_FTP_BAD_FILE_LIST;
807            goto fail;
808          }
809          else {
810            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
811          }
812          break;
813        case PL_UNIX_SYMLINK_PRETARGET4:
814          if(c != '\r' && c != '\n') {
815            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
816            parser->item_offset = finfo->b_used - 1;
817            parser->item_length = 1;
818          }
819          else {
820            parser->error = CURLE_FTP_BAD_FILE_LIST;
821            goto fail;
822          }
823          break;
824        case PL_UNIX_SYMLINK_TARGET:
825          parser->item_length++;
826          if(c == '\r') {
827            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
828          }
829          else if(c == '\n') {
830            finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
831            parser->offsets.symlink_target = parser->item_offset;
832            result = ftp_pl_insert_finfo(conn, infop);
833            if(result) {
834              parser->error = result;
835              goto fail;
836            }
837            parser->state.UNIX.main = PL_UNIX_FILETYPE;
838          }
839          break;
840        case PL_UNIX_SYMLINK_WINDOWSEOL:
841          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, infop);
845            if(result) {
846              parser->error = result;
847              goto fail;
848            }
849            parser->state.UNIX.main = PL_UNIX_FILETYPE;
850          }
851          else {
852            parser->error = CURLE_FTP_BAD_FILE_LIST;
853            goto fail;
854          }
855          break;
856        }
857        break;
858      }
859      break;
860    case OS_TYPE_WIN_NT:
861      switch(parser->state.NT.main) {
862      case PL_WINNT_DATE:
863        parser->item_length++;
864        if(parser->item_length < 9) {
865          if(!strchr("0123456789-", c)) { /* only simple control */
866            parser->error = CURLE_FTP_BAD_FILE_LIST;
867            goto fail;
868          }
869        }
870        else if(parser->item_length == 9) {
871          if(c == ' ') {
872            parser->state.NT.main = PL_WINNT_TIME;
873            parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
874          }
875          else {
876            parser->error = CURLE_FTP_BAD_FILE_LIST;
877            goto fail;
878          }
879        }
880        else {
881          parser->error = CURLE_FTP_BAD_FILE_LIST;
882          goto fail;
883        }
884        break;
885      case PL_WINNT_TIME:
886        parser->item_length++;
887        switch(parser->state.NT.sub.time) {
888        case PL_WINNT_TIME_PRESPACE:
889          if(!ISSPACE(c)) {
890            parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
891          }
892          break;
893        case PL_WINNT_TIME_TIME:
894          if(c == ' ') {
895            parser->offsets.time = parser->item_offset;
896            finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
897            parser->state.NT.main = PL_WINNT_DIRORSIZE;
898            parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
899            parser->item_length = 0;
900          }
901          else if(!strchr("APM0123456789:", c)) {
902            parser->error = CURLE_FTP_BAD_FILE_LIST;
903            goto fail;
904          }
905          break;
906        }
907        break;
908      case PL_WINNT_DIRORSIZE:
909        switch(parser->state.NT.sub.dirorsize) {
910        case PL_WINNT_DIRORSIZE_PRESPACE:
911          if(c == ' ') {
912
913          }
914          else {
915            parser->item_offset = finfo->b_used - 1;
916            parser->item_length = 1;
917            parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
918          }
919          break;
920        case PL_WINNT_DIRORSIZE_CONTENT:
921          parser->item_length ++;
922          if(c == ' ') {
923            finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
924            if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
925              finfo->filetype = CURLFILETYPE_DIRECTORY;
926              finfo->size = 0;
927            }
928            else {
929              char *endptr;
930              if(curlx_strtoofft(finfo->b_data +
931                                 parser->item_offset,
932                                 &endptr, 10, &finfo->size)) {
933                parser->error = CURLE_FTP_BAD_FILE_LIST;
934                goto fail;
935              }
936              /* correct file type */
937              parser->file_data->info.filetype = CURLFILETYPE_FILE;
938            }
939
940            parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
941            parser->item_length = 0;
942            parser->state.NT.main = PL_WINNT_FILENAME;
943            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
944          }
945          break;
946        }
947        break;
948      case PL_WINNT_FILENAME:
949        switch(parser->state.NT.sub.filename) {
950        case PL_WINNT_FILENAME_PRESPACE:
951          if(c != ' ') {
952            parser->item_offset = finfo->b_used -1;
953            parser->item_length = 1;
954            parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
955          }
956          break;
957        case PL_WINNT_FILENAME_CONTENT:
958          parser->item_length++;
959          if(c == '\r') {
960            parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
961            finfo->b_data[finfo->b_used - 1] = 0;
962          }
963          else if(c == '\n') {
964            parser->offsets.filename = parser->item_offset;
965            finfo->b_data[finfo->b_used - 1] = 0;
966            parser->offsets.filename = parser->item_offset;
967            result = ftp_pl_insert_finfo(conn, infop);
968            if(result) {
969              parser->error = result;
970              goto fail;
971            }
972            parser->state.NT.main = PL_WINNT_DATE;
973            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
974          }
975          break;
976        case PL_WINNT_FILENAME_WINEOL:
977          if(c == '\n') {
978            parser->offsets.filename = parser->item_offset;
979            result = ftp_pl_insert_finfo(conn, infop);
980            if(result) {
981              parser->error = result;
982              goto fail;
983            }
984            parser->state.NT.main = PL_WINNT_DATE;
985            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
986          }
987          else {
988            parser->error = CURLE_FTP_BAD_FILE_LIST;
989            goto fail;
990          }
991          break;
992        }
993        break;
994      }
995      break;
996    default:
997      retsize = bufflen + 1;
998      goto fail;
999    }
1000
1001    i++;
1002  }
1003
1004fail:
1005
1006  /* Clean up any allocated memory. */
1007  if(parser->file_data) {
1008    Curl_fileinfo_dtor(NULL, parser->file_data);
1009    parser->file_data = NULL;
1010  }
1011
1012  return retsize;
1013}
1014
1015#endif /* CURL_DISABLE_FTP */
1016