Coverage Report

Created: 2026-03-12 06:35

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