xref: /third_party/curl/lib/ftplistparser.c (revision 13498266)
1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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.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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25/**
26 * Now implemented:
27 *
28 * 1) Unix version 1
29 * drwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog
30 * 2) Unix version 2
31 * drwxr-xr-x 1 user01 ftp  512 Jan 29 1997  prog
32 * 3) Unix version 3
33 * drwxr-xr-x 1      1   1  512 Jan 29 23:32 prog
34 * 4) Unix symlink
35 * lrwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog -> prog2000
36 * 5) DOS style
37 * 01-29-97 11:32PM <DIR> prog
38 */
39
40#include "curl_setup.h"
41
42#ifndef CURL_DISABLE_FTP
43
44#include <curl/curl.h>
45
46#include "urldata.h"
47#include "fileinfo.h"
48#include "llist.h"
49#include "strtoofft.h"
50#include "ftp.h"
51#include "ftplistparser.h"
52#include "curl_fnmatch.h"
53#include "curl_memory.h"
54#include "multiif.h"
55/* The last #include file should be: */
56#include "memdebug.h"
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
181static void fileinfo_dtor(void *user, void *element)
182{
183  (void)user;
184  Curl_fileinfo_cleanup(element);
185}
186
187CURLcode Curl_wildcard_init(struct WildcardData *wc)
188{
189  Curl_llist_init(&wc->filelist, fileinfo_dtor);
190  wc->state = CURLWC_INIT;
191
192  return CURLE_OK;
193}
194
195void Curl_wildcard_dtor(struct WildcardData **wcp)
196{
197  struct WildcardData *wc = *wcp;
198  if(!wc)
199    return;
200
201  if(wc->dtor) {
202    wc->dtor(wc->ftpwc);
203    wc->dtor = ZERO_NULL;
204    wc->ftpwc = NULL;
205  }
206  DEBUGASSERT(wc->ftpwc == NULL);
207
208  Curl_llist_destroy(&wc->filelist, NULL);
209  free(wc->path);
210  wc->path = NULL;
211  free(wc->pattern);
212  wc->pattern = NULL;
213  wc->state = CURLWC_INIT;
214  free(wc);
215  *wcp = NULL;
216}
217
218struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
219{
220  return calloc(1, sizeof(struct ftp_parselist_data));
221}
222
223
224void Curl_ftp_parselist_data_free(struct ftp_parselist_data **parserp)
225{
226  struct ftp_parselist_data *parser = *parserp;
227  if(parser)
228    Curl_fileinfo_cleanup(parser->file_data);
229  free(parser);
230  *parserp = NULL;
231}
232
233
234CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data)
235{
236  return pl_data->error;
237}
238
239
240#define FTP_LP_MALFORMATED_PERM 0x01000000
241
242static unsigned int ftp_pl_get_permission(const char *str)
243{
244  unsigned int permissions = 0;
245  /* USER */
246  if(str[0] == 'r')
247    permissions |= 1 << 8;
248  else if(str[0] != '-')
249    permissions |= FTP_LP_MALFORMATED_PERM;
250  if(str[1] == 'w')
251    permissions |= 1 << 7;
252  else if(str[1] != '-')
253    permissions |= FTP_LP_MALFORMATED_PERM;
254
255  if(str[2] == 'x')
256    permissions |= 1 << 6;
257  else if(str[2] == 's') {
258    permissions |= 1 << 6;
259    permissions |= 1 << 11;
260  }
261  else if(str[2] == 'S')
262    permissions |= 1 << 11;
263  else if(str[2] != '-')
264    permissions |= FTP_LP_MALFORMATED_PERM;
265  /* GROUP */
266  if(str[3] == 'r')
267    permissions |= 1 << 5;
268  else if(str[3] != '-')
269    permissions |= FTP_LP_MALFORMATED_PERM;
270  if(str[4] == 'w')
271    permissions |= 1 << 4;
272  else if(str[4] != '-')
273    permissions |= FTP_LP_MALFORMATED_PERM;
274  if(str[5] == 'x')
275    permissions |= 1 << 3;
276  else if(str[5] == 's') {
277    permissions |= 1 << 3;
278    permissions |= 1 << 10;
279  }
280  else if(str[5] == 'S')
281    permissions |= 1 << 10;
282  else if(str[5] != '-')
283    permissions |= FTP_LP_MALFORMATED_PERM;
284  /* others */
285  if(str[6] == 'r')
286    permissions |= 1 << 2;
287  else if(str[6] != '-')
288    permissions |= FTP_LP_MALFORMATED_PERM;
289  if(str[7] == 'w')
290    permissions |= 1 << 1;
291  else if(str[7] != '-')
292      permissions |= FTP_LP_MALFORMATED_PERM;
293  if(str[8] == 'x')
294    permissions |= 1;
295  else if(str[8] == 't') {
296    permissions |= 1;
297    permissions |= 1 << 9;
298  }
299  else if(str[8] == 'T')
300    permissions |= 1 << 9;
301  else if(str[8] != '-')
302    permissions |= FTP_LP_MALFORMATED_PERM;
303
304  return permissions;
305}
306
307static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data,
308                                    struct fileinfo *infop)
309{
310  curl_fnmatch_callback compare;
311  struct WildcardData *wc = data->wildcard;
312  struct ftp_wc *ftpwc = wc->ftpwc;
313  struct Curl_llist *llist = &wc->filelist;
314  struct ftp_parselist_data *parser = ftpwc->parser;
315  bool add = TRUE;
316  struct curl_fileinfo *finfo = &infop->info;
317
318  /* set the finfo pointers */
319  char *str = Curl_dyn_ptr(&infop->buf);
320  finfo->filename       = str + parser->offsets.filename;
321  finfo->strings.group  = parser->offsets.group ?
322                          str + parser->offsets.group : NULL;
323  finfo->strings.perm   = parser->offsets.perm ?
324                          str + parser->offsets.perm : NULL;
325  finfo->strings.target = parser->offsets.symlink_target ?
326                          str + parser->offsets.symlink_target : NULL;
327  finfo->strings.time   = str + parser->offsets.time;
328  finfo->strings.user   = parser->offsets.user ?
329                          str + parser->offsets.user : NULL;
330
331  /* get correct fnmatch callback */
332  compare = data->set.fnmatch;
333  if(!compare)
334    compare = Curl_fnmatch;
335
336  /* filter pattern-corresponding filenames */
337  Curl_set_in_callback(data, true);
338  if(compare(data->set.fnmatch_data, wc->pattern,
339             finfo->filename) == 0) {
340    /* discard symlink which is containing multiple " -> " */
341    if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
342       (strstr(finfo->strings.target, " -> "))) {
343      add = FALSE;
344    }
345  }
346  else {
347    add = FALSE;
348  }
349  Curl_set_in_callback(data, false);
350
351  if(add) {
352    Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list);
353  }
354  else {
355    Curl_fileinfo_cleanup(infop);
356  }
357
358  ftpwc->parser->file_data = NULL;
359  return CURLE_OK;
360}
361
362#define MAX_FTPLIST_BUFFER 10000 /* arbitrarily set */
363
364size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
365                          void *connptr)
366{
367  size_t bufflen = size*nmemb;
368  struct Curl_easy *data = (struct Curl_easy *)connptr;
369  struct ftp_wc *ftpwc = data->wildcard->ftpwc;
370  struct ftp_parselist_data *parser = ftpwc->parser;
371  size_t i = 0;
372  CURLcode result;
373  size_t retsize = bufflen;
374
375  if(parser->error) { /* error in previous call */
376    /* scenario:
377     * 1. call => OK..
378     * 2. call => OUT_OF_MEMORY (or other error)
379     * 3. (last) call => is skipped RIGHT HERE and the error is handled later
380     *    in wc_statemach()
381     */
382    goto fail;
383  }
384
385  if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
386    /* considering info about FILE response format */
387    parser->os_type = ISDIGIT(buffer[0]) ? OS_TYPE_WIN_NT : OS_TYPE_UNIX;
388  }
389
390  while(i < bufflen) { /* FSM */
391    char *mem;
392    size_t len; /* number of bytes of data in the dynbuf */
393    char c = buffer[i];
394    struct fileinfo *infop;
395    struct curl_fileinfo *finfo;
396    if(!parser->file_data) { /* tmp file data is not allocated yet */
397      parser->file_data = Curl_fileinfo_alloc();
398      if(!parser->file_data) {
399        parser->error = CURLE_OUT_OF_MEMORY;
400        goto fail;
401      }
402      parser->item_offset = 0;
403      parser->item_length = 0;
404      Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER);
405    }
406
407    infop = parser->file_data;
408    finfo = &infop->info;
409
410    if(Curl_dyn_addn(&infop->buf, &c, 1)) {
411      parser->error = CURLE_OUT_OF_MEMORY;
412      goto fail;
413    }
414    len = Curl_dyn_len(&infop->buf);
415    mem = Curl_dyn_ptr(&infop->buf);
416
417    switch(parser->os_type) {
418    case OS_TYPE_UNIX:
419      switch(parser->state.UNIX.main) {
420      case PL_UNIX_TOTALSIZE:
421        switch(parser->state.UNIX.sub.total_dirsize) {
422        case PL_UNIX_TOTALSIZE_INIT:
423          if(c == 't') {
424            parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING;
425            parser->item_length++;
426          }
427          else {
428            parser->state.UNIX.main = PL_UNIX_FILETYPE;
429            /* start FSM again not considering size of directory */
430            Curl_dyn_reset(&infop->buf);
431            continue;
432          }
433          break;
434        case PL_UNIX_TOTALSIZE_READING:
435          parser->item_length++;
436          if(c == '\r') {
437            parser->item_length--;
438            Curl_dyn_setlen(&infop->buf, --len);
439          }
440          else if(c == '\n') {
441            mem[parser->item_length - 1] = 0;
442            if(!strncmp("total ", mem, 6)) {
443              char *endptr = mem + 6;
444              /* here we can deal with directory size, pass the leading
445                 whitespace and then the digits */
446              while(ISBLANK(*endptr))
447                endptr++;
448              while(ISDIGIT(*endptr))
449                endptr++;
450              if(*endptr) {
451                parser->error = CURLE_FTP_BAD_FILE_LIST;
452                goto fail;
453              }
454              parser->state.UNIX.main = PL_UNIX_FILETYPE;
455              Curl_dyn_reset(&infop->buf);
456            }
457            else {
458              parser->error = CURLE_FTP_BAD_FILE_LIST;
459              goto fail;
460            }
461          }
462          break;
463        }
464        break;
465      case PL_UNIX_FILETYPE:
466        switch(c) {
467        case '-':
468          finfo->filetype = CURLFILETYPE_FILE;
469          break;
470        case 'd':
471          finfo->filetype = CURLFILETYPE_DIRECTORY;
472          break;
473        case 'l':
474          finfo->filetype = CURLFILETYPE_SYMLINK;
475          break;
476        case 'p':
477          finfo->filetype = CURLFILETYPE_NAMEDPIPE;
478          break;
479        case 's':
480          finfo->filetype = CURLFILETYPE_SOCKET;
481          break;
482        case 'c':
483          finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
484          break;
485        case 'b':
486          finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
487          break;
488        case 'D':
489          finfo->filetype = CURLFILETYPE_DOOR;
490          break;
491        default:
492          parser->error = CURLE_FTP_BAD_FILE_LIST;
493          goto fail;
494        }
495        parser->state.UNIX.main = PL_UNIX_PERMISSION;
496        parser->item_length = 0;
497        parser->item_offset = 1;
498        break;
499      case PL_UNIX_PERMISSION:
500        parser->item_length++;
501        if(parser->item_length <= 9) {
502          if(!strchr("rwx-tTsS", c)) {
503            parser->error = CURLE_FTP_BAD_FILE_LIST;
504            goto fail;
505          }
506        }
507        else if(parser->item_length == 10) {
508          unsigned int perm;
509          if(c != ' ') {
510            parser->error = CURLE_FTP_BAD_FILE_LIST;
511            goto fail;
512          }
513          mem[10] = 0; /* terminate permissions */
514          perm = ftp_pl_get_permission(mem + parser->item_offset);
515          if(perm & FTP_LP_MALFORMATED_PERM) {
516            parser->error = CURLE_FTP_BAD_FILE_LIST;
517            goto fail;
518          }
519          parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM;
520          parser->file_data->info.perm = perm;
521          parser->offsets.perm = parser->item_offset;
522
523          parser->item_length = 0;
524          parser->state.UNIX.main = PL_UNIX_HLINKS;
525          parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
526        }
527        break;
528      case PL_UNIX_HLINKS:
529        switch(parser->state.UNIX.sub.hlinks) {
530        case PL_UNIX_HLINKS_PRESPACE:
531          if(c != ' ') {
532            if(ISDIGIT(c)) {
533              parser->item_offset = len - 1;
534              parser->item_length = 1;
535              parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
536            }
537            else {
538              parser->error = CURLE_FTP_BAD_FILE_LIST;
539              goto fail;
540            }
541          }
542          break;
543        case PL_UNIX_HLINKS_NUMBER:
544          parser->item_length ++;
545          if(c == ' ') {
546            char *p;
547            long int hlinks;
548            mem[parser->item_offset + parser->item_length - 1] = 0;
549            hlinks = strtol(mem + parser->item_offset, &p, 10);
550            if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
551              parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
552              parser->file_data->info.hardlinks = hlinks;
553            }
554            parser->item_length = 0;
555            parser->item_offset = 0;
556            parser->state.UNIX.main = PL_UNIX_USER;
557            parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
558          }
559          else if(!ISDIGIT(c)) {
560            parser->error = CURLE_FTP_BAD_FILE_LIST;
561            goto fail;
562          }
563          break;
564        }
565        break;
566      case PL_UNIX_USER:
567        switch(parser->state.UNIX.sub.user) {
568        case PL_UNIX_USER_PRESPACE:
569          if(c != ' ') {
570            parser->item_offset = len - 1;
571            parser->item_length = 1;
572            parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
573          }
574          break;
575        case PL_UNIX_USER_PARSING:
576          parser->item_length++;
577          if(c == ' ') {
578            mem[parser->item_offset + parser->item_length - 1] = 0;
579            parser->offsets.user = parser->item_offset;
580            parser->state.UNIX.main = PL_UNIX_GROUP;
581            parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
582            parser->item_offset = 0;
583            parser->item_length = 0;
584          }
585          break;
586        }
587        break;
588      case PL_UNIX_GROUP:
589        switch(parser->state.UNIX.sub.group) {
590        case PL_UNIX_GROUP_PRESPACE:
591          if(c != ' ') {
592            parser->item_offset = len - 1;
593            parser->item_length = 1;
594            parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
595          }
596          break;
597        case PL_UNIX_GROUP_NAME:
598          parser->item_length++;
599          if(c == ' ') {
600            mem[parser->item_offset + parser->item_length - 1] = 0;
601            parser->offsets.group = parser->item_offset;
602            parser->state.UNIX.main = PL_UNIX_SIZE;
603            parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
604            parser->item_offset = 0;
605            parser->item_length = 0;
606          }
607          break;
608        }
609        break;
610      case PL_UNIX_SIZE:
611        switch(parser->state.UNIX.sub.size) {
612        case PL_UNIX_SIZE_PRESPACE:
613          if(c != ' ') {
614            if(ISDIGIT(c)) {
615              parser->item_offset = len - 1;
616              parser->item_length = 1;
617              parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
618            }
619            else {
620              parser->error = CURLE_FTP_BAD_FILE_LIST;
621              goto fail;
622            }
623          }
624          break;
625        case PL_UNIX_SIZE_NUMBER:
626          parser->item_length++;
627          if(c == ' ') {
628            char *p;
629            curl_off_t fsize;
630            mem[parser->item_offset + parser->item_length - 1] = 0;
631            if(!curlx_strtoofft(mem + parser->item_offset,
632                                &p, 10, &fsize)) {
633              if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
634                 fsize != CURL_OFF_T_MIN) {
635                parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
636                parser->file_data->info.size = fsize;
637              }
638              parser->item_length = 0;
639              parser->item_offset = 0;
640              parser->state.UNIX.main = PL_UNIX_TIME;
641              parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1;
642            }
643          }
644          else if(!ISDIGIT(c)) {
645            parser->error = CURLE_FTP_BAD_FILE_LIST;
646            goto fail;
647          }
648          break;
649        }
650        break;
651      case PL_UNIX_TIME:
652        switch(parser->state.UNIX.sub.time) {
653        case PL_UNIX_TIME_PREPART1:
654          if(c != ' ') {
655            if(ISALNUM(c)) {
656              parser->item_offset = len -1;
657              parser->item_length = 1;
658              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
659            }
660            else {
661              parser->error = CURLE_FTP_BAD_FILE_LIST;
662              goto fail;
663            }
664          }
665          break;
666        case PL_UNIX_TIME_PART1:
667          parser->item_length++;
668          if(c == ' ') {
669            parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2;
670          }
671          else if(!ISALNUM(c) && c != '.') {
672            parser->error = CURLE_FTP_BAD_FILE_LIST;
673            goto fail;
674          }
675          break;
676        case PL_UNIX_TIME_PREPART2:
677          parser->item_length++;
678          if(c != ' ') {
679            if(ISALNUM(c)) {
680              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2;
681            }
682            else {
683              parser->error = CURLE_FTP_BAD_FILE_LIST;
684              goto fail;
685            }
686          }
687          break;
688        case PL_UNIX_TIME_PART2:
689          parser->item_length++;
690          if(c == ' ') {
691            parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3;
692          }
693          else if(!ISALNUM(c) && c != '.') {
694            parser->error = CURLE_FTP_BAD_FILE_LIST;
695            goto fail;
696          }
697          break;
698        case PL_UNIX_TIME_PREPART3:
699          parser->item_length++;
700          if(c != ' ') {
701            if(ISALNUM(c)) {
702              parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3;
703            }
704            else {
705              parser->error = CURLE_FTP_BAD_FILE_LIST;
706              goto fail;
707            }
708          }
709          break;
710        case PL_UNIX_TIME_PART3:
711          parser->item_length++;
712          if(c == ' ') {
713            mem[parser->item_offset + parser->item_length -1] = 0;
714            parser->offsets.time = parser->item_offset;
715            /*
716              if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) {
717                parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
718              }
719            */
720            if(finfo->filetype == CURLFILETYPE_SYMLINK) {
721              parser->state.UNIX.main = PL_UNIX_SYMLINK;
722              parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
723            }
724            else {
725              parser->state.UNIX.main = PL_UNIX_FILENAME;
726              parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
727            }
728          }
729          else if(!ISALNUM(c) && c != '.' && c != ':') {
730            parser->error = CURLE_FTP_BAD_FILE_LIST;
731            goto fail;
732          }
733          break;
734        }
735        break;
736      case PL_UNIX_FILENAME:
737        switch(parser->state.UNIX.sub.filename) {
738        case PL_UNIX_FILENAME_PRESPACE:
739          if(c != ' ') {
740            parser->item_offset = len - 1;
741            parser->item_length = 1;
742            parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
743          }
744          break;
745        case PL_UNIX_FILENAME_NAME:
746          parser->item_length++;
747          if(c == '\r') {
748            parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
749          }
750          else if(c == '\n') {
751            mem[parser->item_offset + parser->item_length - 1] = 0;
752            parser->offsets.filename = parser->item_offset;
753            parser->state.UNIX.main = PL_UNIX_FILETYPE;
754            result = ftp_pl_insert_finfo(data, infop);
755            if(result) {
756              parser->error = result;
757              goto fail;
758            }
759          }
760          break;
761        case PL_UNIX_FILENAME_WINDOWSEOL:
762          if(c == '\n') {
763            mem[parser->item_offset + parser->item_length - 1] = 0;
764            parser->offsets.filename = parser->item_offset;
765            parser->state.UNIX.main = PL_UNIX_FILETYPE;
766            result = ftp_pl_insert_finfo(data, infop);
767            if(result) {
768              parser->error = result;
769              goto fail;
770            }
771          }
772          else {
773            parser->error = CURLE_FTP_BAD_FILE_LIST;
774            goto fail;
775          }
776          break;
777        }
778        break;
779      case PL_UNIX_SYMLINK:
780        switch(parser->state.UNIX.sub.symlink) {
781        case PL_UNIX_SYMLINK_PRESPACE:
782          if(c != ' ') {
783            parser->item_offset = len - 1;
784            parser->item_length = 1;
785            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
786          }
787          break;
788        case PL_UNIX_SYMLINK_NAME:
789          parser->item_length++;
790          if(c == ' ') {
791            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
792          }
793          else if(c == '\r' || c == '\n') {
794            parser->error = CURLE_FTP_BAD_FILE_LIST;
795            goto fail;
796          }
797          break;
798        case PL_UNIX_SYMLINK_PRETARGET1:
799          parser->item_length++;
800          if(c == '-') {
801            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
802          }
803          else if(c == '\r' || c == '\n') {
804            parser->error = CURLE_FTP_BAD_FILE_LIST;
805            goto fail;
806          }
807          else {
808            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
809          }
810          break;
811        case PL_UNIX_SYMLINK_PRETARGET2:
812          parser->item_length++;
813          if(c == '>') {
814            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
815          }
816          else if(c == '\r' || c == '\n') {
817            parser->error = CURLE_FTP_BAD_FILE_LIST;
818            goto fail;
819          }
820          else {
821            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
822          }
823          break;
824        case PL_UNIX_SYMLINK_PRETARGET3:
825          parser->item_length++;
826          if(c == ' ') {
827            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
828            /* now place where is symlink following */
829            mem[parser->item_offset + parser->item_length - 4] = 0;
830            parser->offsets.filename = parser->item_offset;
831            parser->item_length = 0;
832            parser->item_offset = 0;
833          }
834          else if(c == '\r' || c == '\n') {
835            parser->error = CURLE_FTP_BAD_FILE_LIST;
836            goto fail;
837          }
838          else {
839            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
840          }
841          break;
842        case PL_UNIX_SYMLINK_PRETARGET4:
843          if(c != '\r' && c != '\n') {
844            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
845            parser->item_offset = len - 1;
846            parser->item_length = 1;
847          }
848          else {
849            parser->error = CURLE_FTP_BAD_FILE_LIST;
850            goto fail;
851          }
852          break;
853        case PL_UNIX_SYMLINK_TARGET:
854          parser->item_length++;
855          if(c == '\r') {
856            parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
857          }
858          else if(c == '\n') {
859            mem[parser->item_offset + parser->item_length - 1] = 0;
860            parser->offsets.symlink_target = parser->item_offset;
861            result = ftp_pl_insert_finfo(data, infop);
862            if(result) {
863              parser->error = result;
864              goto fail;
865            }
866            parser->state.UNIX.main = PL_UNIX_FILETYPE;
867          }
868          break;
869        case PL_UNIX_SYMLINK_WINDOWSEOL:
870          if(c == '\n') {
871            mem[parser->item_offset + parser->item_length - 1] = 0;
872            parser->offsets.symlink_target = parser->item_offset;
873            result = ftp_pl_insert_finfo(data, infop);
874            if(result) {
875              parser->error = result;
876              goto fail;
877            }
878            parser->state.UNIX.main = PL_UNIX_FILETYPE;
879          }
880          else {
881            parser->error = CURLE_FTP_BAD_FILE_LIST;
882            goto fail;
883          }
884          break;
885        }
886        break;
887      }
888      break;
889    case OS_TYPE_WIN_NT:
890      switch(parser->state.NT.main) {
891      case PL_WINNT_DATE:
892        parser->item_length++;
893        if(parser->item_length < 9) {
894          if(!strchr("0123456789-", c)) { /* only simple control */
895            parser->error = CURLE_FTP_BAD_FILE_LIST;
896            goto fail;
897          }
898        }
899        else if(parser->item_length == 9) {
900          if(c == ' ') {
901            parser->state.NT.main = PL_WINNT_TIME;
902            parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE;
903          }
904          else {
905            parser->error = CURLE_FTP_BAD_FILE_LIST;
906            goto fail;
907          }
908        }
909        else {
910          parser->error = CURLE_FTP_BAD_FILE_LIST;
911          goto fail;
912        }
913        break;
914      case PL_WINNT_TIME:
915        parser->item_length++;
916        switch(parser->state.NT.sub.time) {
917        case PL_WINNT_TIME_PRESPACE:
918          if(!ISBLANK(c)) {
919            parser->state.NT.sub.time = PL_WINNT_TIME_TIME;
920          }
921          break;
922        case PL_WINNT_TIME_TIME:
923          if(c == ' ') {
924            parser->offsets.time = parser->item_offset;
925            mem[parser->item_offset + parser->item_length -1] = 0;
926            parser->state.NT.main = PL_WINNT_DIRORSIZE;
927            parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
928            parser->item_length = 0;
929          }
930          else if(!strchr("APM0123456789:", c)) {
931            parser->error = CURLE_FTP_BAD_FILE_LIST;
932            goto fail;
933          }
934          break;
935        }
936        break;
937      case PL_WINNT_DIRORSIZE:
938        switch(parser->state.NT.sub.dirorsize) {
939        case PL_WINNT_DIRORSIZE_PRESPACE:
940          if(c != ' ') {
941            parser->item_offset = len - 1;
942            parser->item_length = 1;
943            parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
944          }
945          break;
946        case PL_WINNT_DIRORSIZE_CONTENT:
947          parser->item_length ++;
948          if(c == ' ') {
949            mem[parser->item_offset + parser->item_length - 1] = 0;
950            if(strcmp("<DIR>", mem + parser->item_offset) == 0) {
951              finfo->filetype = CURLFILETYPE_DIRECTORY;
952              finfo->size = 0;
953            }
954            else {
955              char *endptr;
956              if(curlx_strtoofft(mem +
957                                 parser->item_offset,
958                                 &endptr, 10, &finfo->size)) {
959                parser->error = CURLE_FTP_BAD_FILE_LIST;
960                goto fail;
961              }
962              /* correct file type */
963              parser->file_data->info.filetype = CURLFILETYPE_FILE;
964            }
965
966            parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE;
967            parser->item_length = 0;
968            parser->state.NT.main = PL_WINNT_FILENAME;
969            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
970          }
971          break;
972        }
973        break;
974      case PL_WINNT_FILENAME:
975        switch(parser->state.NT.sub.filename) {
976        case PL_WINNT_FILENAME_PRESPACE:
977          if(c != ' ') {
978            parser->item_offset = len -1;
979            parser->item_length = 1;
980            parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
981          }
982          break;
983        case PL_WINNT_FILENAME_CONTENT:
984          parser->item_length++;
985          if(c == '\r') {
986            parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
987            mem[len - 1] = 0;
988          }
989          else if(c == '\n') {
990            parser->offsets.filename = parser->item_offset;
991            mem[len - 1] = 0;
992            result = ftp_pl_insert_finfo(data, infop);
993            if(result) {
994              parser->error = result;
995              goto fail;
996            }
997            parser->state.NT.main = PL_WINNT_DATE;
998            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
999          }
1000          break;
1001        case PL_WINNT_FILENAME_WINEOL:
1002          if(c == '\n') {
1003            parser->offsets.filename = parser->item_offset;
1004            result = ftp_pl_insert_finfo(data, infop);
1005            if(result) {
1006              parser->error = result;
1007              goto fail;
1008            }
1009            parser->state.NT.main = PL_WINNT_DATE;
1010            parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
1011          }
1012          else {
1013            parser->error = CURLE_FTP_BAD_FILE_LIST;
1014            goto fail;
1015          }
1016          break;
1017        }
1018        break;
1019      }
1020      break;
1021    default:
1022      retsize = bufflen + 1;
1023      goto fail;
1024    }
1025
1026    i++;
1027  }
1028  return retsize;
1029
1030fail:
1031
1032  /* Clean up any allocated memory. */
1033  if(parser->file_data) {
1034    Curl_fileinfo_cleanup(parser->file_data);
1035    parser->file_data = NULL;
1036  }
1037
1038  return retsize;
1039}
1040
1041#endif /* CURL_DISABLE_FTP */
1042