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