Coverage Report

Created: 2025-10-10 06:19

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