Coverage Report

Created: 2025-10-10 06:09

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