Coverage Report

Created: 2025-12-04 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/file.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
#include "curl_setup.h"
26
27
#ifndef CURL_DISABLE_FILE
28
29
#ifdef HAVE_NETINET_IN_H
30
#include <netinet/in.h>
31
#endif
32
#ifdef HAVE_NETDB_H
33
#include <netdb.h>
34
#endif
35
#ifdef HAVE_ARPA_INET_H
36
#include <arpa/inet.h>
37
#endif
38
#ifdef HAVE_NET_IF_H
39
#include <net/if.h>
40
#endif
41
#ifdef HAVE_SYS_IOCTL_H
42
#include <sys/ioctl.h>
43
#endif
44
45
#ifdef HAVE_SYS_PARAM_H
46
#include <sys/param.h>
47
#endif
48
49
#ifdef HAVE_SYS_TYPES_H
50
#include <sys/types.h>
51
#endif
52
53
#ifdef HAVE_DIRENT_H
54
#include <dirent.h>
55
#endif
56
57
#include "urldata.h"
58
#include <curl/curl.h>
59
#include "progress.h"
60
#include "sendf.h"
61
#include "escape.h"
62
#include "file.h"
63
#include "multiif.h"
64
#include "transfer.h"
65
#include "url.h"
66
#include "parsedate.h" /* for the week day and month names */
67
#include "curlx/fopen.h"
68
#include "curlx/warnless.h"
69
#include "curl_range.h"
70
71
#if defined(_WIN32) || defined(MSDOS)
72
#define DOS_FILESYSTEM 1
73
#elif defined(__amigaos4__)
74
#define AMIGA_FILESYSTEM 1
75
#endif
76
77
/* meta key for storing protocol meta at easy handle */
78
7.47k
#define CURL_META_FILE_EASY   "meta:proto:file:easy"
79
80
struct FILEPROTO {
81
  char *path; /* the path we operate on */
82
  char *freepath; /* pointer to the allocated block we must free, this might
83
                     differ from the 'path' pointer */
84
  int fd;     /* open file descriptor to read from! */
85
};
86
87
/*
88
 * Forward declarations.
89
 */
90
91
static CURLcode file_do(struct Curl_easy *data, bool *done);
92
static CURLcode file_done(struct Curl_easy *data,
93
                          CURLcode status, bool premature);
94
static CURLcode file_connect(struct Curl_easy *data, bool *done);
95
static CURLcode file_disconnect(struct Curl_easy *data,
96
                                struct connectdata *conn,
97
                                bool dead_connection);
98
static CURLcode file_setup_connection(struct Curl_easy *data,
99
                                      struct connectdata *conn);
100
101
/*
102
 * FILE scheme handler.
103
 */
104
105
const struct Curl_handler Curl_handler_file = {
106
  "file",                               /* scheme */
107
  file_setup_connection,                /* setup_connection */
108
  file_do,                              /* do_it */
109
  file_done,                            /* done */
110
  ZERO_NULL,                            /* do_more */
111
  file_connect,                         /* connect_it */
112
  ZERO_NULL,                            /* connecting */
113
  ZERO_NULL,                            /* doing */
114
  ZERO_NULL,                            /* proto_pollset */
115
  ZERO_NULL,                            /* doing_pollset */
116
  ZERO_NULL,                            /* domore_pollset */
117
  ZERO_NULL,                            /* perform_pollset */
118
  file_disconnect,                      /* disconnect */
119
  ZERO_NULL,                            /* write_resp */
120
  ZERO_NULL,                            /* write_resp_hd */
121
  ZERO_NULL,                            /* connection_check */
122
  ZERO_NULL,                            /* attach connection */
123
  ZERO_NULL,                            /* follow */
124
  0,                                    /* defport */
125
  CURLPROTO_FILE,                       /* protocol */
126
  CURLPROTO_FILE,                       /* family */
127
  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
128
};
129
130
static void file_cleanup(struct FILEPROTO *file)
131
2.64k
{
132
2.64k
  Curl_safefree(file->freepath);
133
2.64k
  file->path = NULL;
134
2.64k
  if(file->fd != -1) {
135
1.07k
    close(file->fd);
136
1.07k
    file->fd = -1;
137
1.07k
  }
138
2.64k
}
139
140
static void file_easy_dtor(void *key, size_t klen, void *entry)
141
1.32k
{
142
1.32k
  struct FILEPROTO *file = entry;
143
1.32k
  (void)key;
144
1.32k
  (void)klen;
145
1.32k
  file_cleanup(file);
146
1.32k
  curlx_free(file);
147
1.32k
}
148
149
static CURLcode file_setup_connection(struct Curl_easy *data,
150
                                      struct connectdata *conn)
151
1.32k
{
152
1.32k
  struct FILEPROTO *filep;
153
1.32k
  (void)conn;
154
  /* allocate the FILE specific struct */
155
1.32k
  filep = curlx_calloc(1, sizeof(*filep));
156
1.32k
  if(!filep ||
157
1.32k
     Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
158
0
    return CURLE_OUT_OF_MEMORY;
159
160
1.32k
  return CURLE_OK;
161
1.32k
}
162
163
/*
164
 * file_connect() gets called from Curl_protocol_connect() to allow us to
165
 * do protocol-specific actions at connect-time. We emulate a
166
 * connect-then-transfer protocol and "connect" to the file here
167
 */
168
static CURLcode file_connect(struct Curl_easy *data, bool *done)
169
2.41k
{
170
2.41k
  char *real_path;
171
2.41k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
172
2.41k
  int fd;
173
#ifdef DOS_FILESYSTEM
174
  size_t i;
175
  char *actual_path;
176
#endif
177
2.41k
  size_t real_path_len;
178
2.41k
  CURLcode result;
179
180
2.41k
  if(!file)
181
0
    return CURLE_FAILED_INIT;
182
183
2.41k
  if(file->path) {
184
    /* already connected.
185
     * the handler->connect_it() is normally only called once, but
186
     * FILE does a special check on setting up the connection which
187
     * calls this explicitly. */
188
1.09k
    *done = TRUE;
189
1.09k
    return CURLE_OK;
190
1.09k
  }
191
192
1.32k
  result = Curl_urldecode(data->state.up.path, 0, &real_path,
193
1.32k
                          &real_path_len, REJECT_ZERO);
194
1.32k
  if(result)
195
1
    return result;
196
197
#ifdef DOS_FILESYSTEM
198
  /* If the first character is a slash, and there is
199
     something that looks like a drive at the beginning of
200
     the path, skip the slash. If we remove the initial
201
     slash in all cases, paths without drive letters end up
202
     relative to the current directory which is not how
203
     browsers work.
204
205
     Some browsers accept | instead of : as the drive letter
206
     separator, so we do too.
207
208
     On other platforms, we need the slash to indicate an
209
     absolute pathname. On Windows, absolute paths start
210
     with a drive letter.
211
  */
212
  actual_path = real_path;
213
  if((actual_path[0] == '/') &&
214
      actual_path[1] &&
215
     (actual_path[2] == ':' || actual_path[2] == '|')) {
216
    actual_path[2] = ':';
217
    actual_path++;
218
    real_path_len--;
219
  }
220
221
  /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
222
  for(i = 0; i < real_path_len; ++i)
223
    if(actual_path[i] == '/')
224
      actual_path[i] = '\\';
225
    else if(!actual_path[i]) { /* binary zero */
226
      Curl_safefree(real_path);
227
      return CURLE_URL_MALFORMAT;
228
    }
229
230
  fd = curlx_open(actual_path, O_RDONLY | CURL_O_BINARY);
231
  file->path = actual_path;
232
#else
233
1.32k
  if(memchr(real_path, 0, real_path_len)) {
234
    /* binary zeroes indicate foul play */
235
0
    Curl_safefree(real_path);
236
0
    return CURLE_URL_MALFORMAT;
237
0
  }
238
239
  #ifdef AMIGA_FILESYSTEM
240
  /*
241
   * A leading slash in an AmigaDOS path denotes the parent
242
   * directory, and hence we block this as it is relative.
243
   * Absolute paths start with 'volumename:', so we check for
244
   * this first. Failing that, we treat the path as a real Unix
245
   * path, but only if the application was compiled with -lunix.
246
   */
247
  fd = -1;
248
  file->path = real_path;
249
250
  if(real_path[0] == '/') {
251
    extern int __unix_path_semantics;
252
    if(strchr(real_path + 1, ':')) {
253
      /* Amiga absolute path */
254
      fd = curlx_open(real_path + 1, O_RDONLY);
255
      file->path++;
256
    }
257
    else if(__unix_path_semantics) {
258
      /* -lunix fallback */
259
      fd = curlx_open(real_path, O_RDONLY);
260
    }
261
  }
262
  #else
263
1.32k
  fd = curlx_open(real_path, O_RDONLY);
264
1.32k
  file->path = real_path;
265
1.32k
  #endif
266
1.32k
#endif
267
1.32k
  curlx_free(file->freepath);
268
1.32k
  file->freepath = real_path; /* free this when done */
269
270
1.32k
  file->fd = fd;
271
1.32k
  if(!data->state.upload && (fd == -1)) {
272
215
    failf(data, "Could not open file %s", data->state.up.path);
273
215
    file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
274
215
    return CURLE_FILE_COULDNT_READ_FILE;
275
215
  }
276
1.10k
  *done = TRUE;
277
278
1.10k
  return CURLE_OK;
279
1.32k
}
280
281
static CURLcode file_done(struct Curl_easy *data,
282
                          CURLcode status, bool premature)
283
2.64k
{
284
2.64k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
285
2.64k
  (void)status;
286
2.64k
  (void)premature;
287
288
2.64k
  if(file)
289
1.32k
    file_cleanup(file);
290
291
2.64k
  return CURLE_OK;
292
2.64k
}
293
294
static CURLcode file_disconnect(struct Curl_easy *data,
295
                                struct connectdata *conn,
296
                                bool dead_connection)
297
1.32k
{
298
1.32k
  (void)dead_connection;
299
1.32k
  (void)conn;
300
1.32k
  return file_done(data, CURLE_OK, FALSE);
301
1.32k
}
302
303
#ifdef DOS_FILESYSTEM
304
#define DIRSEP '\\'
305
#else
306
57
#define DIRSEP '/'
307
#endif
308
309
static CURLcode file_upload(struct Curl_easy *data,
310
                            struct FILEPROTO *file)
311
57
{
312
57
  const char *dir = strchr(file->path, DIRSEP);
313
57
  int fd;
314
57
  int mode;
315
57
  CURLcode result = CURLE_OK;
316
57
  char *xfer_ulbuf;
317
57
  size_t xfer_ulblen;
318
57
  curl_off_t bytecount = 0;
319
57
  struct_stat file_stat;
320
57
  const char *sendbuf;
321
57
  bool eos = FALSE;
322
323
  /*
324
   * Since FILE: does not do the full init, we need to provide some extra
325
   * assignments here.
326
   */
327
328
57
  if(!dir)
329
0
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
330
331
57
  if(!dir[1])
332
6
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
333
334
51
  mode = O_WRONLY | O_CREAT | CURL_O_BINARY;
335
51
  if(data->state.resume_from)
336
31
    mode |= O_APPEND;
337
20
  else
338
20
    mode |= O_TRUNC;
339
340
#ifdef _WIN32
341
  fd = curlx_open(file->path, mode,
342
                  data->set.new_file_perms & (_S_IREAD | _S_IWRITE));
343
#elif (defined(ANDROID) || defined(__ANDROID__)) && \
344
  (defined(__i386__) || defined(__arm__))
345
  fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms);
346
#else
347
51
  fd = curlx_open(file->path, mode, data->set.new_file_perms);
348
51
#endif
349
51
  if(fd < 0) {
350
17
    failf(data, "cannot open %s for writing", file->path);
351
17
    return CURLE_WRITE_ERROR;
352
17
  }
353
354
34
  if(data->state.infilesize != -1)
355
    /* known size of data to "upload" */
356
34
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
357
358
  /* treat the negative resume offset value as the case of "-" */
359
34
  if(data->state.resume_from < 0) {
360
0
    if(fstat(fd, &file_stat)) {
361
0
      close(fd);
362
0
      failf(data, "cannot get the size of %s", file->path);
363
0
      return CURLE_WRITE_ERROR;
364
0
    }
365
0
    data->state.resume_from = (curl_off_t)file_stat.st_size;
366
0
  }
367
368
34
  result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
369
34
  if(result)
370
0
    goto out;
371
372
34
  while(!result && !eos) {
373
34
    size_t nread, nwritten;
374
34
    ssize_t rv;
375
34
    size_t readcount;
376
377
34
    result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
378
34
    if(result)
379
0
      break;
380
381
34
    if(!readcount)
382
34
      break;
383
384
0
    nread = readcount;
385
386
    /* skip bytes before resume point */
387
0
    if(data->state.resume_from) {
388
0
      if((curl_off_t)nread <= data->state.resume_from) {
389
0
        data->state.resume_from -= nread;
390
0
        nread = 0;
391
0
        sendbuf = xfer_ulbuf;
392
0
      }
393
0
      else {
394
0
        sendbuf = xfer_ulbuf + data->state.resume_from;
395
0
        nread -= (size_t)data->state.resume_from;
396
0
        data->state.resume_from = 0;
397
0
      }
398
0
    }
399
0
    else
400
0
      sendbuf = xfer_ulbuf;
401
402
    /* write the data to the target */
403
0
    rv = write(fd, sendbuf, nread);
404
0
    if(!curlx_sztouz(rv, &nwritten) || (nwritten != nread)) {
405
0
      result = CURLE_SEND_ERROR;
406
0
      break;
407
0
    }
408
0
    bytecount += nwritten;
409
410
0
    Curl_pgrsSetUploadCounter(data, bytecount);
411
412
0
    result = Curl_pgrsCheck(data);
413
0
  }
414
34
  if(!result)
415
34
    result = Curl_pgrsUpdate(data);
416
417
34
out:
418
34
  close(fd);
419
34
  Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
420
421
34
  return result;
422
34
}
423
424
/*
425
 * file_do() is the protocol-specific function for the do-phase, separated
426
 * from the connect-phase above. Other protocols merely setup the transfer in
427
 * the do-phase, to have it done in the main transfer loop but since some
428
 * platforms we support do not allow select()ing etc on file handles (as
429
 * opposed to sockets) we instead perform the whole do-operation in this
430
 * function.
431
 */
432
static CURLcode file_do(struct Curl_easy *data, bool *done)
433
1.09k
{
434
  /* This implementation ignores the hostname in conformance with
435
     RFC 1738. Only local files (reachable via the standard file system)
436
     are supported. This means that files on remotely mounted directories
437
     (via NFS, Samba, NT sharing) can be accessed through a file:// URL
438
  */
439
1.09k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
440
1.09k
  CURLcode result = CURLE_OK;
441
1.09k
  struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
442
                          Windows version to have a different struct without
443
                          having to redefine the simple word 'stat' */
444
1.09k
  curl_off_t expected_size = -1;
445
1.09k
  bool size_known;
446
1.09k
  bool fstated = FALSE;
447
1.09k
  int fd;
448
1.09k
  char *xfer_buf;
449
1.09k
  size_t xfer_blen;
450
451
1.09k
  *done = TRUE; /* unconditionally */
452
1.09k
  if(!file)
453
0
    return CURLE_FAILED_INIT;
454
455
1.09k
  if(data->state.upload)
456
57
    return file_upload(data, file);
457
458
  /* get the fd from the connection phase */
459
1.03k
  fd = file->fd;
460
461
  /* VMS: This only works reliable for STREAMLF files */
462
1.03k
  if(fstat(fd, &statbuf) != -1) {
463
1.03k
    if(!S_ISDIR(statbuf.st_mode))
464
295
      expected_size = statbuf.st_size;
465
    /* and store the modification time */
466
1.03k
    data->info.filetime = statbuf.st_mtime;
467
1.03k
    fstated = TRUE;
468
1.03k
  }
469
470
1.03k
  if(fstated && !data->state.range && data->set.timecondition &&
471
42
     !Curl_meets_timecondition(data, data->info.filetime))
472
22
    return CURLE_OK;
473
474
1.01k
  if(fstated) {
475
1.01k
    time_t filetime;
476
1.01k
    struct tm buffer;
477
1.01k
    const struct tm *tm = &buffer;
478
1.01k
    char header[80];
479
1.01k
    int headerlen;
480
1.01k
    static const char accept_ranges[] = { "Accept-ranges: bytes\r\n" };
481
1.01k
    if(expected_size >= 0) {
482
295
      headerlen =
483
295
        curl_msnprintf(header, sizeof(header),
484
295
                       "Content-Length: %" FMT_OFF_T "\r\n", expected_size);
485
295
      result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
486
295
      if(result)
487
0
        return result;
488
489
295
      result = Curl_client_write(data, CLIENTWRITE_HEADER,
490
295
                                 accept_ranges, sizeof(accept_ranges) - 1);
491
295
      if(result != CURLE_OK)
492
0
        return result;
493
295
    }
494
495
1.01k
    filetime = (time_t)statbuf.st_mtime;
496
1.01k
    result = Curl_gmtime(filetime, &buffer);
497
1.01k
    if(result)
498
0
      return result;
499
500
    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
501
1.01k
    headerlen =
502
1.01k
      curl_msnprintf(header, sizeof(header),
503
1.01k
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
504
1.01k
                     Curl_wkday[tm->tm_wday ? tm->tm_wday - 1 : 6],
505
1.01k
                     tm->tm_mday,
506
1.01k
                     Curl_month[tm->tm_mon],
507
1.01k
                     tm->tm_year + 1900,
508
1.01k
                     tm->tm_hour,
509
1.01k
                     tm->tm_min,
510
1.01k
                     tm->tm_sec);
511
1.01k
    result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
512
1.01k
    if(!result)
513
      /* end of headers */
514
1.01k
      result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
515
1.01k
    if(result)
516
0
      return result;
517
    /* set the file size to make it available post transfer */
518
1.01k
    Curl_pgrsSetDownloadSize(data, expected_size);
519
1.01k
    if(data->req.no_body)
520
1
      return CURLE_OK;
521
1.01k
  }
522
523
  /* Check whether file range has been specified */
524
1.01k
  result = Curl_range(data);
525
1.01k
  if(result)
526
64
    return result;
527
528
  /* Adjust the start offset in case we want to get the N last bytes
529
   * of the stream if the filesize could be determined */
530
948
  if(data->state.resume_from < 0) {
531
152
    if(!fstated) {
532
0
      failf(data, "cannot get the size of file.");
533
0
      return CURLE_READ_ERROR;
534
0
    }
535
152
    data->state.resume_from += (curl_off_t)statbuf.st_size;
536
152
  }
537
538
948
  if(data->state.resume_from > 0) {
539
    /* We check explicitly if we have a start offset, because
540
     * expected_size may be -1 if we do not know how large the file is,
541
     * in which case we should not adjust it. */
542
198
    if(data->state.resume_from <= expected_size)
543
64
      expected_size -= data->state.resume_from;
544
134
    else {
545
134
      failf(data, "failed to resume file:// transfer");
546
134
      return CURLE_BAD_DOWNLOAD_RESUME;
547
134
    }
548
198
  }
549
550
  /* A high water mark has been specified so we obey... */
551
814
  if(data->req.maxdownload > 0)
552
341
    expected_size = data->req.maxdownload;
553
554
814
  if(!fstated || (expected_size <= 0))
555
301
    size_known = FALSE;
556
513
  else
557
513
    size_known = TRUE;
558
559
  /* The following is a shortcut implementation of file reading
560
     this is both more efficient than the former call to download() and
561
     it avoids problems with select() and recv() on file descriptors
562
     in Winsock */
563
814
  if(size_known)
564
513
    Curl_pgrsSetDownloadSize(data, expected_size);
565
566
814
  if(data->state.resume_from) {
567
193
    if(!S_ISDIR(statbuf.st_mode)) {
568
#ifdef __AMIGA__
569
      if(data->state.resume_from !=
570
         lseek(fd, (off_t)data->state.resume_from, SEEK_SET))
571
#else
572
99
      if(data->state.resume_from !=
573
99
         lseek(fd, data->state.resume_from, SEEK_SET))
574
35
#endif
575
35
        return CURLE_BAD_DOWNLOAD_RESUME;
576
99
    }
577
94
    else {
578
94
      return CURLE_BAD_DOWNLOAD_RESUME;
579
94
    }
580
193
  }
581
582
685
  result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
583
685
  if(result)
584
0
    goto out;
585
586
685
  if(!S_ISDIR(statbuf.st_mode)) {
587
15.8k
    while(!result) {
588
15.8k
      ssize_t nread;
589
      /* Do not fill a whole buffer if we want less than all data */
590
15.8k
      size_t bytestoread;
591
592
15.8k
      if(size_known) {
593
15.7k
        bytestoread = (expected_size < (curl_off_t)(xfer_blen - 1)) ?
594
15.4k
          curlx_sotouz(expected_size) : (xfer_blen - 1);
595
15.7k
      }
596
70
      else
597
70
        bytestoread = xfer_blen - 1;
598
599
15.8k
      nread = read(fd, xfer_buf, bytestoread);
600
601
15.8k
      if(nread > 0)
602
15.6k
        xfer_buf[nread] = 0;
603
604
15.8k
      if(nread <= 0 || (size_known && (expected_size == 0)))
605
235
        break;
606
607
15.6k
      if(size_known)
608
15.5k
        expected_size -= nread;
609
610
15.6k
      result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
611
15.6k
      if(result)
612
21
        goto out;
613
614
15.6k
      result = Curl_pgrsCheck(data);
615
15.6k
      if(result)
616
0
        goto out;
617
15.6k
    }
618
256
  }
619
429
  else {
620
429
#ifdef HAVE_OPENDIR
621
429
    DIR *dir = opendir(file->path);
622
429
    struct dirent *entry;
623
624
429
    if(!dir) {
625
0
      result = CURLE_READ_ERROR;
626
0
      goto out;
627
0
    }
628
429
    else {
629
20.0k
      while((entry = readdir(dir))) {
630
19.5k
        if(entry->d_name[0] != '.') {
631
18.3k
          result = Curl_client_write(data, CLIENTWRITE_BODY,
632
18.3k
                   entry->d_name, strlen(entry->d_name));
633
18.3k
          if(result)
634
9
            break;
635
18.3k
          result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
636
18.3k
          if(result)
637
2
            break;
638
18.3k
        }
639
19.5k
      }
640
429
      closedir(dir);
641
429
    }
642
#else
643
    failf(data, "Directory listing not yet implemented on this platform.");
644
    result = CURLE_READ_ERROR;
645
#endif
646
429
  }
647
648
664
  if(!result)
649
653
    result = Curl_pgrsUpdate(data);
650
651
685
out:
652
685
  Curl_multi_xfer_buf_release(data, xfer_buf);
653
685
  return result;
654
664
}
655
656
#endif