Coverage Report

Created: 2025-11-09 06:14

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