Coverage Report

Created: 2026-04-29 07:01

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