Coverage Report

Created: 2023-12-08 06:48

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