Coverage Report

Created: 2026-10-22 06:56

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