Coverage Report

Created: 2026-10-15 06:19

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
0
#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
0
{
84
0
  curlx_safefree(file->freepath);
85
0
  file->path = NULL;
86
0
  file->is_dir = FALSE;
87
0
  if(file->fd != -1) {
88
0
    curlx_close(file->fd);
89
0
    file->fd = -1;
90
0
  }
91
0
}
92
93
static void file_easy_dtor(const void *key, size_t klen, void *entry)
94
0
{
95
0
  struct FILEPROTO *file = entry;
96
0
  (void)key;
97
0
  (void)klen;
98
0
  file_cleanup(file);
99
0
  curlx_free(file);
100
0
}
101
102
static CURLcode file_setup_connection(struct Curl_easy *data,
103
                                      struct connectdata *conn)
104
0
{
105
0
  struct FILEPROTO *filep;
106
0
  (void)conn;
107
  /* allocate the FILE specific struct */
108
0
  filep = curlx_calloc(1, sizeof(*filep));
109
0
  if(filep)
110
0
    filep->fd = -1;
111
0
  if(!filep ||
112
0
     Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
113
0
    return CURLE_OUT_OF_MEMORY;
114
115
0
  return CURLE_OK;
116
0
}
117
118
static CURLcode file_done(struct Curl_easy *data,
119
                          CURLcode status, bool premature)
120
0
{
121
0
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
122
0
  (void)status;
123
0
  (void)premature;
124
125
0
  if(file)
126
0
    file_cleanup(file);
127
128
0
  return CURLE_OK;
129
0
}
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
0
{
170
0
  char *real_path;
171
0
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
172
#ifdef _WIN32
173
  curlx_struct_stat statbuf;
174
#endif
175
0
  int fd;
176
#ifdef DOS_FILESYSTEM
177
  size_t i;
178
  char *actual_path;
179
#endif
180
0
  size_t real_path_len;
181
0
  CURLcode result;
182
183
0
  if(!file)
184
0
    return CURLE_FAILED_INIT;
185
186
0
  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
0
    *done = TRUE;
192
0
    return CURLE_OK;
193
0
  }
194
195
0
  result = Curl_urldecode(data->state.up.path, 0, &real_path,
196
0
                          &real_path_len, REJECT_ZERO);
197
0
  if(result)
198
0
    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
0
  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
0
  fd = curlx_open(real_path, O_RDONLY);
266
0
  file->path = real_path;
267
0
#endif
268
0
#endif
269
0
  curlx_free(file->freepath);
270
0
  file->freepath = real_path; /* free this when done */
271
272
0
  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
0
  if(!data->state.upload && (fd == -1) && !file->is_dir) {
280
0
    failf(data, "Could not open file %s", data->state.up.path);
281
0
    file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
282
0
    return CURLE_FILE_COULDNT_READ_FILE;
283
0
  }
284
0
  *done = TRUE;
285
286
0
  return CURLE_OK;
287
0
}
288
289
static CURLcode file_disconnect(struct Curl_easy *data,
290
                                struct connectdata *conn,
291
                                bool dead_connection)
292
0
{
293
0
  (void)dead_connection;
294
0
  (void)conn;
295
0
  return file_done(data, CURLE_OK, FALSE);
296
0
}
297
298
#ifdef DOS_FILESYSTEM
299
#define DIRSEP '\\'
300
#else
301
0
#define DIRSEP '/'
302
#endif
303
304
static CURLcode file_upload(struct Curl_easy *data,
305
                            struct FILEPROTO *file)
306
0
{
307
0
  const char *dir = strchr(file->path, DIRSEP);
308
0
  int fd;
309
0
  int mode;
310
0
  CURLcode result = CURLE_OK;
311
0
  char *xfer_ulbuf;
312
0
  size_t xfer_ulblen;
313
0
  curlx_struct_stat file_stat;
314
0
  const char *sendbuf;
315
0
  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
0
  if(!dir)
323
0
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
324
325
0
  if(!dir[1])
326
0
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
327
328
0
  mode = O_WRONLY | O_CREAT | CURL_O_BINARY;
329
0
  if(data->state.resume_from)
330
0
    mode |= O_APPEND;
331
0
  else
332
0
    mode |= O_TRUNC;
333
334
0
  fd = curlx_open(file->path, mode,
335
#ifdef _WIN32
336
                  data->set.new_file_perms & (_S_IREAD | _S_IWRITE)
337
#else
338
0
                  (mode_t)data->set.new_file_perms
339
0
#endif
340
0
                  );
341
0
  if(fd < 0) {
342
0
    failf(data, "cannot open %s for writing", file->path);
343
0
    return CURLE_WRITE_ERROR;
344
0
  }
345
346
0
  if(data->state.infilesize != -1)
347
    /* known size of data to "upload" */
348
0
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
349
350
  /* treat the negative resume offset value as the case of "-" */
351
0
  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
0
  result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
361
0
  if(result)
362
0
    goto out;
363
364
0
  while(!result && !eos) {
365
0
    size_t nread, nwritten;
366
0
    ssize_t rv;
367
0
    size_t readcount;
368
369
0
    result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
370
0
    if(result)
371
0
      break;
372
373
0
    if(!readcount)
374
0
      break;
375
376
0
    nread = readcount;
377
378
    /* skip bytes before resume point */
379
0
    if(data->state.resume_from) {
380
0
      if((curl_off_t)nread <= data->state.resume_from) {
381
0
        data->state.resume_from -= nread;
382
0
        nread = 0;
383
0
        sendbuf = xfer_ulbuf;
384
0
      }
385
0
      else {
386
0
        sendbuf = xfer_ulbuf + data->state.resume_from;
387
0
        nread -= (size_t)data->state.resume_from;
388
0
        data->state.resume_from = 0;
389
0
      }
390
0
    }
391
0
    else
392
0
      sendbuf = xfer_ulbuf;
393
394
    /* write the data to the target */
395
0
    rv = write(fd, sendbuf, nread);
396
0
    if(!curlx_sztouz(rv, &nwritten) || (nwritten != nread)) {
397
0
      result = CURLE_SEND_ERROR;
398
0
      break;
399
0
    }
400
0
    Curl_pgrs_upload_inc(data, nwritten);
401
402
0
    result = Curl_pgrsCheck(data);
403
0
  }
404
0
  if(!result)
405
0
    result = Curl_pgrsUpdate(data);
406
407
0
out:
408
0
  curlx_close(fd);
409
0
  Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
410
411
0
  return result;
412
0
}
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
0
{
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
0
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
481
0
  CURLcode result = CURLE_OK;
482
0
  curlx_struct_stat statbuf;
483
0
  curl_off_t expected_size = -1;
484
0
  bool size_known;
485
0
  bool fstated = FALSE;
486
0
  int fd;
487
0
  char *xfer_buf;
488
0
  size_t xfer_blen;
489
490
0
  *done = TRUE; /* unconditionally */
491
0
  if(!file)
492
0
    return CURLE_FAILED_INIT;
493
494
0
  if(data->state.upload)
495
0
    return file_upload(data, file);
496
497
  /* get the fd from the connection phase */
498
0
  fd = file->fd;
499
500
  /* VMS: This only works reliable for STREAMLF files */
501
0
  if((file->is_dir ? file_stat(file->path, &statbuf) :
502
0
                     curlx_fstat(fd, &statbuf)) != -1) {
503
0
    file->is_dir = (fd == -1) || S_ISDIR(statbuf.st_mode);
504
0
    if(!file->is_dir)
505
0
      expected_size = statbuf.st_size;
506
    /* and store the modification time */
507
0
    data->info.filetime = statbuf.st_mtime;
508
0
    fstated = TRUE;
509
0
  }
510
511
0
  if(fstated && !data->state.range && data->set.timecondition &&
512
0
     !Curl_meets_timecondition(data, data->info.filetime))
513
0
    return CURLE_OK;
514
515
0
  if(fstated) {
516
0
    time_t filetime;
517
0
    struct tm buffer;
518
0
    const struct tm *tm = &buffer;
519
0
    char header[80];
520
0
    int headerlen;
521
0
    static const char accept_ranges[] = { "Accept-ranges: bytes\r\n" };
522
0
    if(expected_size >= 0) {
523
0
      headerlen =
524
0
        curl_msnprintf(header, sizeof(header),
525
0
                       "Content-Length: %" FMT_OFF_T "\r\n", expected_size);
526
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
527
0
      if(result)
528
0
        return result;
529
530
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER,
531
0
                                 accept_ranges, CURL_CSTRLEN(accept_ranges));
532
0
      if(result != CURLE_OK)
533
0
        return result;
534
0
    }
535
536
0
    filetime = (time_t)statbuf.st_mtime;
537
0
    result = curlx_gmtime(filetime, &buffer);
538
0
    if(result)
539
0
      return result;
540
541
    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
542
0
    headerlen =
543
0
      curl_msnprintf(header, sizeof(header),
544
0
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
545
0
                     Curl_wkday[tm->tm_wday ? tm->tm_wday - 1 : 6],
546
0
                     tm->tm_mday,
547
0
                     Curl_month[tm->tm_mon],
548
0
                     tm->tm_year + 1900,
549
0
                     tm->tm_hour,
550
0
                     tm->tm_min,
551
0
                     tm->tm_sec);
552
0
    result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
553
0
    if(!result)
554
      /* end of headers */
555
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
556
0
    if(result)
557
0
      return result;
558
    /* set the file size to make it available post transfer */
559
0
    Curl_pgrsSetDownloadSize(data, expected_size);
560
0
    if(data->req.no_body)
561
0
      return CURLE_OK;
562
0
  }
563
564
  /* Check whether file range has been specified */
565
0
  result = Curl_range(data);
566
0
  if(result)
567
0
    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
0
  if(data->state.resume_from < 0) {
572
0
    if(!fstated) {
573
0
      failf(data, "cannot get the size of file.");
574
0
      return CURLE_READ_ERROR;
575
0
    }
576
0
    data->state.resume_from += (curl_off_t)statbuf.st_size;
577
0
  }
578
579
0
  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
0
    if(data->state.resume_from <= expected_size)
584
0
      expected_size -= data->state.resume_from;
585
0
    else {
586
0
      failf(data, "failed to resume file:// transfer");
587
0
      return CURLE_BAD_DOWNLOAD_RESUME;
588
0
    }
589
0
  }
590
591
  /* A high water mark has been specified so we obey... */
592
0
  if(data->req.maxdownload > 0)
593
0
    expected_size = data->req.maxdownload;
594
595
0
  if(!fstated || (expected_size <= 0))
596
0
    size_known = FALSE;
597
0
  else
598
0
    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
0
  if(size_known)
605
0
    Curl_pgrsSetDownloadSize(data, expected_size);
606
607
0
  if(data->state.resume_from) {
608
0
    if(!file->is_dir) {
609
0
      if(data->state.resume_from !=
610
0
         curl_lseek(fd, data->state.resume_from, SEEK_SET))
611
0
        return CURLE_BAD_DOWNLOAD_RESUME;
612
0
    }
613
0
    else {
614
0
      return CURLE_BAD_DOWNLOAD_RESUME;
615
0
    }
616
0
  }
617
618
0
  result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
619
0
  if(result)
620
0
    goto out;
621
622
0
  if(!file->is_dir) {
623
0
    while(!result) {
624
0
      ssize_t nread;
625
      /* Do not fill a whole buffer if we want less than all data */
626
0
      size_t bytestoread;
627
628
0
      if(size_known) {
629
0
        bytestoread = (expected_size < (curl_off_t)(xfer_blen - 1)) ?
630
0
          curlx_sotouz(expected_size) : (xfer_blen - 1);
631
0
      }
632
0
      else
633
0
        bytestoread = xfer_blen - 1;
634
635
0
      nread = read(fd, xfer_buf, bytestoread);
636
637
0
      if(nread > 0)
638
0
        xfer_buf[nread] = 0;
639
640
0
      if(nread <= 0 || (size_known && (expected_size == 0)))
641
0
        break;
642
643
0
      if(size_known)
644
0
        expected_size -= nread;
645
646
0
      result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
647
0
      if(result)
648
0
        goto out;
649
650
0
      result = Curl_pgrsCheck(data);
651
0
      if(result)
652
0
        goto out;
653
0
    }
654
0
  }
655
0
  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
0
    struct dirent *entry;
663
664
0
    if(!dir) {
665
0
      result = CURLE_READ_ERROR;
666
0
      goto out;
667
0
    }
668
0
    else {
669
0
      while((entry = readdir(dir))) {
670
0
        if(entry->d_name[0] != '.') {
671
0
          result = Curl_client_write(data, CLIENTWRITE_BODY,
672
0
                   entry->d_name, strlen(entry->d_name));
673
0
          if(result)
674
0
            break;
675
0
          result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
676
0
          if(result)
677
0
            break;
678
0
        }
679
0
      }
680
0
      closedir(dir);
681
0
    }
682
#else
683
    failf(data, "Directory listing not yet implemented on this platform.");
684
    result = CURLE_READ_ERROR;
685
#endif
686
0
  }
687
688
0
  if(!result)
689
0
    result = Curl_pgrsUpdate(data);
690
691
0
out:
692
0
  Curl_multi_xfer_buf_release(data, xfer_buf);
693
0
  return result;
694
0
}
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