Coverage Report

Created: 2026-09-01 06:59

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/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(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
#ifdef _WIN32
335
  fd = curlx_open(file->path, mode,
336
                  data->set.new_file_perms & (_S_IREAD | _S_IWRITE));
337
#elif (defined(ANDROID) || defined(__ANDROID__)) && \
338
  (defined(__i386__) || defined(__arm__))
339
  fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms);
340
#else
341
0
  fd = curlx_open(file->path, mode, data->set.new_file_perms);
342
0
#endif
343
0
  if(fd < 0) {
344
0
    failf(data, "cannot open %s for writing", file->path);
345
0
    return CURLE_WRITE_ERROR;
346
0
  }
347
348
0
  if(data->state.infilesize != -1)
349
    /* known size of data to "upload" */
350
0
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
351
352
  /* treat the negative resume offset value as the case of "-" */
353
0
  if(data->state.resume_from < 0) {
354
0
    if(curlx_fstat(fd, &file_stat)) {
355
0
      curlx_close(fd);
356
0
      failf(data, "cannot get the size of %s", file->path);
357
0
      return CURLE_WRITE_ERROR;
358
0
    }
359
0
    data->state.resume_from = (curl_off_t)file_stat.st_size;
360
0
  }
361
362
0
  result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
363
0
  if(result)
364
0
    goto out;
365
366
0
  while(!result && !eos) {
367
0
    size_t nread, nwritten;
368
0
    ssize_t rv;
369
0
    size_t readcount;
370
371
0
    result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
372
0
    if(result)
373
0
      break;
374
375
0
    if(!readcount)
376
0
      break;
377
378
0
    nread = readcount;
379
380
    /* skip bytes before resume point */
381
0
    if(data->state.resume_from) {
382
0
      if((curl_off_t)nread <= data->state.resume_from) {
383
0
        data->state.resume_from -= nread;
384
0
        nread = 0;
385
0
        sendbuf = xfer_ulbuf;
386
0
      }
387
0
      else {
388
0
        sendbuf = xfer_ulbuf + data->state.resume_from;
389
0
        nread -= (size_t)data->state.resume_from;
390
0
        data->state.resume_from = 0;
391
0
      }
392
0
    }
393
0
    else
394
0
      sendbuf = xfer_ulbuf;
395
396
    /* write the data to the target */
397
0
    rv = write(fd, sendbuf, nread);
398
0
    if(!curlx_sztouz(rv, &nwritten) || (nwritten != nread)) {
399
0
      result = CURLE_SEND_ERROR;
400
0
      break;
401
0
    }
402
0
    Curl_pgrs_upload_inc(data, nwritten);
403
404
0
    result = Curl_pgrsCheck(data);
405
0
  }
406
0
  if(!result)
407
0
    result = Curl_pgrsUpdate(data);
408
409
0
out:
410
0
  curlx_close(fd);
411
0
  Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
412
413
0
  return result;
414
0
}
415
416
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP)
417
static CURLcode win32_file_list(struct Curl_easy *data, const char *path)
418
{
419
  WIN32_FIND_DATA entry;
420
  HANDLE handle;
421
  char *pattern;
422
  size_t pathlen = strlen(path);
423
  CURLcode result = CURLE_OK;
424
  DWORD error;
425
426
  pattern = curl_maprintf("%s%s*", path,
427
                          pathlen &&
428
                          (path[pathlen - 1] == '\\' ||
429
                           path[pathlen - 1] == '/') ? "" : "\\");
430
  if(!pattern)
431
    return CURLE_OUT_OF_MEMORY;
432
433
  handle = curlx_FindFirstFile(pattern, &entry);
434
  curlx_free(pattern);
435
  if(handle == INVALID_HANDLE_VALUE)
436
    return CURLE_READ_ERROR;
437
438
  do {
439
    if(entry.cFileName[0] != TEXT('.')) {
440
      char *name = curlx_convert_tchar_to_UTF8(entry.cFileName);
441
442
      if(!name) {
443
        result = CURLE_OUT_OF_MEMORY;
444
        break;
445
      }
446
447
      result = Curl_client_write(data, CLIENTWRITE_BODY,
448
                                 name, strlen(name));
449
      curlx_free(name);
450
      if(result)
451
        break;
452
453
      result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
454
      if(result)
455
        break;
456
    }
457
  } while(FindNextFile(handle, &entry));
458
459
  error = GetLastError();
460
  if(!result && error != ERROR_NO_MORE_FILES)
461
    result = CURLE_READ_ERROR;
462
463
  FindClose(handle);
464
  return result;
465
}
466
#endif
467
468
/*
469
 * file_do() is the protocol-specific function for the do-phase, separated
470
 * from the connect-phase above. Other protocols merely setup the transfer in
471
 * the do-phase, to have it done in the main transfer loop but since some
472
 * platforms we support do not allow select()ing etc on file handles (as
473
 * opposed to sockets) we instead perform the whole do-operation in this
474
 * function.
475
 */
476
static CURLcode file_do(struct Curl_easy *data, bool *done)
477
0
{
478
  /* This implementation ignores the hostname in conformance with
479
     RFC 1738. Only local files (reachable via the standard file system)
480
     are supported. This means that files on remotely mounted directories
481
     (via NFS, Samba, NT sharing) can be accessed through a file:// URL */
482
0
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
483
0
  CURLcode result = CURLE_OK;
484
0
  curlx_struct_stat statbuf;
485
0
  curl_off_t expected_size = -1;
486
0
  bool size_known;
487
0
  bool fstated = FALSE;
488
0
  int fd;
489
0
  char *xfer_buf;
490
0
  size_t xfer_blen;
491
492
0
  *done = TRUE; /* unconditionally */
493
0
  if(!file)
494
0
    return CURLE_FAILED_INIT;
495
496
0
  if(data->state.upload)
497
0
    return file_upload(data, file);
498
499
  /* get the fd from the connection phase */
500
0
  fd = file->fd;
501
502
  /* VMS: This only works reliable for STREAMLF files */
503
0
  if((file->is_dir ? file_stat(file->path, &statbuf) :
504
0
                     curlx_fstat(fd, &statbuf)) != -1) {
505
0
    file->is_dir = (fd == -1) || S_ISDIR(statbuf.st_mode);
506
0
    if(!file->is_dir)
507
0
      expected_size = statbuf.st_size;
508
    /* and store the modification time */
509
0
    data->info.filetime = statbuf.st_mtime;
510
0
    fstated = TRUE;
511
0
  }
512
513
0
  if(fstated && !data->state.range && data->set.timecondition &&
514
0
     !Curl_meets_timecondition(data, data->info.filetime))
515
0
    return CURLE_OK;
516
517
0
  if(fstated) {
518
0
    time_t filetime;
519
0
    struct tm buffer;
520
0
    const struct tm *tm = &buffer;
521
0
    char header[80];
522
0
    int headerlen;
523
0
    static const char accept_ranges[] = { "Accept-ranges: bytes\r\n" };
524
0
    if(expected_size >= 0) {
525
0
      headerlen =
526
0
        curl_msnprintf(header, sizeof(header),
527
0
                       "Content-Length: %" FMT_OFF_T "\r\n", expected_size);
528
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
529
0
      if(result)
530
0
        return result;
531
532
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER,
533
0
                                 accept_ranges, CURL_CSTRLEN(accept_ranges));
534
0
      if(result != CURLE_OK)
535
0
        return result;
536
0
    }
537
538
0
    filetime = (time_t)statbuf.st_mtime;
539
0
    result = curlx_gmtime(filetime, &buffer);
540
0
    if(result)
541
0
      return result;
542
543
    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
544
0
    headerlen =
545
0
      curl_msnprintf(header, sizeof(header),
546
0
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
547
0
                     Curl_wkday[tm->tm_wday ? tm->tm_wday - 1 : 6],
548
0
                     tm->tm_mday,
549
0
                     Curl_month[tm->tm_mon],
550
0
                     tm->tm_year + 1900,
551
0
                     tm->tm_hour,
552
0
                     tm->tm_min,
553
0
                     tm->tm_sec);
554
0
    result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
555
0
    if(!result)
556
      /* end of headers */
557
0
      result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
558
0
    if(result)
559
0
      return result;
560
    /* set the file size to make it available post transfer */
561
0
    Curl_pgrsSetDownloadSize(data, expected_size);
562
0
    if(data->req.no_body)
563
0
      return CURLE_OK;
564
0
  }
565
566
  /* Check whether file range has been specified */
567
0
  result = Curl_range(data);
568
0
  if(result)
569
0
    return result;
570
571
  /* Adjust the start offset in case we want to get the N last bytes
572
   * of the stream if the filesize could be determined */
573
0
  if(data->state.resume_from < 0) {
574
0
    if(!fstated) {
575
0
      failf(data, "cannot get the size of file.");
576
0
      return CURLE_READ_ERROR;
577
0
    }
578
0
    data->state.resume_from += (curl_off_t)statbuf.st_size;
579
0
  }
580
581
0
  if(data->state.resume_from > 0) {
582
    /* We check explicitly if we have a start offset, because
583
     * expected_size may be -1 if we do not know how large the file is,
584
     * in which case we should not adjust it. */
585
0
    if(data->state.resume_from <= expected_size)
586
0
      expected_size -= data->state.resume_from;
587
0
    else {
588
0
      failf(data, "failed to resume file:// transfer");
589
0
      return CURLE_BAD_DOWNLOAD_RESUME;
590
0
    }
591
0
  }
592
593
  /* A high water mark has been specified so we obey... */
594
0
  if(data->req.maxdownload > 0)
595
0
    expected_size = data->req.maxdownload;
596
597
0
  if(!fstated || (expected_size <= 0))
598
0
    size_known = FALSE;
599
0
  else
600
0
    size_known = TRUE;
601
602
  /* The following is a shortcut implementation of file reading
603
     this is both more efficient than the former call to download() and
604
     it avoids problems with select() and recv() on file descriptors
605
     in Winsock */
606
0
  if(size_known)
607
0
    Curl_pgrsSetDownloadSize(data, expected_size);
608
609
0
  if(data->state.resume_from) {
610
0
    if(!file->is_dir) {
611
0
      if(data->state.resume_from !=
612
0
         curl_lseek(fd, data->state.resume_from, SEEK_SET))
613
0
        return CURLE_BAD_DOWNLOAD_RESUME;
614
0
    }
615
0
    else {
616
0
      return CURLE_BAD_DOWNLOAD_RESUME;
617
0
    }
618
0
  }
619
620
0
  result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
621
0
  if(result)
622
0
    goto out;
623
624
0
  if(!file->is_dir) {
625
0
    while(!result) {
626
0
      ssize_t nread;
627
      /* Do not fill a whole buffer if we want less than all data */
628
0
      size_t bytestoread;
629
630
0
      if(size_known) {
631
0
        bytestoread = (expected_size < (curl_off_t)(xfer_blen - 1)) ?
632
0
          curlx_sotouz(expected_size) : (xfer_blen - 1);
633
0
      }
634
0
      else
635
0
        bytestoread = xfer_blen - 1;
636
637
0
      nread = read(fd, xfer_buf, bytestoread);
638
639
0
      if(nread > 0)
640
0
        xfer_buf[nread] = 0;
641
642
0
      if(nread <= 0 || (size_known && (expected_size == 0)))
643
0
        break;
644
645
0
      if(size_known)
646
0
        expected_size -= nread;
647
648
0
      result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
649
0
      if(result)
650
0
        goto out;
651
652
0
      result = Curl_pgrsCheck(data);
653
0
      if(result)
654
0
        goto out;
655
0
    }
656
0
  }
657
0
  else {
658
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP)
659
    result = win32_file_list(data, file->path);
660
    if(result)
661
      goto out;
662
#elif defined(HAVE_OPENDIR)
663
    DIR *dir = opendir(file->path);
664
0
    struct dirent *entry;
665
666
0
    if(!dir) {
667
0
      result = CURLE_READ_ERROR;
668
0
      goto out;
669
0
    }
670
0
    else {
671
0
      while((entry = readdir(dir))) {
672
0
        if(entry->d_name[0] != '.') {
673
0
          result = Curl_client_write(data, CLIENTWRITE_BODY,
674
0
                   entry->d_name, strlen(entry->d_name));
675
0
          if(result)
676
0
            break;
677
0
          result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
678
0
          if(result)
679
0
            break;
680
0
        }
681
0
      }
682
0
      closedir(dir);
683
0
    }
684
#else
685
    failf(data, "Directory listing not yet implemented on this platform.");
686
    result = CURLE_READ_ERROR;
687
#endif
688
0
  }
689
690
0
  if(!result)
691
0
    result = Curl_pgrsUpdate(data);
692
693
0
out:
694
0
  Curl_multi_xfer_buf_release(data, xfer_buf);
695
0
  return result;
696
0
}
697
698
const struct Curl_protocol Curl_protocol_file = {
699
  file_setup_connection,                /* setup_connection */
700
  file_do,                              /* do_it */
701
  file_done,                            /* done */
702
  ZERO_NULL,                            /* do_more */
703
  file_connect,                         /* connect_it */
704
  ZERO_NULL,                            /* connecting */
705
  ZERO_NULL,                            /* doing */
706
  ZERO_NULL,                            /* proto_pollset */
707
  ZERO_NULL,                            /* doing_pollset */
708
  ZERO_NULL,                            /* domore_pollset */
709
  ZERO_NULL,                            /* perform_pollset */
710
  file_disconnect,                      /* disconnect */
711
  ZERO_NULL,                            /* write_resp */
712
  ZERO_NULL,                            /* write_resp_hd */
713
  ZERO_NULL,                            /* connection_is_dead */
714
  ZERO_NULL,                            /* attach connection */
715
  ZERO_NULL,                            /* follow */
716
};
717
718
#endif