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