1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2011, 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 ***************************************************************************/
25 #ifndef CURL_DISABLE_FILE
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
39 #ifdef HAVE_ARPA_INET_H
40 #include <arpa/inet.h>
45 #ifdef HAVE_SYS_IOCTL_H
46 #include <sys/ioctl.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
57 #include "strtoofft.h"
59 #include <curl/curl.h>
64 #include "speedcheck.h"
68 #include "curl_memory.h"
69 #include "parsedate.h" /* for the week day and month names */
72 #define _MPRINTF_REPLACE /* use our functions only */
73 #include <curl/mprintf.h>
75 /* The last #include file should be: */
78 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
79 defined(__SYMBIAN32__)
80 #define DOS_FILESYSTEM 1
83 #ifdef OPEN_NEEDS_ARG3
84 # define open_readonly(p,f) open((p),(f),(0))
86 # define open_readonly(p,f) open((p),(f))
90 * Forward declarations.
93 static CURLcode file_do(struct connectdata *, bool *done);
94 static CURLcode file_done(struct connectdata *conn,
95 CURLcode status, bool premature);
96 static CURLcode file_connect(struct connectdata *conn, bool *done);
97 static CURLcode file_disconnect(struct connectdata *conn,
98 bool dead_connection);
102 * FILE scheme handler.
105 const struct Curl_handler Curl_handler_file = {
107 ZERO_NULL, /* setup_connection */
109 file_done, /* done */
110 ZERO_NULL, /* do_more */
111 file_connect, /* connect_it */
112 ZERO_NULL, /* connecting */
113 ZERO_NULL, /* doing */
114 ZERO_NULL, /* proto_getsock */
115 ZERO_NULL, /* doing_getsock */
116 ZERO_NULL, /* perform_getsock */
117 file_disconnect, /* disconnect */
118 ZERO_NULL, /* readwrite */
120 CURLPROTO_FILE, /* protocol */
121 PROTOPT_NONETWORK /* flags */
126 Check if this is a range download, and if so, set the internal variables
127 properly. This code is copied from the FTP implementation and might as
128 well be factored out.
130 static CURLcode file_range(struct connectdata *conn)
133 curl_off_t totalsize=-1;
136 struct SessionHandle *data = conn->data;
138 if(data->state.use_range && data->state.range) {
139 from=curlx_strtoofft(data->state.range, &ptr, 0);
140 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
142 to=curlx_strtoofft(ptr, &ptr2, 0);
144 /* we didn't get any digit */
147 if((-1 == to) && (from>=0)) {
149 data->state.resume_from = from;
150 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
155 data->req.maxdownload = -from;
156 data->state.resume_from = from;
157 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
163 data->req.maxdownload = totalsize+1; /* include last byte */
164 data->state.resume_from = from;
165 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
166 " getting %" FORMAT_OFF_T " bytes\n",
167 from, data->req.maxdownload));
169 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
170 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
171 from, to, data->req.maxdownload));
174 data->req.maxdownload = -1;
179 * file_connect() gets called from Curl_protocol_connect() to allow us to
180 * do protocol-specific actions at connect-time. We emulate a
181 * connect-then-transfer protocol and "connect" to the file here
183 static CURLcode file_connect(struct connectdata *conn, bool *done)
185 struct SessionHandle *data = conn->data;
186 char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
187 struct FILEPROTO *file;
189 #ifdef DOS_FILESYSTEM
195 return CURLE_OUT_OF_MEMORY;
197 /* If there already is a protocol-specific struct allocated for this
198 sessionhandle, deal with it */
199 Curl_reset_reqproto(conn);
201 if(!data->state.proto.file) {
202 file = calloc(1, sizeof(struct FILEPROTO));
205 return CURLE_OUT_OF_MEMORY;
207 data->state.proto.file = file;
210 /* file is not a protocol that can deal with "persistancy" */
211 file = data->state.proto.file;
212 Curl_safefree(file->freepath);
219 #ifdef DOS_FILESYSTEM
220 /* If the first character is a slash, and there's
221 something that looks like a drive at the beginning of
222 the path, skip the slash. If we remove the initial
223 slash in all cases, paths without drive letters end up
224 relative to the current directory which isn't how
227 Some browsers accept | instead of : as the drive letter
228 separator, so we do too.
230 On other platforms, we need the slash to indicate an
231 absolute pathname. On Windows, absolute paths start
234 actual_path = real_path;
235 if((actual_path[0] == '/') &&
237 (actual_path[2] == ':' || actual_path[2] == '|')) {
238 actual_path[2] = ':';
242 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
243 for(i=0; actual_path[i] != '\0'; ++i)
244 if(actual_path[i] == '/')
245 actual_path[i] = '\\';
247 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
248 file->path = actual_path;
250 fd = open_readonly(real_path, O_RDONLY);
251 file->path = real_path;
253 file->freepath = real_path; /* free this when done */
256 if(!data->set.upload && (fd == -1)) {
257 failf(data, "Couldn't open file %s", data->state.path);
258 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
259 return CURLE_FILE_COULDNT_READ_FILE;
266 static CURLcode file_done(struct connectdata *conn,
267 CURLcode status, bool premature)
269 struct FILEPROTO *file = conn->data->state.proto.file;
270 (void)status; /* not used */
271 (void)premature; /* not used */
274 Curl_safefree(file->freepath);
284 static CURLcode file_disconnect(struct connectdata *conn,
285 bool dead_connection)
287 struct FILEPROTO *file = conn->data->state.proto.file;
288 (void)dead_connection; /* not used */
291 Curl_safefree(file->freepath);
301 #ifdef DOS_FILESYSTEM
307 static CURLcode file_upload(struct connectdata *conn)
309 struct FILEPROTO *file = conn->data->state.proto.file;
310 const char *dir = strchr(file->path, DIRSEP);
312 CURLcode res=CURLE_OK;
313 struct SessionHandle *data = conn->data;
314 char *buf = data->state.buffer;
317 curl_off_t bytecount = 0;
318 struct timeval now = Curl_tvnow();
319 struct_stat file_stat;
323 * Since FILE: doesn't do the full init, we need to provide some extra
326 conn->fread_func = data->set.fread_func;
327 conn->fread_in = data->set.in;
328 conn->data->req.upload_fromhere = buf;
331 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
334 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
336 if(data->state.resume_from)
337 fp = fopen( file->path, "ab" );
341 #ifdef DOS_FILESYSTEM
342 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
343 conn->data->set.new_file_perms);
345 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
346 conn->data->set.new_file_perms);
349 failf(data, "Can't open %s for writing", file->path);
350 return CURLE_WRITE_ERROR;
353 fp = fopen(file->path, "wb");
357 failf(data, "Can't open %s for writing", file->path);
358 return CURLE_WRITE_ERROR;
361 if(-1 != data->set.infilesize)
362 /* known size of data to "upload" */
363 Curl_pgrsSetUploadSize(data, data->set.infilesize);
365 /* treat the negative resume offset value as the case of "-" */
366 if(data->state.resume_from < 0) {
367 if(fstat(fileno(fp), &file_stat)) {
369 failf(data, "Can't get the size of %s", file->path);
370 return CURLE_WRITE_ERROR;
373 data->state.resume_from = (curl_off_t)file_stat.st_size;
376 while(res == CURLE_OK) {
378 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
382 if(readcount <= 0) /* fix questionable compare error. curlvms */
385 nread = (size_t)readcount;
387 /*skip bytes before resume point*/
388 if(data->state.resume_from) {
389 if((curl_off_t)nread <= data->state.resume_from ) {
390 data->state.resume_from -= nread;
395 buf2 = buf + data->state.resume_from;
396 nread -= (size_t)data->state.resume_from;
397 data->state.resume_from = 0;
403 /* write the data to the target */
404 nwrite = fwrite(buf2, 1, nread, fp);
405 if(nwrite != nread) {
406 res = CURLE_SEND_ERROR;
412 Curl_pgrsSetUploadCounter(data, bytecount);
414 if(Curl_pgrsUpdate(conn))
415 res = CURLE_ABORTED_BY_CALLBACK;
417 res = Curl_speedcheck(data, now);
419 if(!res && Curl_pgrsUpdate(conn))
420 res = CURLE_ABORTED_BY_CALLBACK;
428 * file_do() is the protocol-specific function for the do-phase, separated
429 * from the connect-phase above. Other protocols merely setup the transfer in
430 * the do-phase, to have it done in the main transfer loop but since some
431 * platforms we support don't allow select()ing etc on file handles (as
432 * opposed to sockets) we instead perform the whole do-operation in this
435 static CURLcode file_do(struct connectdata *conn, bool *done)
437 /* This implementation ignores the host name in conformance with
438 RFC 1738. Only local files (reachable via the standard file system)
439 are supported. This means that files on remotely mounted directories
440 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
442 CURLcode res = CURLE_OK;
443 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
444 Windows version to have a different struct without
445 having to redefine the simple word 'stat' */
446 curl_off_t expected_size=0;
449 struct SessionHandle *data = conn->data;
450 char *buf = data->state.buffer;
451 curl_off_t bytecount = 0;
453 struct timeval now = Curl_tvnow();
455 *done = TRUE; /* unconditionally */
458 Curl_pgrsStartNow(data);
461 return file_upload(conn);
463 /* get the fd from the connection phase */
464 fd = conn->data->state.proto.file->fd;
466 /* VMS: This only works reliable for STREAMLF files */
467 if(-1 != fstat(fd, &statbuf)) {
468 /* we could stat it, then read out the size */
469 expected_size = statbuf.st_size;
470 /* and store the modification time */
471 data->info.filetime = (long)statbuf.st_mtime;
475 if(fstated && !data->state.range && data->set.timecondition) {
476 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
482 /* If we have selected NOBODY and HEADER, it means that we only want file
483 information. Which for FILE can't be much more than the file size and
485 if(data->set.opt_no_body && data->set.include_header && fstated) {
487 snprintf(buf, sizeof(data->state.buffer),
488 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
489 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
493 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
494 (char *)"Accept-ranges: bytes\r\n", 0);
499 time_t filetime = (time_t)statbuf.st_mtime;
501 const struct tm *tm = &buffer;
502 result = Curl_gmtime(filetime, &buffer);
506 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
507 snprintf(buf, BUFSIZE-1,
508 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
509 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
511 Curl_month[tm->tm_mon],
516 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
518 /* if we fstat()ed the file, set the file size to make it available post-
521 Curl_pgrsSetDownloadSize(data, expected_size);
525 /* Check whether file range has been specified */
528 /* Adjust the start offset in case we want to get the N last bytes
529 * of the stream iff the filesize could be determined */
530 if(data->state.resume_from < 0) {
532 failf(data, "Can't get the size of file.");
533 return CURLE_READ_ERROR;
536 data->state.resume_from += (curl_off_t)statbuf.st_size;
539 if(data->state.resume_from <= expected_size)
540 expected_size -= data->state.resume_from;
542 failf(data, "failed to resume file:// transfer");
543 return CURLE_BAD_DOWNLOAD_RESUME;
546 /* A high water mark has been specified so we obey... */
547 if(data->req.maxdownload > 0)
548 expected_size = data->req.maxdownload;
550 if(fstated && (expected_size == 0))
553 /* The following is a shortcut implementation of file reading
554 this is both more efficient than the former call to download() and
555 it avoids problems with select() and recv() on file descriptors
558 Curl_pgrsSetDownloadSize(data, expected_size);
560 if(data->state.resume_from) {
561 if(data->state.resume_from !=
562 lseek(fd, data->state.resume_from, SEEK_SET))
563 return CURLE_BAD_DOWNLOAD_RESUME;
566 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
568 while(res == CURLE_OK) {
569 /* Don't fill a whole buffer if we want less than all data */
571 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
572 curlx_sotouz(expected_size) : BUFSIZE - 1;
574 nread = read(fd, buf, bytestoread);
579 if(nread <= 0 || expected_size == 0)
583 expected_size -= nread;
585 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
589 Curl_pgrsSetDownloadCounter(data, bytecount);
591 if(Curl_pgrsUpdate(conn))
592 res = CURLE_ABORTED_BY_CALLBACK;
594 res = Curl_speedcheck(data, now);
596 if(Curl_pgrsUpdate(conn))
597 res = CURLE_ABORTED_BY_CALLBACK;