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 ***************************************************************************/
23 #include "curl_setup.h"
25 #ifndef CURL_DISABLE_FILE
27 #ifdef HAVE_NETINET_IN_H
28 #include <netinet/in.h>
33 #ifdef HAVE_ARPA_INET_H
34 #include <arpa/inet.h>
39 #ifdef HAVE_SYS_IOCTL_H
40 #include <sys/ioctl.h>
43 #ifdef HAVE_SYS_PARAM_H
44 #include <sys/param.h>
51 #include "strtoofft.h"
53 #include <curl/curl.h>
58 #include "speedcheck.h"
62 #include "parsedate.h" /* for the week day and month names */
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"
70 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
71 #define DOS_FILESYSTEM 1
74 #ifdef OPEN_NEEDS_ARG3
75 # define open_readonly(p,f) open((p),(f),(0))
77 # define open_readonly(p,f) open((p),(f))
81 * Forward declarations.
84 static CURLcode file_do(struct Curl_easy *data, bool *done);
85 static CURLcode file_done(struct Curl_easy *data,
86 CURLcode status, bool premature);
87 static CURLcode file_connect(struct Curl_easy *data, bool *done);
88 static CURLcode file_disconnect(struct Curl_easy *data,
89 struct connectdata *conn,
90 bool dead_connection);
91 static CURLcode file_setup_connection(struct Curl_easy *data,
92 struct connectdata *conn);
95 * FILE scheme handler.
98 const struct Curl_handler Curl_handler_file = {
100 file_setup_connection, /* setup_connection */
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 */
116 CURLPROTO_FILE, /* protocol */
117 CURLPROTO_FILE, /* family */
118 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
122 static CURLcode file_setup_connection(struct Curl_easy *data,
123 struct connectdata *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;
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
139 static CURLcode file_connect(struct Curl_easy *data, bool *done)
142 struct FILEPROTO *file = data->req.p.file;
144 #ifdef DOS_FILESYSTEM
148 size_t real_path_len;
150 CURLcode result = Curl_urldecode(data->state.up.path, 0, &real_path,
151 &real_path_len, REJECT_ZERO);
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
163 Some browsers accept | instead of : as the drive letter
164 separator, so we do too.
166 On other platforms, we need the slash to indicate an
167 absolute pathname. On Windows, absolute paths start
170 actual_path = real_path;
171 if((actual_path[0] == '/') &&
173 (actual_path[2] == ':' || actual_path[2] == '|')) {
174 actual_path[2] = ':';
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;
188 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
189 file->path = actual_path;
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;
197 fd = open_readonly(real_path, O_RDONLY);
198 file->path = real_path;
200 file->freepath = real_path; /* free this when done */
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;
213 static CURLcode file_done(struct Curl_easy *data,
214 CURLcode status, bool premature)
216 struct FILEPROTO *file = data->req.p.file;
217 (void)status; /* not used */
218 (void)premature; /* not used */
221 Curl_safefree(file->freepath);
231 static CURLcode file_disconnect(struct Curl_easy *data,
232 struct connectdata *conn,
233 bool dead_connection)
235 (void)dead_connection; /* not used */
237 return file_done(data, 0, 0);
240 #ifdef DOS_FILESYSTEM
246 static CURLcode file_upload(struct Curl_easy *data)
248 struct FILEPROTO *file = data->req.p.file;
249 const char *dir = strchr(file->path, DIRSEP);
252 CURLcode result = CURLE_OK;
253 char *buf = data->state.buffer;
254 curl_off_t bytecount = 0;
255 struct_stat file_stat;
259 * Since FILE: doesn't do the full init, we need to provide some extra
262 data->req.upload_fromhere = buf;
265 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
268 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
271 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
273 #define MODE_DEFAULT O_WRONLY|O_CREAT
276 if(data->state.resume_from)
277 mode = MODE_DEFAULT|O_APPEND;
279 mode = MODE_DEFAULT|O_TRUNC;
281 fd = open(file->path, mode, data->set.new_file_perms);
283 failf(data, "Can't open %s for writing", file->path);
284 return CURLE_WRITE_ERROR;
287 if(-1 != data->state.infilesize)
288 /* known size of data to "upload" */
289 Curl_pgrsSetUploadSize(data, data->state.infilesize);
291 /* treat the negative resume offset value as the case of "-" */
292 if(data->state.resume_from < 0) {
293 if(fstat(fd, &file_stat)) {
295 failf(data, "Can't get the size of %s", file->path);
296 return CURLE_WRITE_ERROR;
298 data->state.resume_from = (curl_off_t)file_stat.st_size;
305 result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount);
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;
322 buf2 = buf + data->state.resume_from;
323 nread -= (size_t)data->state.resume_from;
324 data->state.resume_from = 0;
330 /* write the data to the target */
331 nwrite = write(fd, buf2, nread);
332 if(nwrite != nread) {
333 result = CURLE_SEND_ERROR;
339 Curl_pgrsSetUploadCounter(data, bytecount);
341 if(Curl_pgrsUpdate(data))
342 result = CURLE_ABORTED_BY_CALLBACK;
344 result = Curl_speedcheck(data, Curl_now());
346 if(!result && Curl_pgrsUpdate(data))
347 result = CURLE_ABORTED_BY_CALLBACK;
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
362 static CURLcode file_do(struct Curl_easy *data, bool *done)
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
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;
375 bool fstated = FALSE;
376 char *buf = data->state.buffer;
377 curl_off_t bytecount = 0;
379 struct FILEPROTO *file;
381 *done = TRUE; /* unconditionally */
383 Curl_pgrsStartNow(data);
386 return file_upload(data);
388 file = data->req.p.file;
390 /* get the fd from the connection phase */
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;
402 if(fstated && !data->state.range && data->set.timecondition) {
403 if(!Curl_meets_timecondition(data, data->info.filetime)) {
412 const struct tm *tm = &buffer;
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",
420 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
424 result = Curl_client_write(data, CLIENTWRITE_HEADER,
425 accept_ranges, strlen(accept_ranges));
426 if(result != CURLE_OK)
430 filetime = (time_t)statbuf.st_mtime;
431 result = Curl_gmtime(filetime, &buffer);
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],
440 Curl_month[tm->tm_mon],
445 data->set.opt_no_body ? "": "\r\n");
446 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
449 /* set the file size to make it available post transfer */
450 Curl_pgrsSetDownloadSize(data, expected_size);
451 if(data->set.opt_no_body)
455 /* Check whether file range has been specified */
456 result = Curl_range(data);
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) {
464 failf(data, "Can't get the size of file.");
465 return CURLE_READ_ERROR;
467 data->state.resume_from += (curl_off_t)statbuf.st_size;
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;
477 failf(data, "failed to resume file:// transfer");
478 return CURLE_BAD_DOWNLOAD_RESUME;
482 /* A high water mark has been specified so we obey... */
483 if(data->req.maxdownload > 0)
484 expected_size = data->req.maxdownload;
486 if(!fstated || (expected_size <= 0))
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
496 Curl_pgrsSetDownloadSize(data, expected_size);
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;
504 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
508 /* Don't fill a whole buffer if we want less than all data */
512 bytestoread = (expected_size < data->set.buffer_size) ?
513 curlx_sotouz(expected_size) : (size_t)data->set.buffer_size;
516 bytestoread = data->set.buffer_size-1;
518 nread = read(fd, buf, bytestoread);
523 if(nread <= 0 || (size_known && (expected_size == 0)))
528 expected_size -= nread;
530 result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread);
534 Curl_pgrsSetDownloadCounter(data, bytecount);
536 if(Curl_pgrsUpdate(data))
537 result = CURLE_ABORTED_BY_CALLBACK;
539 result = Curl_speedcheck(data, Curl_now());
541 if(Curl_pgrsUpdate(data))
542 result = CURLE_ABORTED_BY_CALLBACK;