Coverage Report

Created: 2025-12-03 07:13

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