Coverage Report

Created: 2026-01-10 07:08

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