diff options
Diffstat (limited to 'lib/file.c')
-rw-r--r-- | lib/file.c | 388 |
1 files changed, 282 insertions, 106 deletions
@@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -18,42 +18,15 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * - * $Id$ ***************************************************************************/ -#include "setup.h" +#include "curl_setup.h" #ifndef CURL_DISABLE_FILE -/* -- WIN32 approved -- */ -#include <stdio.h> -#include <string.h> -#include <stdarg.h> -#include <stdlib.h> -#include <ctype.h> -#ifdef HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif -#ifdef HAVE_SYS_STAT_H -#include <sys/stat.h> -#endif -#ifdef WIN32 -#include <time.h> -#include <io.h> -#include <fcntl.h> -#else -#ifdef HAVE_SYS_SOCKET_H -#include <sys/socket.h> -#endif #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif -#ifdef HAVE_SYS_TIME_H -#include <sys/time.h> -#endif -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif #ifdef HAVE_NETDB_H #include <netdb.h> #endif @@ -63,8 +36,9 @@ #ifdef HAVE_NET_IF_H #include <net/if.h> #endif +#ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> -#include <signal.h> +#endif #ifdef HAVE_SYS_PARAM_H #include <sys/param.h> @@ -74,8 +48,7 @@ #include <fcntl.h> #endif -#endif - +#include "strtoofft.h" #include "urldata.h" #include <curl/curl.h> #include "progress.h" @@ -86,8 +59,9 @@ #include "getinfo.h" #include "transfer.h" #include "url.h" -#include "memory.h" +#include "curl_memory.h" #include "parsedate.h" /* for the week day and month names */ +#include "warnless.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -95,37 +69,139 @@ /* The last #include file should be: */ #include "memdebug.h" +#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \ + defined(__SYMBIAN32__) +#define DOS_FILESYSTEM 1 +#endif + +#ifdef OPEN_NEEDS_ARG3 +# define open_readonly(p,f) open((p),(f),(0)) +#else +# define open_readonly(p,f) open((p),(f)) +#endif + +/* + * Forward declarations. + */ + +static CURLcode file_do(struct connectdata *, bool *done); +static CURLcode file_done(struct connectdata *conn, + CURLcode status, bool premature); +static CURLcode file_connect(struct connectdata *conn, bool *done); +static CURLcode file_disconnect(struct connectdata *conn, + bool dead_connection); +static CURLcode file_setup_connection(struct connectdata *conn); + /* - * Curl_file_connect() gets called from Curl_protocol_connect() to allow us to + * FILE scheme handler. + */ + +const struct Curl_handler Curl_handler_file = { + "FILE", /* scheme */ + file_setup_connection, /* setup_connection */ + file_do, /* do_it */ + file_done, /* done */ + ZERO_NULL, /* do_more */ + file_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + file_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + 0, /* defport */ + CURLPROTO_FILE, /* protocol */ + PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */ +}; + + +static CURLcode file_setup_connection(struct connectdata *conn) +{ + /* allocate the FILE specific struct */ + conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO)); + if(!conn->data->req.protop) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + + /* + Check if this is a range download, and if so, set the internal variables + properly. This code is copied from the FTP implementation and might as + well be factored out. + */ +static CURLcode file_range(struct connectdata *conn) +{ + curl_off_t from, to; + curl_off_t totalsize=-1; + char *ptr; + char *ptr2; + struct SessionHandle *data = conn->data; + + if(data->state.use_range && data->state.range) { + from=curlx_strtoofft(data->state.range, &ptr, 0); + while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) + ptr++; + to=curlx_strtoofft(ptr, &ptr2, 0); + if(ptr == ptr2) { + /* we didn't get any digit */ + to=-1; + } + if((-1 == to) && (from>=0)) { + /* X - */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n", + from)); + } + else if(from < 0) { + /* -Y */ + data->req.maxdownload = -from; + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n", + -from)); + } + else { + /* X-Y */ + totalsize = to-from; + data->req.maxdownload = totalsize+1; /* include last byte */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T + " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n", + from, data->req.maxdownload)); + } + DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T + " to %" CURL_FORMAT_CURL_OFF_T ", totally %" + CURL_FORMAT_CURL_OFF_T " bytes\n", + from, to, data->req.maxdownload)); + } + else + data->req.maxdownload = -1; + return CURLE_OK; +} + +/* + * file_connect() gets called from Curl_protocol_connect() to allow us to * do protocol-specific actions at connect-time. We emulate a * connect-then-transfer protocol and "connect" to the file here */ -CURLcode Curl_file_connect(struct connectdata *conn) +static CURLcode file_connect(struct connectdata *conn, bool *done) { - char *real_path = curl_easy_unescape(conn->data, conn->data->reqdata.path, 0, NULL); - struct FILEPROTO *file; + struct SessionHandle *data = conn->data; + char *real_path; + struct FILEPROTO *file = data->req.protop; int fd; -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +#ifdef DOS_FILESYSTEM int i; char *actual_path; #endif + real_path = curl_easy_unescape(data, data->state.path, 0, NULL); if(!real_path) return CURLE_OUT_OF_MEMORY; - file = (struct FILEPROTO *)calloc(sizeof(struct FILEPROTO), 1); - if(!file) { - free(real_path); - return CURLE_OUT_OF_MEMORY; - } - - if (conn->data->reqdata.proto.file) { - free(conn->data->reqdata.proto.file); - } - - conn->data->reqdata.proto.file = file; - -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +#ifdef DOS_FILESYSTEM /* If the first character is a slash, and there's something that looks like a drive at the beginning of the path, skip the slash. If we remove the initial @@ -141,52 +217,73 @@ CURLcode Curl_file_connect(struct connectdata *conn) with a drive letter. */ actual_path = real_path; - if ((actual_path[0] == '/') && + if((actual_path[0] == '/') && actual_path[1] && - (actual_path[2] == ':' || actual_path[2] == '|')) - { + (actual_path[2] == ':' || actual_path[2] == '|')) { actual_path[2] = ':'; actual_path++; } /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ - for (i=0; actual_path[i] != '\0'; ++i) - if (actual_path[i] == '/') + for(i=0; actual_path[i] != '\0'; ++i) + if(actual_path[i] == '/') actual_path[i] = '\\'; - fd = open(actual_path, O_RDONLY | O_BINARY); /* no CR/LF translation! */ + fd = open_readonly(actual_path, O_RDONLY|O_BINARY); file->path = actual_path; #else - fd = open(real_path, O_RDONLY); + fd = open_readonly(real_path, O_RDONLY); file->path = real_path; #endif file->freepath = real_path; /* free this when done */ file->fd = fd; - if(!conn->data->set.upload && (fd == -1)) { - failf(conn->data, "Couldn't open file %s", conn->data->reqdata.path); - Curl_file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); + if(!data->set.upload && (fd == -1)) { + failf(data, "Couldn't open file %s", data->state.path); + file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); return CURLE_FILE_COULDNT_READ_FILE; } + *done = TRUE; return CURLE_OK; } -CURLcode Curl_file_done(struct connectdata *conn, - CURLcode status, bool premature) +static CURLcode file_done(struct connectdata *conn, + CURLcode status, bool premature) { - struct FILEPROTO *file = conn->data->reqdata.proto.file; + struct FILEPROTO *file = conn->data->req.protop; (void)status; /* not used */ (void)premature; /* not used */ - Curl_safefree(file->freepath); - if(file->fd != -1) - close(file->fd); + if(file) { + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } return CURLE_OK; } -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +static CURLcode file_disconnect(struct connectdata *conn, + bool dead_connection) +{ + struct FILEPROTO *file = conn->data->req.protop; + (void)dead_connection; /* not used */ + + if(file) { + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } + + return CURLE_OK; +} + +#ifdef DOS_FILESYSTEM #define DIRSEP '\\' #else #define DIRSEP '/' @@ -194,9 +291,10 @@ CURLcode Curl_file_done(struct connectdata *conn, static CURLcode file_upload(struct connectdata *conn) { - struct FILEPROTO *file = conn->data->reqdata.proto.file; - char *dir = strchr(file->path, DIRSEP); - FILE *fp; + struct FILEPROTO *file = conn->data->req.protop; + const char *dir = strchr(file->path, DIRSEP); + int fd; + int mode; CURLcode res=CURLE_OK; struct SessionHandle *data = conn->data; char *buf = data->state.buffer; @@ -204,44 +302,84 @@ static CURLcode file_upload(struct connectdata *conn) size_t nwrite; curl_off_t bytecount = 0; struct timeval now = Curl_tvnow(); + struct_stat file_stat; + const char* buf2; /* * Since FILE: doesn't do the full init, we need to provide some extra * assignments here. */ - conn->fread = data->set.fread; + conn->fread_func = data->set.fread_func; conn->fread_in = data->set.in; - conn->data->reqdata.upload_fromhere = buf; + conn->data->req.upload_fromhere = buf; if(!dir) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ if(!dir[1]) - return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ + return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ - fp = fopen(file->path, "wb"); - if(!fp) { +#ifdef O_BINARY +#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY +#else +#define MODE_DEFAULT O_WRONLY|O_CREAT +#endif + + if(data->state.resume_from) + mode = MODE_DEFAULT|O_APPEND; + else + mode = MODE_DEFAULT|O_TRUNC; + + fd = open(file->path, mode, conn->data->set.new_file_perms); + if(fd < 0) { failf(data, "Can't open %s for writing", file->path); return CURLE_WRITE_ERROR; } - if(-1 != data->set.infilesize) + if(-1 != data->state.infilesize) /* known size of data to "upload" */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); + Curl_pgrsSetUploadSize(data, data->state.infilesize); + + /* treat the negative resume offset value as the case of "-" */ + if(data->state.resume_from < 0) { + if(fstat(fd, &file_stat)) { + close(fd); + failf(data, "Can't get the size of %s", file->path); + return CURLE_WRITE_ERROR; + } + else + data->state.resume_from = (curl_off_t)file_stat.st_size; + } - while (res == CURLE_OK) { + while(res == CURLE_OK) { int readcount; res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount); if(res) break; - if (readcount <= 0) /* fix questionable compare error. curlvms */ + if(readcount <= 0) /* fix questionable compare error. curlvms */ break; nread = (size_t)readcount; + /*skip bytes before resume point*/ + if(data->state.resume_from) { + if((curl_off_t)nread <= data->state.resume_from ) { + data->state.resume_from -= nread; + nread = 0; + buf2 = buf; + } + else { + buf2 = buf + data->state.resume_from; + nread -= (size_t)data->state.resume_from; + data->state.resume_from = 0; + } + } + else + buf2 = buf; + /* write the data to the target */ - nwrite = fwrite(buf, 1, nread, fp); + nwrite = write(fd, buf2, nread); if(nwrite != nread) { res = CURLE_SEND_ERROR; break; @@ -259,20 +397,20 @@ static CURLcode file_upload(struct connectdata *conn) if(!res && Curl_pgrsUpdate(conn)) res = CURLE_ABORTED_BY_CALLBACK; - fclose(fp); + close(fd); return res; } /* - * Curl_file() is the protocol-specific function for the do-phase, separated + * file_do() is the protocol-specific function for the do-phase, separated * from the connect-phase above. Other protocols merely setup the transfer in * the do-phase, to have it done in the main transfer loop but since some * platforms we support don't allow select()ing etc on file handles (as * opposed to sockets) we instead perform the whole do-operation in this * function. */ -CURLcode Curl_file(struct connectdata *conn, bool *done) +static CURLcode file_do(struct connectdata *conn, bool *done) { /* This implementation ignores the host name in conformance with RFC 1738. Only local files (reachable via the standard file system) @@ -291,33 +429,44 @@ CURLcode Curl_file(struct connectdata *conn, bool *done) curl_off_t bytecount = 0; int fd; struct timeval now = Curl_tvnow(); + struct FILEPROTO *file; *done = TRUE; /* unconditionally */ - Curl_readwrite_init(conn); Curl_initinfo(data); Curl_pgrsStartNow(data); if(data->set.upload) return file_upload(conn); + file = conn->data->req.protop; + /* get the fd from the connection phase */ - fd = conn->data->reqdata.proto.file->fd; + fd = file->fd; /* VMS: This only works reliable for STREAMLF files */ - if( -1 != fstat(fd, &statbuf)) { + if(-1 != fstat(fd, &statbuf)) { /* we could stat it, then read out the size */ expected_size = statbuf.st_size; + /* and store the modification time */ + data->info.filetime = (long)statbuf.st_mtime; fstated = TRUE; } + if(fstated && !data->state.range && data->set.timecondition) { + if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) { + *done = TRUE; + return CURLE_OK; + } + } + /* If we have selected NOBODY and HEADER, it means that we only want file information. Which for FILE can't be much more than the file size and date. */ - if(conn->bits.no_body && data->set.include_header && fstated) { + if(data->set.opt_no_body && data->set.include_header && fstated) { CURLcode result; snprintf(buf, sizeof(data->state.buffer), - "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size); + "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size); result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); if(result) return result; @@ -328,14 +477,13 @@ CURLcode Curl_file(struct connectdata *conn, bool *done) return result; if(fstated) { - struct tm *tm; - time_t clock = (time_t)statbuf.st_mtime; -#ifdef HAVE_GMTIME_R + time_t filetime = (time_t)statbuf.st_mtime; struct tm buffer; - tm = (struct tm *)gmtime_r(&clock, &buffer); -#else - tm = gmtime(&clock); -#endif + const struct tm *tm = &buffer; + result = Curl_gmtime(filetime, &buffer); + if(result) + return result; + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ snprintf(buf, BUFSIZE-1, "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", @@ -348,17 +496,39 @@ CURLcode Curl_file(struct connectdata *conn, bool *done) tm->tm_sec); result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); } + /* if we fstat()ed the file, set the file size to make it available post- + transfer */ + if(fstated) + Curl_pgrsSetDownloadSize(data, expected_size); return result; } - if (data->reqdata.resume_from <= expected_size) - expected_size -= data->reqdata.resume_from; + /* Check whether file range has been specified */ + file_range(conn); + + /* Adjust the start offset in case we want to get the N last bytes + * of the stream iff the filesize could be determined */ + if(data->state.resume_from < 0) { + if(!fstated) { + failf(data, "Can't get the size of file."); + return CURLE_READ_ERROR; + } + else + data->state.resume_from += (curl_off_t)statbuf.st_size; + } + + if(data->state.resume_from <= expected_size) + expected_size -= data->state.resume_from; else { failf(data, "failed to resume file:// transfer"); return CURLE_BAD_DOWNLOAD_RESUME; } - if (fstated && (expected_size == 0)) + /* A high water mark has been specified so we obey... */ + if(data->req.maxdownload > 0) + expected_size = data->req.maxdownload; + + if(fstated && (expected_size == 0)) return CURLE_OK; /* The following is a shortcut implementation of file reading @@ -368,24 +538,30 @@ CURLcode Curl_file(struct connectdata *conn, bool *done) if(fstated) Curl_pgrsSetDownloadSize(data, expected_size); - if(data->reqdata.resume_from) { - if(data->reqdata.resume_from != - lseek(fd, data->reqdata.resume_from, SEEK_SET)) + if(data->state.resume_from) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) return CURLE_BAD_DOWNLOAD_RESUME; } Curl_pgrsTime(data, TIMER_STARTTRANSFER); - while (res == CURLE_OK) { - nread = read(fd, buf, BUFSIZE-1); + while(res == CURLE_OK) { + /* Don't fill a whole buffer if we want less than all data */ + size_t bytestoread = + (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ? + curlx_sotouz(expected_size) : BUFSIZE - 1; + + nread = read(fd, buf, bytestoread); - if ( nread > 0) + if(nread > 0) buf[nread] = 0; - if (nread <= 0) + if(nread <= 0 || expected_size == 0) break; bytecount += nread; + expected_size -= nread; res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); if(res) |