1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * SPDX-License-Identifier: curl
23 ***************************************************************************/
25 #include "curl_setup.h"
27 #ifndef CURL_DISABLE_FILE
29 #ifdef HAVE_NETINET_IN_H
30 #include <netinet/in.h>
35 #ifdef HAVE_ARPA_INET_H
36 #include <arpa/inet.h>
41 #ifdef HAVE_SYS_IOCTL_H
42 #include <sys/ioctl.h>
45 #ifdef HAVE_SYS_PARAM_H
46 #include <sys/param.h>
53 #include "strtoofft.h"
55 #include <curl/curl.h>
60 #include "speedcheck.h"
64 #include "parsedate.h" /* for the week day and month names */
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"
72 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
73 #define DOS_FILESYSTEM 1
74 #elif defined(__amigaos4__)
75 #define AMIGA_FILESYSTEM 1
78 #ifdef OPEN_NEEDS_ARG3
79 # define open_readonly(p,f) open((p),(f),(0))
81 # define open_readonly(p,f) open((p),(f))
85 * Forward declarations.
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);
99 * FILE scheme handler.
102 const struct Curl_handler Curl_handler_file = {
104 file_setup_connection, /* setup_connection */
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 */
120 CURLPROTO_FILE, /* protocol */
121 CURLPROTO_FILE, /* family */
122 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
126 static CURLcode file_setup_connection(struct Curl_easy *data,
127 struct connectdata *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;
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
143 static CURLcode file_connect(struct Curl_easy *data, bool *done)
146 struct FILEPROTO *file = data->req.p.file;
148 #ifdef DOS_FILESYSTEM
152 size_t real_path_len;
154 CURLcode result = Curl_urldecode(data->state.up.path, 0, &real_path,
155 &real_path_len, REJECT_ZERO);
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
167 Some browsers accept | instead of : as the drive letter
168 separator, so we do too.
170 On other platforms, we need the slash to indicate an
171 absolute pathname. On Windows, absolute paths start
174 actual_path = real_path;
175 if((actual_path[0] == '/') &&
177 (actual_path[2] == ':' || actual_path[2] == '|')) {
178 actual_path[2] = ':';
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;
192 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
193 file->path = actual_path;
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;
201 #ifdef AMIGA_FILESYSTEM
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.
210 file->path = real_path;
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);
219 else if(__unix_path_semantics) {
220 /* -lunix fallback */
221 fd = open_readonly(real_path, O_RDONLY);
225 fd = open_readonly(real_path, O_RDONLY);
226 file->path = real_path;
229 file->freepath = real_path; /* free this when done */
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;
242 static CURLcode file_done(struct Curl_easy *data,
243 CURLcode status, bool premature)
245 struct FILEPROTO *file = data->req.p.file;
246 (void)status; /* not used */
247 (void)premature; /* not used */
250 Curl_safefree(file->freepath);
260 static CURLcode file_disconnect(struct Curl_easy *data,
261 struct connectdata *conn,
262 bool dead_connection)
264 (void)dead_connection; /* not used */
266 return file_done(data, CURLE_OK, FALSE);
269 #ifdef DOS_FILESYSTEM
275 static CURLcode file_upload(struct Curl_easy *data)
277 struct FILEPROTO *file = data->req.p.file;
278 const char *dir = strchr(file->path, DIRSEP);
281 CURLcode result = CURLE_OK;
282 char *buf = data->state.buffer;
283 curl_off_t bytecount = 0;
284 struct_stat file_stat;
288 * Since FILE: doesn't do the full init, we need to provide some extra
291 data->req.upload_fromhere = buf;
294 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
297 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
300 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
302 #define MODE_DEFAULT O_WRONLY|O_CREAT
305 if(data->state.resume_from)
306 mode = MODE_DEFAULT|O_APPEND;
308 mode = MODE_DEFAULT|O_TRUNC;
310 fd = open(file->path, mode, data->set.new_file_perms);
312 failf(data, "Can't open %s for writing", file->path);
313 return CURLE_WRITE_ERROR;
316 if(-1 != data->state.infilesize)
317 /* known size of data to "upload" */
318 Curl_pgrsSetUploadSize(data, data->state.infilesize);
320 /* treat the negative resume offset value as the case of "-" */
321 if(data->state.resume_from < 0) {
322 if(fstat(fd, &file_stat)) {
324 failf(data, "Can't get the size of %s", file->path);
325 return CURLE_WRITE_ERROR;
327 data->state.resume_from = (curl_off_t)file_stat.st_size;
334 result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount);
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;
351 buf2 = buf + data->state.resume_from;
352 nread -= (size_t)data->state.resume_from;
353 data->state.resume_from = 0;
359 /* write the data to the target */
360 nwrite = write(fd, buf2, nread);
361 if(nwrite != nread) {
362 result = CURLE_SEND_ERROR;
368 Curl_pgrsSetUploadCounter(data, bytecount);
370 if(Curl_pgrsUpdate(data))
371 result = CURLE_ABORTED_BY_CALLBACK;
373 result = Curl_speedcheck(data, Curl_now());
375 if(!result && Curl_pgrsUpdate(data))
376 result = CURLE_ABORTED_BY_CALLBACK;
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
391 static CURLcode file_do(struct Curl_easy *data, bool *done)
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
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;
404 bool fstated = FALSE;
405 char *buf = data->state.buffer;
406 curl_off_t bytecount = 0;
408 struct FILEPROTO *file;
410 *done = TRUE; /* unconditionally */
412 Curl_pgrsStartNow(data);
415 return file_upload(data);
417 file = data->req.p.file;
419 /* get the fd from the connection phase */
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;
431 if(fstated && !data->state.range && data->set.timecondition) {
432 if(!Curl_meets_timecondition(data, data->info.filetime)) {
441 const struct tm *tm = &buffer;
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",
449 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
453 result = Curl_client_write(data, CLIENTWRITE_HEADER,
454 accept_ranges, strlen(accept_ranges));
455 if(result != CURLE_OK)
459 filetime = (time_t)statbuf.st_mtime;
460 result = Curl_gmtime(filetime, &buffer);
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],
469 Curl_month[tm->tm_mon],
474 data->set.opt_no_body ? "": "\r\n");
475 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
478 /* set the file size to make it available post transfer */
479 Curl_pgrsSetDownloadSize(data, expected_size);
480 if(data->set.opt_no_body)
484 /* Check whether file range has been specified */
485 result = Curl_range(data);
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) {
493 failf(data, "Can't get the size of file.");
494 return CURLE_READ_ERROR;
496 data->state.resume_from += (curl_off_t)statbuf.st_size;
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;
506 failf(data, "failed to resume file:// transfer");
507 return CURLE_BAD_DOWNLOAD_RESUME;
511 /* A high water mark has been specified so we obey... */
512 if(data->req.maxdownload > 0)
513 expected_size = data->req.maxdownload;
515 if(!fstated || (expected_size <= 0))
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
525 Curl_pgrsSetDownloadSize(data, expected_size);
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;
533 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
537 /* Don't fill a whole buffer if we want less than all data */
541 bytestoread = (expected_size < data->set.buffer_size) ?
542 curlx_sotouz(expected_size) : (size_t)data->set.buffer_size;
545 bytestoread = data->set.buffer_size-1;
547 nread = read(fd, buf, bytestoread);
552 if(nread <= 0 || (size_known && (expected_size == 0)))
557 expected_size -= nread;
559 result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread);
563 Curl_pgrsSetDownloadCounter(data, bytecount);
565 if(Curl_pgrsUpdate(data))
566 result = CURLE_ABORTED_BY_CALLBACK;
568 result = Curl_speedcheck(data, Curl_now());
570 if(Curl_pgrsUpdate(data))
571 result = CURLE_ABORTED_BY_CALLBACK;