1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <[email protected]>, 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# define open_readonly(p,f) open((p),(f))
82#endif
83
84/*
85 * Forward declarations.
86 */
87
88static CURLcode file_do(struct Curl_easy *data, bool *done);
89static CURLcode file_done(struct Curl_easy *data,
90 CURLcode status, bool premature);
91static CURLcode file_connect(struct Curl_easy *data, bool *done);
92static CURLcode file_disconnect(struct Curl_easy *data,
93 struct connectdata *conn,
94 bool dead_connection);
95static CURLcode file_setup_connection(struct Curl_easy *data,
96 struct connectdata *conn);
97
98/*
99 * FILE scheme handler.
100 */
101
102const 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
126static CURLcode file_setup_connection(struct Curl_easy *data,
127 struct connectdata *conn)
128{
129 (void)conn;
130 /* allocate the FILE specific struct */
131 data->req.p.file = calloc(1, sizeof(struct FILEPROTO));
132 if(!data->req.p.file)
133 return CURLE_OUT_OF_MEMORY;
134
135 return CURLE_OK;
136}
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 */
143static CURLcode file_connect(struct Curl_easy *data, bool *done)
144{
145 char *real_path;
146 struct FILEPROTO *file = data->req.p.file;
147 int fd;
148#ifdef DOS_FILESYSTEM
149 size_t i;
150 char *actual_path;
151#endif
152 size_t real_path_len;
153
154 CURLcode result = Curl_urldecode(data->state.up.path, 0, &real_path,
155 &real_path_len, REJECT_ZERO);
156 if(result)
157 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 if(memchr(real_path, 0, real_path_len)) {
196 /* binary zeroes indicate foul play */
197 Curl_safefree(real_path);
198 return CURLE_URL_MALFORMAT;
199 }
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 fd = open_readonly(real_path, O_RDONLY);
226 file->path = real_path;
227 #endif
228#endif
229 file->freepath = real_path; /* free this when done */
230
231 file->fd = fd;
232 if(!data->set.upload && (fd == -1)) {
233 failf(data, "Couldn't open file %s", data->state.up.path);
234 file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
235 return CURLE_FILE_COULDNT_READ_FILE;
236 }
237 *done = TRUE;
238
239 return CURLE_OK;
240}
241
242static CURLcode file_done(struct Curl_easy *data,
243 CURLcode status, bool premature)
244{
245 struct FILEPROTO *file = data->req.p.file;
246 (void)status; /* not used */
247 (void)premature; /* not used */
248
249 if(file) {
250 Curl_safefree(file->freepath);
251 file->path = NULL;
252 if(file->fd != -1)
253 close(file->fd);
254 file->fd = -1;
255 }
256
257 return CURLE_OK;
258}
259
260static CURLcode file_disconnect(struct Curl_easy *data,
261 struct connectdata *conn,
262 bool dead_connection)
263{
264 (void)dead_connection; /* not used */
265 (void)conn;
266 return file_done(data, CURLE_OK, FALSE);
267}
268
269#ifdef DOS_FILESYSTEM
270#define DIRSEP '\\'
271#else
272#define DIRSEP '/'
273#endif
274
275static CURLcode file_upload(struct Curl_easy *data)
276{
277 struct FILEPROTO *file = data->req.p.file;
278 const char *dir = strchr(file->path, DIRSEP);
279 int fd;
280 int mode;
281 CURLcode result = CURLE_OK;
282 char *buf = data->state.buffer;
283 curl_off_t bytecount = 0;
284 struct_stat file_stat;
285 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 data->req.upload_fromhere = buf;
292
293 if(!dir)
294 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
295
296 if(!dir[1])
297 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#define MODE_DEFAULT O_WRONLY|O_CREAT
303#endif
304
305 if(data->state.resume_from)
306 mode = MODE_DEFAULT|O_APPEND;
307 else
308 mode = MODE_DEFAULT|O_TRUNC;
309
310 fd = open(file->path, mode, data->set.new_file_perms);
311 if(fd < 0) {
312 failf(data, "Can't open %s for writing", file->path);
313 return CURLE_WRITE_ERROR;
314 }
315
316 if(-1 != data->state.infilesize)
317 /* known size of data to "upload" */
318 Curl_pgrsSetUploadSize(data, data->state.infilesize);
319
320 /* treat the negative resume offset value as the case of "-" */
321 if(data->state.resume_from < 0) {
322 if(fstat(fd, &file_stat)) {
323 close(fd);
324 failf(data, "Can't get the size of %s", file->path);
325 return CURLE_WRITE_ERROR;
326 }
327 data->state.resume_from = (curl_off_t)file_stat.st_size;
328 }
329
330 while(!result) {
331 size_t nread;
332 size_t nwrite;
333 size_t readcount;
334 result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount);
335 if(result)
336 break;
337
338 if(!readcount)
339 break;
340
341 nread = readcount;
342
343 /*skip bytes before resume point*/
344 if(data->state.resume_from) {
345 if((curl_off_t)nread <= data->state.resume_from) {
346 data->state.resume_from -= nread;
347 nread = 0;
348 buf2 = buf;
349 }
350 else {
351 buf2 = buf + data->state.resume_from;
352 nread -= (size_t)data->state.resume_from;
353 data->state.resume_from = 0;
354 }
355 }
356 else
357 buf2 = buf;
358
359 /* write the data to the target */
360 nwrite = write(fd, buf2, nread);
361 if(nwrite != nread) {
362 result = CURLE_SEND_ERROR;
363 break;
364 }
365
366 bytecount += nread;
367
368 Curl_pgrsSetUploadCounter(data, bytecount);
369
370 if(Curl_pgrsUpdate(data))
371 result = CURLE_ABORTED_BY_CALLBACK;
372 else
373 result = Curl_speedcheck(data, Curl_now());
374 }
375 if(!result && Curl_pgrsUpdate(data))
376 result = CURLE_ABORTED_BY_CALLBACK;
377
378 close(fd);
379
380 return result;
381}
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 */
391static CURLcode file_do(struct Curl_easy *data, bool *done)
392{
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 CURLcode result = CURLE_OK;
399 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 curl_off_t expected_size = -1;
403 bool size_known;
404 bool fstated = FALSE;
405 char *buf = data->state.buffer;
406 curl_off_t bytecount = 0;
407 int fd;
408 struct FILEPROTO *file;
409
410 *done = TRUE; /* unconditionally */
411
412 Curl_pgrsStartNow(data);
413
414 if(data->set.upload)
415 return file_upload(data);
416
417 file = data->req.p.file;
418
419 /* get the fd from the connection phase */
420 fd = file->fd;
421
422 /* VMS: This only works reliable for STREAMLF files */
423 if(-1 != fstat(fd, &statbuf)) {
424 if(!S_ISDIR(statbuf.st_mode))
425 expected_size = statbuf.st_size;
426 /* and store the modification time */
427 data->info.filetime = statbuf.st_mtime;
428 fstated = TRUE;
429 }
430
431 if(fstated && !data->state.range && data->set.timecondition) {
432 if(!Curl_meets_timecondition(data, data->info.filetime)) {
433 *done = TRUE;
434 return CURLE_OK;
435 }
436 }
437
438 if(fstated) {
439 time_t filetime;
440 struct tm buffer;
441 const struct tm *tm = &buffer;
442 char header[80];
443 int headerlen;
444 char accept_ranges[24]= { "Accept-ranges: bytes\r\n" };
445 if(expected_size >= 0) {
446 headerlen = msnprintf(header, sizeof(header),
447 "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n",
448 expected_size);
449 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
450 if(result)
451 return result;
452
453 result = Curl_client_write(data, CLIENTWRITE_HEADER,
454 accept_ranges, strlen(accept_ranges));
455 if(result != CURLE_OK)
456 return result;
457 }
458
459 filetime = (time_t)statbuf.st_mtime;
460 result = Curl_gmtime(filetime, &buffer);
461 if(result)
462 return result;
463
464 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
465 headerlen = msnprintf(header, sizeof(header),
466 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n%s",
467 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
468 tm->tm_mday,
469 Curl_month[tm->tm_mon],
470 tm->tm_year + 1900,
471 tm->tm_hour,
472 tm->tm_min,
473 tm->tm_sec,
474 data->set.opt_no_body ? "": "\r\n");
475 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
476 if(result)
477 return result;
478 /* set the file size to make it available post transfer */
479 Curl_pgrsSetDownloadSize(data, expected_size);
480 if(data->set.opt_no_body)
481 return result;
482 }
483
484 /* Check whether file range has been specified */
485 result = Curl_range(data);
486 if(result)
487 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 if(data->state.resume_from < 0) {
492 if(!fstated) {
493 failf(data, "Can't get the size of file.");
494 return CURLE_READ_ERROR;
495 }
496 data->state.resume_from += (curl_off_t)statbuf.st_size;
497 }
498
499 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 if(data->state.resume_from <= expected_size)
504 expected_size -= data->state.resume_from;
505 else {
506 failf(data, "failed to resume file:// transfer");
507 return CURLE_BAD_DOWNLOAD_RESUME;
508 }
509 }
510
511 /* A high water mark has been specified so we obey... */
512 if(data->req.maxdownload > 0)
513 expected_size = data->req.maxdownload;
514
515 if(!fstated || (expected_size <= 0))
516 size_known = FALSE;
517 else
518 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 if(size_known)
525 Curl_pgrsSetDownloadSize(data, expected_size);
526
527 if(data->state.resume_from) {
528 if(data->state.resume_from !=
529 lseek(fd, data->state.resume_from, SEEK_SET))
530 return CURLE_BAD_DOWNLOAD_RESUME;
531 }
532
533 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
534
535 while(!result) {
536 ssize_t nread;
537 /* Don't fill a whole buffer if we want less than all data */
538 size_t bytestoread;
539
540 if(size_known) {
541 bytestoread = (expected_size < data->set.buffer_size) ?
542 curlx_sotouz(expected_size) : (size_t)data->set.buffer_size;
543 }
544 else
545 bytestoread = data->set.buffer_size-1;
546
547 nread = read(fd, buf, bytestoread);
548
549 if(nread > 0)
550 buf[nread] = 0;
551
552 if(nread <= 0 || (size_known && (expected_size == 0)))
553 break;
554
555 bytecount += nread;
556 if(size_known)
557 expected_size -= nread;
558
559 result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread);
560 if(result)
561 return result;
562
563 Curl_pgrsSetDownloadCounter(data, bytecount);
564
565 if(Curl_pgrsUpdate(data))
566 result = CURLE_ABORTED_BY_CALLBACK;
567 else
568 result = Curl_speedcheck(data, Curl_now());
569 }
570 if(Curl_pgrsUpdate(data))
571 result = CURLE_ABORTED_BY_CALLBACK;
572
573 return result;
574}
575
576#endif
577