summaryrefslogtreecommitdiffstats
path: root/lib/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/file.c')
-rw-r--r--lib/file.c388
1 files changed, 282 insertions, 106 deletions
diff --git a/lib/file.c b/lib/file.c
index b247a77..73df42e 100644
--- a/lib/file.c
+++ b/lib/file.c
@@ -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)