Coverage Report

Created: 2024-02-25 06:14

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