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