Coverage Report

Created: 2026-07-30 07:03

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