1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2015, 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 http://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 #include "curl_printf.h"
66 /* The last #include files should be: */
67 #include "curl_memory.h"
70 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
71 defined(__SYMBIAN32__)
72 #define DOS_FILESYSTEM 1
75 #ifdef OPEN_NEEDS_ARG3
76 # define open_readonly(p,f) open((p),(f),(0))
78 # define open_readonly(p,f) open((p),(f))
82 * Forward declarations.
85 static CURLcode file_do(struct connectdata *, bool *done);
86 static CURLcode file_done(struct connectdata *conn,
87 CURLcode status, bool premature);
88 static CURLcode file_connect(struct connectdata *conn, bool *done);
89 static CURLcode file_disconnect(struct connectdata *conn,
90 bool dead_connection);
91 static CURLcode file_setup_connection(struct connectdata *conn);
94 * FILE scheme handler.
97 const struct Curl_handler Curl_handler_file = {
99 file_setup_connection, /* setup_connection */
101 file_done, /* done */
102 ZERO_NULL, /* do_more */
103 file_connect, /* connect_it */
104 ZERO_NULL, /* connecting */
105 ZERO_NULL, /* doing */
106 ZERO_NULL, /* proto_getsock */
107 ZERO_NULL, /* doing_getsock */
108 ZERO_NULL, /* domore_getsock */
109 ZERO_NULL, /* perform_getsock */
110 file_disconnect, /* disconnect */
111 ZERO_NULL, /* readwrite */
113 CURLPROTO_FILE, /* protocol */
114 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
118 static CURLcode file_setup_connection(struct connectdata *conn)
120 /* allocate the FILE specific struct */
121 conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
122 if(!conn->data->req.protop)
123 return CURLE_OUT_OF_MEMORY;
129 Check if this is a range download, and if so, set the internal variables
130 properly. This code is copied from the FTP implementation and might as
131 well be factored out.
133 static CURLcode file_range(struct connectdata *conn)
136 curl_off_t totalsize=-1;
139 struct SessionHandle *data = conn->data;
141 if(data->state.use_range && data->state.range) {
142 from=curlx_strtoofft(data->state.range, &ptr, 0);
143 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
145 to=curlx_strtoofft(ptr, &ptr2, 0);
147 /* we didn't get any digit */
150 if((-1 == to) && (from>=0)) {
152 data->state.resume_from = from;
153 DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n",
158 data->req.maxdownload = -from;
159 data->state.resume_from = from;
160 DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n",
166 data->req.maxdownload = totalsize+1; /* include last byte */
167 data->state.resume_from = from;
168 DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T
169 " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n",
170 from, data->req.maxdownload));
172 DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T
173 " to %" CURL_FORMAT_CURL_OFF_T ", totally %"
174 CURL_FORMAT_CURL_OFF_T " bytes\n",
175 from, to, data->req.maxdownload));
178 data->req.maxdownload = -1;
183 * file_connect() gets called from Curl_protocol_connect() to allow us to
184 * do protocol-specific actions at connect-time. We emulate a
185 * connect-then-transfer protocol and "connect" to the file here
187 static CURLcode file_connect(struct connectdata *conn, bool *done)
189 struct SessionHandle *data = conn->data;
191 struct FILEPROTO *file = data->req.protop;
193 #ifdef DOS_FILESYSTEM
199 real_path = curl_easy_unescape(data, data->state.path, 0, &real_path_len);
201 return CURLE_OUT_OF_MEMORY;
203 #ifdef DOS_FILESYSTEM
204 /* If the first character is a slash, and there's
205 something that looks like a drive at the beginning of
206 the path, skip the slash. If we remove the initial
207 slash in all cases, paths without drive letters end up
208 relative to the current directory which isn't how
211 Some browsers accept | instead of : as the drive letter
212 separator, so we do too.
214 On other platforms, we need the slash to indicate an
215 absolute pathname. On Windows, absolute paths start
218 actual_path = real_path;
219 if((actual_path[0] == '/') &&
221 (actual_path[2] == ':' || actual_path[2] == '|')) {
222 actual_path[2] = ':';
227 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
228 for(i=0; i < real_path_len; ++i)
229 if(actual_path[i] == '/')
230 actual_path[i] = '\\';
231 else if(!actual_path[i]) /* binary zero */
232 return CURLE_URL_MALFORMAT;
234 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
235 file->path = actual_path;
237 if(memchr(real_path, 0, real_path_len))
238 /* binary zeroes indicate foul play */
239 return CURLE_URL_MALFORMAT;
241 fd = open_readonly(real_path, O_RDONLY);
242 file->path = real_path;
244 file->freepath = real_path; /* free this when done */
247 if(!data->set.upload && (fd == -1)) {
248 failf(data, "Couldn't open file %s", data->state.path);
249 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
250 return CURLE_FILE_COULDNT_READ_FILE;
257 static CURLcode file_done(struct connectdata *conn,
258 CURLcode status, bool premature)
260 struct FILEPROTO *file = conn->data->req.protop;
261 (void)status; /* not used */
262 (void)premature; /* not used */
265 Curl_safefree(file->freepath);
275 static CURLcode file_disconnect(struct connectdata *conn,
276 bool dead_connection)
278 struct FILEPROTO *file = conn->data->req.protop;
279 (void)dead_connection; /* not used */
282 Curl_safefree(file->freepath);
292 #ifdef DOS_FILESYSTEM
298 static CURLcode file_upload(struct connectdata *conn)
300 struct FILEPROTO *file = conn->data->req.protop;
301 const char *dir = strchr(file->path, DIRSEP);
304 CURLcode result = CURLE_OK;
305 struct SessionHandle *data = conn->data;
306 char *buf = data->state.buffer;
309 curl_off_t bytecount = 0;
310 struct timeval now = Curl_tvnow();
311 struct_stat file_stat;
315 * Since FILE: doesn't do the full init, we need to provide some extra
318 conn->data->req.upload_fromhere = buf;
321 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
324 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
327 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
329 #define MODE_DEFAULT O_WRONLY|O_CREAT
332 if(data->state.resume_from)
333 mode = MODE_DEFAULT|O_APPEND;
335 mode = MODE_DEFAULT|O_TRUNC;
337 fd = open(file->path, mode, conn->data->set.new_file_perms);
339 failf(data, "Can't open %s for writing", file->path);
340 return CURLE_WRITE_ERROR;
343 if(-1 != data->state.infilesize)
344 /* known size of data to "upload" */
345 Curl_pgrsSetUploadSize(data, data->state.infilesize);
347 /* treat the negative resume offset value as the case of "-" */
348 if(data->state.resume_from < 0) {
349 if(fstat(fd, &file_stat)) {
351 failf(data, "Can't get the size of %s", file->path);
352 return CURLE_WRITE_ERROR;
355 data->state.resume_from = (curl_off_t)file_stat.st_size;
360 result = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
364 if(readcount <= 0) /* fix questionable compare error. curlvms */
367 nread = (size_t)readcount;
369 /*skip bytes before resume point*/
370 if(data->state.resume_from) {
371 if((curl_off_t)nread <= data->state.resume_from ) {
372 data->state.resume_from -= nread;
377 buf2 = buf + data->state.resume_from;
378 nread -= (size_t)data->state.resume_from;
379 data->state.resume_from = 0;
385 /* write the data to the target */
386 nwrite = write(fd, buf2, nread);
387 if(nwrite != nread) {
388 result = CURLE_SEND_ERROR;
394 Curl_pgrsSetUploadCounter(data, bytecount);
396 if(Curl_pgrsUpdate(conn))
397 result = CURLE_ABORTED_BY_CALLBACK;
399 result = Curl_speedcheck(data, now);
401 if(!result && Curl_pgrsUpdate(conn))
402 result = CURLE_ABORTED_BY_CALLBACK;
410 * file_do() is the protocol-specific function for the do-phase, separated
411 * from the connect-phase above. Other protocols merely setup the transfer in
412 * the do-phase, to have it done in the main transfer loop but since some
413 * platforms we support don't allow select()ing etc on file handles (as
414 * opposed to sockets) we instead perform the whole do-operation in this
417 static CURLcode file_do(struct connectdata *conn, bool *done)
419 /* This implementation ignores the host name in conformance with
420 RFC 1738. Only local files (reachable via the standard file system)
421 are supported. This means that files on remotely mounted directories
422 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
424 CURLcode result = CURLE_OK;
425 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
426 Windows version to have a different struct without
427 having to redefine the simple word 'stat' */
428 curl_off_t expected_size=0;
431 struct SessionHandle *data = conn->data;
432 char *buf = data->state.buffer;
433 curl_off_t bytecount = 0;
435 struct timeval now = Curl_tvnow();
436 struct FILEPROTO *file;
438 *done = TRUE; /* unconditionally */
441 Curl_pgrsStartNow(data);
444 return file_upload(conn);
446 file = conn->data->req.protop;
448 /* get the fd from the connection phase */
451 /* VMS: This only works reliable for STREAMLF files */
452 if(-1 != fstat(fd, &statbuf)) {
453 /* we could stat it, then read out the size */
454 expected_size = statbuf.st_size;
455 /* and store the modification time */
456 data->info.filetime = (long)statbuf.st_mtime;
460 if(fstated && !data->state.range && data->set.timecondition) {
461 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
467 /* If we have selected NOBODY and HEADER, it means that we only want file
468 information. Which for FILE can't be much more than the file size and
470 if(data->set.opt_no_body && data->set.include_header && fstated) {
471 snprintf(buf, sizeof(data->state.buffer),
472 "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
473 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
477 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
478 (char *)"Accept-ranges: bytes\r\n", 0);
483 time_t filetime = (time_t)statbuf.st_mtime;
485 const struct tm *tm = &buffer;
486 result = Curl_gmtime(filetime, &buffer);
490 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
491 snprintf(buf, BUFSIZE-1,
492 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
493 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
495 Curl_month[tm->tm_mon],
500 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
502 /* if we fstat()ed the file, set the file size to make it available post-
505 Curl_pgrsSetDownloadSize(data, expected_size);
509 /* Check whether file range has been specified */
512 /* Adjust the start offset in case we want to get the N last bytes
513 * of the stream iff the filesize could be determined */
514 if(data->state.resume_from < 0) {
516 failf(data, "Can't get the size of file.");
517 return CURLE_READ_ERROR;
520 data->state.resume_from += (curl_off_t)statbuf.st_size;
523 if(data->state.resume_from <= expected_size)
524 expected_size -= data->state.resume_from;
526 failf(data, "failed to resume file:// transfer");
527 return CURLE_BAD_DOWNLOAD_RESUME;
530 /* A high water mark has been specified so we obey... */
531 if(data->req.maxdownload > 0)
532 expected_size = data->req.maxdownload;
534 if(fstated && (expected_size == 0))
537 /* The following is a shortcut implementation of file reading
538 this is both more efficient than the former call to download() and
539 it avoids problems with select() and recv() on file descriptors
542 Curl_pgrsSetDownloadSize(data, expected_size);
544 if(data->state.resume_from) {
545 if(data->state.resume_from !=
546 lseek(fd, data->state.resume_from, SEEK_SET))
547 return CURLE_BAD_DOWNLOAD_RESUME;
550 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
553 /* Don't fill a whole buffer if we want less than all data */
555 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
556 curlx_sotouz(expected_size) : BUFSIZE - 1;
558 nread = read(fd, buf, bytestoread);
563 if(nread <= 0 || expected_size == 0)
567 expected_size -= nread;
569 result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
573 Curl_pgrsSetDownloadCounter(data, bytecount);
575 if(Curl_pgrsUpdate(conn))
576 result = CURLE_ABORTED_BY_CALLBACK;
578 result = Curl_speedcheck(data, now);
580 if(Curl_pgrsUpdate(conn))
581 result = CURLE_ABORTED_BY_CALLBACK;