1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2014, 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 "curl_memory.h"
63 #include "parsedate.h" /* for the week day and month names */
66 #define _MPRINTF_REPLACE /* use our functions only */
67 #include <curl/mprintf.h>
69 /* The last #include file should be: */
72 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
73 defined(__SYMBIAN32__)
74 #define DOS_FILESYSTEM 1
77 #ifdef OPEN_NEEDS_ARG3
78 # define open_readonly(p,f) open((p),(f),(0))
80 # define open_readonly(p,f) open((p),(f))
84 * Forward declarations.
87 static CURLcode file_do(struct connectdata *, bool *done);
88 static CURLcode file_done(struct connectdata *conn,
89 CURLcode status, bool premature);
90 static CURLcode file_connect(struct connectdata *conn, bool *done);
91 static CURLcode file_disconnect(struct connectdata *conn,
92 bool dead_connection);
93 static CURLcode file_setup_connection(struct connectdata *conn);
96 * FILE scheme handler.
99 const struct Curl_handler Curl_handler_file = {
101 file_setup_connection, /* setup_connection */
103 file_done, /* done */
104 ZERO_NULL, /* do_more */
105 file_connect, /* connect_it */
106 ZERO_NULL, /* connecting */
107 ZERO_NULL, /* doing */
108 ZERO_NULL, /* proto_getsock */
109 ZERO_NULL, /* doing_getsock */
110 ZERO_NULL, /* domore_getsock */
111 ZERO_NULL, /* perform_getsock */
112 file_disconnect, /* disconnect */
113 ZERO_NULL, /* readwrite */
115 CURLPROTO_FILE, /* protocol */
116 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
120 static CURLcode file_setup_connection(struct connectdata *conn)
122 /* allocate the FILE specific struct */
123 conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
124 if(!conn->data->req.protop)
125 return CURLE_OUT_OF_MEMORY;
131 Check if this is a range download, and if so, set the internal variables
132 properly. This code is copied from the FTP implementation and might as
133 well be factored out.
135 static CURLcode file_range(struct connectdata *conn)
138 curl_off_t totalsize=-1;
141 struct SessionHandle *data = conn->data;
143 if(data->state.use_range && data->state.range) {
144 from=curlx_strtoofft(data->state.range, &ptr, 0);
145 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
147 to=curlx_strtoofft(ptr, &ptr2, 0);
149 /* we didn't get any digit */
152 if((-1 == to) && (from>=0)) {
154 data->state.resume_from = from;
155 DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n",
160 data->req.maxdownload = -from;
161 data->state.resume_from = from;
162 DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n",
168 data->req.maxdownload = totalsize+1; /* include last byte */
169 data->state.resume_from = from;
170 DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T
171 " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n",
172 from, data->req.maxdownload));
174 DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T
175 " to %" CURL_FORMAT_CURL_OFF_T ", totally %"
176 CURL_FORMAT_CURL_OFF_T " bytes\n",
177 from, to, data->req.maxdownload));
180 data->req.maxdownload = -1;
185 * file_connect() gets called from Curl_protocol_connect() to allow us to
186 * do protocol-specific actions at connect-time. We emulate a
187 * connect-then-transfer protocol and "connect" to the file here
189 static CURLcode file_connect(struct connectdata *conn, bool *done)
191 struct SessionHandle *data = conn->data;
193 struct FILEPROTO *file = data->req.protop;
195 #ifdef DOS_FILESYSTEM
200 real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
202 return CURLE_OUT_OF_MEMORY;
204 #ifdef DOS_FILESYSTEM
205 /* If the first character is a slash, and there's
206 something that looks like a drive at the beginning of
207 the path, skip the slash. If we remove the initial
208 slash in all cases, paths without drive letters end up
209 relative to the current directory which isn't how
212 Some browsers accept | instead of : as the drive letter
213 separator, so we do too.
215 On other platforms, we need the slash to indicate an
216 absolute pathname. On Windows, absolute paths start
219 actual_path = real_path;
220 if((actual_path[0] == '/') &&
222 (actual_path[2] == ':' || actual_path[2] == '|')) {
223 actual_path[2] = ':';
227 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
228 for(i=0; actual_path[i] != '\0'; ++i)
229 if(actual_path[i] == '/')
230 actual_path[i] = '\\';
232 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
233 file->path = actual_path;
235 fd = open_readonly(real_path, O_RDONLY);
236 file->path = real_path;
238 file->freepath = real_path; /* free this when done */
241 if(!data->set.upload && (fd == -1)) {
242 failf(data, "Couldn't open file %s", data->state.path);
243 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
244 return CURLE_FILE_COULDNT_READ_FILE;
251 static CURLcode file_done(struct connectdata *conn,
252 CURLcode status, bool premature)
254 struct FILEPROTO *file = conn->data->req.protop;
255 (void)status; /* not used */
256 (void)premature; /* not used */
259 Curl_safefree(file->freepath);
269 static CURLcode file_disconnect(struct connectdata *conn,
270 bool dead_connection)
272 struct FILEPROTO *file = conn->data->req.protop;
273 (void)dead_connection; /* not used */
276 Curl_safefree(file->freepath);
286 #ifdef DOS_FILESYSTEM
292 static CURLcode file_upload(struct connectdata *conn)
294 struct FILEPROTO *file = conn->data->req.protop;
295 const char *dir = strchr(file->path, DIRSEP);
298 CURLcode res=CURLE_OK;
299 struct SessionHandle *data = conn->data;
300 char *buf = data->state.buffer;
303 curl_off_t bytecount = 0;
304 struct timeval now = Curl_tvnow();
305 struct_stat file_stat;
309 * Since FILE: doesn't do the full init, we need to provide some extra
312 conn->fread_func = data->set.fread_func;
313 conn->fread_in = data->set.in;
314 conn->data->req.upload_fromhere = buf;
317 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
320 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
323 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
325 #define MODE_DEFAULT O_WRONLY|O_CREAT
328 if(data->state.resume_from)
329 mode = MODE_DEFAULT|O_APPEND;
331 mode = MODE_DEFAULT|O_TRUNC;
333 fd = open(file->path, mode, conn->data->set.new_file_perms);
335 failf(data, "Can't open %s for writing", file->path);
336 return CURLE_WRITE_ERROR;
339 if(-1 != data->state.infilesize)
340 /* known size of data to "upload" */
341 Curl_pgrsSetUploadSize(data, data->state.infilesize);
343 /* treat the negative resume offset value as the case of "-" */
344 if(data->state.resume_from < 0) {
345 if(fstat(fd, &file_stat)) {
347 failf(data, "Can't get the size of %s", file->path);
348 return CURLE_WRITE_ERROR;
351 data->state.resume_from = (curl_off_t)file_stat.st_size;
354 while(res == CURLE_OK) {
356 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
360 if(readcount <= 0) /* fix questionable compare error. curlvms */
363 nread = (size_t)readcount;
365 /*skip bytes before resume point*/
366 if(data->state.resume_from) {
367 if((curl_off_t)nread <= data->state.resume_from ) {
368 data->state.resume_from -= nread;
373 buf2 = buf + data->state.resume_from;
374 nread -= (size_t)data->state.resume_from;
375 data->state.resume_from = 0;
381 /* write the data to the target */
382 nwrite = write(fd, buf2, nread);
383 if(nwrite != nread) {
384 res = CURLE_SEND_ERROR;
390 Curl_pgrsSetUploadCounter(data, bytecount);
392 if(Curl_pgrsUpdate(conn))
393 res = CURLE_ABORTED_BY_CALLBACK;
395 res = Curl_speedcheck(data, now);
397 if(!res && Curl_pgrsUpdate(conn))
398 res = CURLE_ABORTED_BY_CALLBACK;
406 * file_do() is the protocol-specific function for the do-phase, separated
407 * from the connect-phase above. Other protocols merely setup the transfer in
408 * the do-phase, to have it done in the main transfer loop but since some
409 * platforms we support don't allow select()ing etc on file handles (as
410 * opposed to sockets) we instead perform the whole do-operation in this
413 static CURLcode file_do(struct connectdata *conn, bool *done)
415 /* This implementation ignores the host name in conformance with
416 RFC 1738. Only local files (reachable via the standard file system)
417 are supported. This means that files on remotely mounted directories
418 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
420 CURLcode res = CURLE_OK;
421 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
422 Windows version to have a different struct without
423 having to redefine the simple word 'stat' */
424 curl_off_t expected_size=0;
427 struct SessionHandle *data = conn->data;
428 char *buf = data->state.buffer;
429 curl_off_t bytecount = 0;
431 struct timeval now = Curl_tvnow();
432 struct FILEPROTO *file;
434 *done = TRUE; /* unconditionally */
437 Curl_pgrsStartNow(data);
440 return file_upload(conn);
442 file = conn->data->req.protop;
444 /* get the fd from the connection phase */
447 /* VMS: This only works reliable for STREAMLF files */
448 if(-1 != fstat(fd, &statbuf)) {
449 /* we could stat it, then read out the size */
450 expected_size = statbuf.st_size;
451 /* and store the modification time */
452 data->info.filetime = (long)statbuf.st_mtime;
456 if(fstated && !data->state.range && data->set.timecondition) {
457 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
463 /* If we have selected NOBODY and HEADER, it means that we only want file
464 information. Which for FILE can't be much more than the file size and
466 if(data->set.opt_no_body && data->set.include_header && fstated) {
468 snprintf(buf, sizeof(data->state.buffer),
469 "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
470 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
474 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
475 (char *)"Accept-ranges: bytes\r\n", 0);
480 time_t filetime = (time_t)statbuf.st_mtime;
482 const struct tm *tm = &buffer;
483 result = Curl_gmtime(filetime, &buffer);
487 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
488 snprintf(buf, BUFSIZE-1,
489 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
490 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
492 Curl_month[tm->tm_mon],
497 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
499 /* if we fstat()ed the file, set the file size to make it available post-
502 Curl_pgrsSetDownloadSize(data, expected_size);
506 /* Check whether file range has been specified */
509 /* Adjust the start offset in case we want to get the N last bytes
510 * of the stream iff the filesize could be determined */
511 if(data->state.resume_from < 0) {
513 failf(data, "Can't get the size of file.");
514 return CURLE_READ_ERROR;
517 data->state.resume_from += (curl_off_t)statbuf.st_size;
520 if(data->state.resume_from <= expected_size)
521 expected_size -= data->state.resume_from;
523 failf(data, "failed to resume file:// transfer");
524 return CURLE_BAD_DOWNLOAD_RESUME;
527 /* A high water mark has been specified so we obey... */
528 if(data->req.maxdownload > 0)
529 expected_size = data->req.maxdownload;
531 if(fstated && (expected_size == 0))
534 /* The following is a shortcut implementation of file reading
535 this is both more efficient than the former call to download() and
536 it avoids problems with select() and recv() on file descriptors
539 Curl_pgrsSetDownloadSize(data, expected_size);
541 if(data->state.resume_from) {
542 if(data->state.resume_from !=
543 lseek(fd, data->state.resume_from, SEEK_SET))
544 return CURLE_BAD_DOWNLOAD_RESUME;
547 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
549 while(res == CURLE_OK) {
550 /* Don't fill a whole buffer if we want less than all data */
552 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
553 curlx_sotouz(expected_size) : BUFSIZE - 1;
555 nread = read(fd, buf, bytestoread);
560 if(nread <= 0 || expected_size == 0)
564 expected_size -= nread;
566 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
570 Curl_pgrsSetDownloadCounter(data, bytecount);
572 if(Curl_pgrsUpdate(conn))
573 res = CURLE_ABORTED_BY_CALLBACK;
575 res = Curl_speedcheck(data, now);
577 if(Curl_pgrsUpdate(conn))
578 res = CURLE_ABORTED_BY_CALLBACK;