1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2016, 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.haxx.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 /* The last 3 #include files should be in this order */
65 #include "curl_printf.h"
66 #include "curl_memory.h"
69 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
70 defined(__SYMBIAN32__)
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 connectdata *, bool *done);
85 static CURLcode file_done(struct connectdata *conn,
86 CURLcode status, bool premature);
87 static CURLcode file_connect(struct connectdata *conn, bool *done);
88 static CURLcode file_disconnect(struct connectdata *conn,
89 bool dead_connection);
90 static CURLcode file_setup_connection(struct connectdata *conn);
93 * FILE scheme handler.
96 const struct Curl_handler Curl_handler_file = {
98 file_setup_connection, /* setup_connection */
100 file_done, /* done */
101 ZERO_NULL, /* do_more */
102 file_connect, /* connect_it */
103 ZERO_NULL, /* connecting */
104 ZERO_NULL, /* doing */
105 ZERO_NULL, /* proto_getsock */
106 ZERO_NULL, /* doing_getsock */
107 ZERO_NULL, /* domore_getsock */
108 ZERO_NULL, /* perform_getsock */
109 file_disconnect, /* disconnect */
110 ZERO_NULL, /* readwrite */
112 CURLPROTO_FILE, /* protocol */
113 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
117 static CURLcode file_setup_connection(struct connectdata *conn)
119 /* allocate the FILE specific struct */
120 conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
121 if(!conn->data->req.protop)
122 return CURLE_OUT_OF_MEMORY;
128 Check if this is a range download, and if so, set the internal variables
129 properly. This code is copied from the FTP implementation and might as
130 well be factored out.
132 static CURLcode file_range(struct connectdata *conn)
135 curl_off_t totalsize=-1;
138 struct Curl_easy *data = conn->data;
140 if(data->state.use_range && data->state.range) {
141 from=curlx_strtoofft(data->state.range, &ptr, 0);
142 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
144 to=curlx_strtoofft(ptr, &ptr2, 0);
146 /* we didn't get any digit */
149 if((-1 == to) && (from>=0)) {
151 data->state.resume_from = from;
152 DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n",
157 data->req.maxdownload = -from;
158 data->state.resume_from = from;
159 DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n",
165 data->req.maxdownload = totalsize+1; /* include last byte */
166 data->state.resume_from = from;
167 DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T
168 " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n",
169 from, data->req.maxdownload));
171 DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T
172 " to %" CURL_FORMAT_CURL_OFF_T ", totally %"
173 CURL_FORMAT_CURL_OFF_T " bytes\n",
174 from, to, data->req.maxdownload));
177 data->req.maxdownload = -1;
182 * file_connect() gets called from Curl_protocol_connect() to allow us to
183 * do protocol-specific actions at connect-time. We emulate a
184 * connect-then-transfer protocol and "connect" to the file here
186 static CURLcode file_connect(struct connectdata *conn, bool *done)
188 struct Curl_easy *data = conn->data;
190 struct FILEPROTO *file = data->req.protop;
192 #ifdef DOS_FILESYSTEM
198 real_path = curl_easy_unescape(data, data->state.path, 0, &real_path_len);
200 return CURLE_OUT_OF_MEMORY;
202 #ifdef DOS_FILESYSTEM
203 /* If the first character is a slash, and there's
204 something that looks like a drive at the beginning of
205 the path, skip the slash. If we remove the initial
206 slash in all cases, paths without drive letters end up
207 relative to the current directory which isn't how
210 Some browsers accept | instead of : as the drive letter
211 separator, so we do too.
213 On other platforms, we need the slash to indicate an
214 absolute pathname. On Windows, absolute paths start
217 actual_path = real_path;
218 if((actual_path[0] == '/') &&
220 (actual_path[2] == ':' || actual_path[2] == '|')) {
221 actual_path[2] = ':';
226 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
227 for(i=0; i < real_path_len; ++i)
228 if(actual_path[i] == '/')
229 actual_path[i] = '\\';
230 else if(!actual_path[i]) { /* binary zero */
231 Curl_safefree(real_path);
232 return CURLE_URL_MALFORMAT;
235 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
236 file->path = actual_path;
238 if(memchr(real_path, 0, real_path_len)) {
239 /* binary zeroes indicate foul play */
240 Curl_safefree(real_path);
241 return CURLE_URL_MALFORMAT;
244 fd = open_readonly(real_path, O_RDONLY);
245 file->path = real_path;
247 file->freepath = real_path; /* free this when done */
250 if(!data->set.upload && (fd == -1)) {
251 failf(data, "Couldn't open file %s", data->state.path);
252 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
253 return CURLE_FILE_COULDNT_READ_FILE;
260 static CURLcode file_done(struct connectdata *conn,
261 CURLcode status, bool premature)
263 struct FILEPROTO *file = conn->data->req.protop;
264 (void)status; /* not used */
265 (void)premature; /* not used */
268 Curl_safefree(file->freepath);
278 static CURLcode file_disconnect(struct connectdata *conn,
279 bool dead_connection)
281 struct FILEPROTO *file = conn->data->req.protop;
282 (void)dead_connection; /* not used */
285 Curl_safefree(file->freepath);
295 #ifdef DOS_FILESYSTEM
301 static CURLcode file_upload(struct connectdata *conn)
303 struct FILEPROTO *file = conn->data->req.protop;
304 const char *dir = strchr(file->path, DIRSEP);
307 CURLcode result = CURLE_OK;
308 struct Curl_easy *data = conn->data;
309 char *buf = data->state.buffer;
312 curl_off_t bytecount = 0;
313 struct timeval now = Curl_tvnow();
314 struct_stat file_stat;
318 * Since FILE: doesn't do the full init, we need to provide some extra
321 conn->data->req.upload_fromhere = buf;
324 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
327 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
330 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
332 #define MODE_DEFAULT O_WRONLY|O_CREAT
335 if(data->state.resume_from)
336 mode = MODE_DEFAULT|O_APPEND;
338 mode = MODE_DEFAULT|O_TRUNC;
340 fd = open(file->path, mode, conn->data->set.new_file_perms);
342 failf(data, "Can't open %s for writing", file->path);
343 return CURLE_WRITE_ERROR;
346 if(-1 != data->state.infilesize)
347 /* known size of data to "upload" */
348 Curl_pgrsSetUploadSize(data, data->state.infilesize);
350 /* treat the negative resume offset value as the case of "-" */
351 if(data->state.resume_from < 0) {
352 if(fstat(fd, &file_stat)) {
354 failf(data, "Can't get the size of %s", file->path);
355 return CURLE_WRITE_ERROR;
358 data->state.resume_from = (curl_off_t)file_stat.st_size;
363 result = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
367 if(readcount <= 0) /* fix questionable compare error. curlvms */
370 nread = (size_t)readcount;
372 /*skip bytes before resume point*/
373 if(data->state.resume_from) {
374 if((curl_off_t)nread <= data->state.resume_from) {
375 data->state.resume_from -= nread;
380 buf2 = buf + data->state.resume_from;
381 nread -= (size_t)data->state.resume_from;
382 data->state.resume_from = 0;
388 /* write the data to the target */
389 nwrite = write(fd, buf2, nread);
390 if(nwrite != nread) {
391 result = CURLE_SEND_ERROR;
397 Curl_pgrsSetUploadCounter(data, bytecount);
399 if(Curl_pgrsUpdate(conn))
400 result = CURLE_ABORTED_BY_CALLBACK;
402 result = Curl_speedcheck(data, now);
404 if(!result && Curl_pgrsUpdate(conn))
405 result = CURLE_ABORTED_BY_CALLBACK;
413 * file_do() is the protocol-specific function for the do-phase, separated
414 * from the connect-phase above. Other protocols merely setup the transfer in
415 * the do-phase, to have it done in the main transfer loop but since some
416 * platforms we support don't allow select()ing etc on file handles (as
417 * opposed to sockets) we instead perform the whole do-operation in this
420 static CURLcode file_do(struct connectdata *conn, bool *done)
422 /* This implementation ignores the host name in conformance with
423 RFC 1738. Only local files (reachable via the standard file system)
424 are supported. This means that files on remotely mounted directories
425 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
427 CURLcode result = CURLE_OK;
428 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
429 Windows version to have a different struct without
430 having to redefine the simple word 'stat' */
431 curl_off_t expected_size=0;
435 struct Curl_easy *data = conn->data;
436 char *buf = data->state.buffer;
437 curl_off_t bytecount = 0;
439 struct timeval now = Curl_tvnow();
440 struct FILEPROTO *file;
442 *done = TRUE; /* unconditionally */
445 Curl_pgrsStartNow(data);
448 return file_upload(conn);
450 file = conn->data->req.protop;
452 /* get the fd from the connection phase */
455 /* VMS: This only works reliable for STREAMLF files */
456 if(-1 != fstat(fd, &statbuf)) {
457 /* we could stat it, then read out the size */
458 expected_size = statbuf.st_size;
459 /* and store the modification time */
460 data->info.filetime = (long)statbuf.st_mtime;
464 if(fstated && !data->state.range && data->set.timecondition) {
465 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
471 /* If we have selected NOBODY and HEADER, it means that we only want file
472 information. Which for FILE can't be much more than the file size and
474 if(data->set.opt_no_body && data->set.include_header && fstated) {
477 const struct tm *tm = &buffer;
478 snprintf(buf, sizeof(data->state.buffer),
479 "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
480 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
484 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
485 (char *)"Accept-ranges: bytes\r\n", 0);
489 filetime = (time_t)statbuf.st_mtime;
490 result = Curl_gmtime(filetime, &buffer);
494 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
495 snprintf(buf, BUFSIZE-1,
496 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
497 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
499 Curl_month[tm->tm_mon],
504 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
506 /* set the file size to make it available post transfer */
507 Curl_pgrsSetDownloadSize(data, expected_size);
511 /* Check whether file range has been specified */
514 /* Adjust the start offset in case we want to get the N last bytes
515 * of the stream iff the filesize could be determined */
516 if(data->state.resume_from < 0) {
518 failf(data, "Can't get the size of file.");
519 return CURLE_READ_ERROR;
522 data->state.resume_from += (curl_off_t)statbuf.st_size;
525 if(data->state.resume_from <= expected_size)
526 expected_size -= data->state.resume_from;
528 failf(data, "failed to resume file:// transfer");
529 return CURLE_BAD_DOWNLOAD_RESUME;
532 /* A high water mark has been specified so we obey... */
533 if(data->req.maxdownload > 0)
534 expected_size = data->req.maxdownload;
536 if(!fstated || (expected_size == 0))
541 /* The following is a shortcut implementation of file reading
542 this is both more efficient than the former call to download() and
543 it avoids problems with select() and recv() on file descriptors
546 Curl_pgrsSetDownloadSize(data, expected_size);
548 if(data->state.resume_from) {
549 if(data->state.resume_from !=
550 lseek(fd, data->state.resume_from, SEEK_SET))
551 return CURLE_BAD_DOWNLOAD_RESUME;
554 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
557 /* Don't fill a whole buffer if we want less than all data */
562 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
563 curlx_sotouz(expected_size) : BUFSIZE - 1;
566 bytestoread = BUFSIZE-1;
568 nread = read(fd, buf, bytestoread);
573 if(nread <= 0 || (size_known && (expected_size == 0)))
578 expected_size -= nread;
580 result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
584 Curl_pgrsSetDownloadCounter(data, bytecount);
586 if(Curl_pgrsUpdate(conn))
587 result = CURLE_ABORTED_BY_CALLBACK;
589 result = Curl_speedcheck(data, now);
591 if(Curl_pgrsUpdate(conn))
592 result = CURLE_ABORTED_BY_CALLBACK;