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 */
71 #define _MPRINTF_REPLACE /* use our functions only */
72 #include <curl/mprintf.h>
74 /* The last #include file should be: */
77 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
78 defined(__SYMBIAN32__)
79 #define DOS_FILESYSTEM 1
82 #ifdef OPEN_NEEDS_ARG3
83 # define open_readonly(p,f) open((p),(f),(0))
85 # define open_readonly(p,f) open((p),(f))
89 * Forward declarations.
92 static CURLcode file_do(struct connectdata *, bool *done);
93 static CURLcode file_done(struct connectdata *conn,
94 CURLcode status, bool premature);
95 static CURLcode file_connect(struct connectdata *conn, bool *done);
98 * FILE scheme handler.
101 const struct Curl_handler Curl_handler_file = {
103 ZERO_NULL, /* setup_connection */
105 file_done, /* done */
106 ZERO_NULL, /* do_more */
107 file_connect, /* connect_it */
108 ZERO_NULL, /* connecting */
109 ZERO_NULL, /* doing */
110 ZERO_NULL, /* proto_getsock */
111 ZERO_NULL, /* doing_getsock */
112 ZERO_NULL, /* perform_getsock */
113 ZERO_NULL, /* disconnect */
114 ZERO_NULL, /* readwrite */
116 CURLPROTO_FILE, /* protocol */
117 PROTOPT_NONETWORK /* flags */
122 Check if this is a range download, and if so, set the internal variables
123 properly. This code is copied from the FTP implementation and might as
124 well be factored out.
126 static CURLcode file_range(struct connectdata *conn)
129 curl_off_t totalsize=-1;
132 struct SessionHandle *data = conn->data;
134 if(data->state.use_range && data->state.range) {
135 from=curlx_strtoofft(data->state.range, &ptr, 0);
136 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
138 to=curlx_strtoofft(ptr, &ptr2, 0);
140 /* we didn't get any digit */
143 if((-1 == to) && (from>=0)) {
145 data->state.resume_from = from;
146 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
151 data->req.maxdownload = -from;
152 data->state.resume_from = from;
153 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
159 data->req.maxdownload = totalsize+1; /* include last byte */
160 data->state.resume_from = from;
161 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
162 " getting %" FORMAT_OFF_T " bytes\n",
163 from, data->req.maxdownload));
165 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
166 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
167 from, to, data->req.maxdownload));
170 data->req.maxdownload = -1;
175 * file_connect() gets called from Curl_protocol_connect() to allow us to
176 * do protocol-specific actions at connect-time. We emulate a
177 * connect-then-transfer protocol and "connect" to the file here
179 static CURLcode file_connect(struct connectdata *conn, bool *done)
181 struct SessionHandle *data = conn->data;
182 char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
183 struct FILEPROTO *file;
185 #ifdef DOS_FILESYSTEM
191 return CURLE_OUT_OF_MEMORY;
193 /* If there already is a protocol-specific struct allocated for this
194 sessionhandle, deal with it */
195 Curl_reset_reqproto(conn);
197 if(!data->state.proto.file) {
198 file = calloc(1, sizeof(struct FILEPROTO));
201 return CURLE_OUT_OF_MEMORY;
203 data->state.proto.file = file;
206 /* file is not a protocol that can deal with "persistancy" */
207 file = data->state.proto.file;
208 Curl_safefree(file->freepath);
212 file->freepath = NULL;
216 #ifdef DOS_FILESYSTEM
217 /* If the first character is a slash, and there's
218 something that looks like a drive at the beginning of
219 the path, skip the slash. If we remove the initial
220 slash in all cases, paths without drive letters end up
221 relative to the current directory which isn't how
224 Some browsers accept | instead of : as the drive letter
225 separator, so we do too.
227 On other platforms, we need the slash to indicate an
228 absolute pathname. On Windows, absolute paths start
231 actual_path = real_path;
232 if((actual_path[0] == '/') &&
234 (actual_path[2] == ':' || actual_path[2] == '|')) {
235 actual_path[2] = ':';
239 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
240 for(i=0; actual_path[i] != '\0'; ++i)
241 if(actual_path[i] == '/')
242 actual_path[i] = '\\';
244 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
245 file->path = actual_path;
247 fd = open_readonly(real_path, O_RDONLY);
248 file->path = real_path;
250 file->freepath = real_path; /* free this when done */
253 if(!data->set.upload && (fd == -1)) {
254 failf(data, "Couldn't open file %s", data->state.path);
255 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
256 return CURLE_FILE_COULDNT_READ_FILE;
263 static CURLcode file_done(struct connectdata *conn,
264 CURLcode status, bool premature)
266 struct FILEPROTO *file = conn->data->state.proto.file;
267 (void)status; /* not used */
268 (void)premature; /* not used */
269 Curl_safefree(file->freepath);
277 #ifdef DOS_FILESYSTEM
283 static CURLcode file_upload(struct connectdata *conn)
285 struct FILEPROTO *file = conn->data->state.proto.file;
286 const char *dir = strchr(file->path, DIRSEP);
288 CURLcode res=CURLE_OK;
289 struct SessionHandle *data = conn->data;
290 char *buf = data->state.buffer;
293 curl_off_t bytecount = 0;
294 struct timeval now = Curl_tvnow();
295 struct_stat file_stat;
299 * Since FILE: doesn't do the full init, we need to provide some extra
302 conn->fread_func = data->set.fread_func;
303 conn->fread_in = data->set.in;
304 conn->data->req.upload_fromhere = buf;
307 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
310 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
312 if(data->state.resume_from)
313 fp = fopen( file->path, "ab" );
317 #ifdef DOS_FILESYSTEM
318 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
319 conn->data->set.new_file_perms);
321 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
322 conn->data->set.new_file_perms);
325 failf(data, "Can't open %s for writing", file->path);
326 return CURLE_WRITE_ERROR;
329 fp = fopen(file->path, "wb");
333 failf(data, "Can't open %s for writing", file->path);
334 return CURLE_WRITE_ERROR;
337 if(-1 != data->set.infilesize)
338 /* known size of data to "upload" */
339 Curl_pgrsSetUploadSize(data, data->set.infilesize);
341 /* treat the negative resume offset value as the case of "-" */
342 if(data->state.resume_from < 0) {
343 if(fstat(fileno(fp), &file_stat)) {
345 failf(data, "Can't get the size of %s", file->path);
346 return CURLE_WRITE_ERROR;
349 data->state.resume_from = (curl_off_t)file_stat.st_size;
352 while(res == CURLE_OK) {
354 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
358 if(readcount <= 0) /* fix questionable compare error. curlvms */
361 nread = (size_t)readcount;
363 /*skip bytes before resume point*/
364 if(data->state.resume_from) {
365 if((curl_off_t)nread <= data->state.resume_from ) {
366 data->state.resume_from -= nread;
371 buf2 = buf + data->state.resume_from;
372 nread -= (size_t)data->state.resume_from;
373 data->state.resume_from = 0;
379 /* write the data to the target */
380 nwrite = fwrite(buf2, 1, nread, fp);
381 if(nwrite != nread) {
382 res = CURLE_SEND_ERROR;
388 Curl_pgrsSetUploadCounter(data, bytecount);
390 if(Curl_pgrsUpdate(conn))
391 res = CURLE_ABORTED_BY_CALLBACK;
393 res = Curl_speedcheck(data, now);
395 if(!res && Curl_pgrsUpdate(conn))
396 res = CURLE_ABORTED_BY_CALLBACK;
404 * file_do() is the protocol-specific function for the do-phase, separated
405 * from the connect-phase above. Other protocols merely setup the transfer in
406 * the do-phase, to have it done in the main transfer loop but since some
407 * platforms we support don't allow select()ing etc on file handles (as
408 * opposed to sockets) we instead perform the whole do-operation in this
411 static CURLcode file_do(struct connectdata *conn, bool *done)
413 /* This implementation ignores the host name in conformance with
414 RFC 1738. Only local files (reachable via the standard file system)
415 are supported. This means that files on remotely mounted directories
416 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
418 CURLcode res = CURLE_OK;
419 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
420 Windows version to have a different struct without
421 having to redefine the simple word 'stat' */
422 curl_off_t expected_size=0;
426 struct SessionHandle *data = conn->data;
427 char *buf = data->state.buffer;
428 curl_off_t bytecount = 0;
430 struct timeval now = Curl_tvnow();
432 *done = TRUE; /* unconditionally */
435 Curl_pgrsStartNow(data);
438 return file_upload(conn);
440 /* get the fd from the connection phase */
441 fd = conn->data->state.proto.file->fd;
443 /* VMS: This only works reliable for STREAMLF files */
444 if(-1 != fstat(fd, &statbuf)) {
445 /* we could stat it, then read out the size */
446 expected_size = statbuf.st_size;
447 /* and store the modification time */
448 data->info.filetime = (long)statbuf.st_mtime;
452 if(fstated && !data->state.range && data->set.timecondition) {
453 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
459 /* If we have selected NOBODY and HEADER, it means that we only want file
460 information. Which for FILE can't be much more than the file size and
462 if(data->set.opt_no_body && data->set.include_header && fstated) {
464 snprintf(buf, sizeof(data->state.buffer),
465 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
466 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
470 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
471 (char *)"Accept-ranges: bytes\r\n", 0);
476 time_t filetime = (time_t)statbuf.st_mtime;
478 const struct tm *tm = &buffer;
479 result = Curl_gmtime(filetime, &buffer);
483 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
484 snprintf(buf, BUFSIZE-1,
485 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
486 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
488 Curl_month[tm->tm_mon],
493 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
495 /* if we fstat()ed the file, set the file size to make it available post-
498 Curl_pgrsSetDownloadSize(data, expected_size);
502 /* Check whether file range has been specified */
505 /* Adjust the start offset in case we want to get the N last bytes
506 * of the stream iff the filesize could be determined */
507 if(data->state.resume_from < 0) {
509 failf(data, "Can't get the size of file.");
510 return CURLE_READ_ERROR;
513 data->state.resume_from += (curl_off_t)statbuf.st_size;
516 if(data->state.resume_from <= expected_size)
517 expected_size -= data->state.resume_from;
519 failf(data, "failed to resume file:// transfer");
520 return CURLE_BAD_DOWNLOAD_RESUME;
523 /* A high water mark has been specified so we obey... */
524 if(data->req.maxdownload > 0)
525 expected_size = data->req.maxdownload;
527 if(fstated && (expected_size == 0))
530 /* The following is a shortcut implementation of file reading
531 this is both more efficient than the former call to download() and
532 it avoids problems with select() and recv() on file descriptors
535 Curl_pgrsSetDownloadSize(data, expected_size);
537 if(data->state.resume_from) {
538 if(data->state.resume_from !=
539 lseek(fd, data->state.resume_from, SEEK_SET))
540 return CURLE_BAD_DOWNLOAD_RESUME;
543 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
545 while(res == CURLE_OK) {
546 /* Don't fill a whole buffer if we want less than all data */
547 bytestoread = (expected_size < BUFSIZE-1)?(size_t)expected_size:BUFSIZE-1;
548 nread = read(fd, buf, bytestoread);
553 if(nread <= 0 || expected_size == 0)
557 expected_size -= nread;
559 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
563 Curl_pgrsSetDownloadCounter(data, bytecount);
565 if(Curl_pgrsUpdate(conn))
566 res = CURLE_ABORTED_BY_CALLBACK;
568 res = Curl_speedcheck(data, now);
570 if(Curl_pgrsUpdate(conn))
571 res = CURLE_ABORTED_BY_CALLBACK;