Coverage Report

Created: 2022-10-16 06:45

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