#include "setup.h"
#ifndef CURL_DISABLE_FILE
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "strtoofft.h"
#include "urldata.h"
#include <curl/curl.h>
#include "progress.h"
#include "sendf.h"
#include "escape.h"
#include "file.h"
#include "speedcheck.h"
#include "getinfo.h"
#include "transfer.h"
#include "url.h"
#include "curl_memory.h"
#include "parsedate.h"
#include "warnless.h"
#define _MPRINTF_REPLACE
#include <curl/mprintf.h>
#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
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);
const struct Curl_handler Curl_handler_file = {
"FILE",
ZERO_NULL,
file_do,
file_done,
ZERO_NULL,
file_connect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
file_disconnect,
ZERO_NULL,
0,
CURLPROTO_FILE,
PROTOPT_NONETWORK | PROTOPT_NOURLQUERY
};
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) {
to=-1;
}
if((-1 == to) && (from>=0)) {
data->state.resume_from = from;
DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
from));
}
else if(from < 0) {
data->req.maxdownload = -from;
data->state.resume_from = from;
DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
-from));
}
else {
totalsize = to-from;
data->req.maxdownload = totalsize+1;
data->state.resume_from = from;
DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
" getting %" FORMAT_OFF_T " bytes\n",
from, data->req.maxdownload));
}
DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
" to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
from, to, data->req.maxdownload));
}
else
data->req.maxdownload = -1;
return CURLE_OK;
}
static CURLcode file_connect(struct connectdata *conn, bool *done)
{
struct SessionHandle *data = conn->data;
char *real_path;
struct FILEPROTO *file;
int fd;
#ifdef DOS_FILESYSTEM
int i;
char *actual_path;
#endif
Curl_reset_reqproto(conn);
real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
if(!real_path)
return CURLE_OUT_OF_MEMORY;
if(!data->state.proto.file) {
file = calloc(1, sizeof(struct FILEPROTO));
if(!file) {
free(real_path);
return CURLE_OUT_OF_MEMORY;
}
data->state.proto.file = file;
}
else {
file = data->state.proto.file;
Curl_safefree(file->freepath);
file->path = NULL;
if(file->fd != -1)
close(file->fd);
file->fd = -1;
}
#ifdef DOS_FILESYSTEM
actual_path = real_path;
if((actual_path[0] == '/') &&
actual_path[1] &&
(actual_path[2] == ':' || actual_path[2] == '|')) {
actual_path[2] = ':';
actual_path++;
}
for(i=0; actual_path[i] != '\0'; ++i)
if(actual_path[i] == '/')
actual_path[i] = '\\';
fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
file->path = actual_path;
#else
fd = open_readonly(real_path, O_RDONLY);
file->path = real_path;
#endif
file->freepath = real_path;
file->fd = fd;
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;
}
static CURLcode file_done(struct connectdata *conn,
CURLcode status, bool premature)
{
struct FILEPROTO *file = conn->data->state.proto.file;
(void)status;
(void)premature;
if(file) {
Curl_safefree(file->freepath);
file->path = NULL;
if(file->fd != -1)
close(file->fd);
file->fd = -1;
}
return CURLE_OK;
}
static CURLcode file_disconnect(struct connectdata *conn,
bool dead_connection)
{
struct FILEPROTO *file = conn->data->state.proto.file;
(void)dead_connection;
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 '/'
#endif
static CURLcode file_upload(struct connectdata *conn)
{
struct FILEPROTO *file = conn->data->state.proto.file;
const char *dir = strchr(file->path, DIRSEP);
FILE *fp;
CURLcode res=CURLE_OK;
struct SessionHandle *data = conn->data;
char *buf = data->state.buffer;
size_t nread;
size_t nwrite;
curl_off_t bytecount = 0;
struct timeval now = Curl_tvnow();
struct_stat file_stat;
const char* buf2;
conn->fread_func = data->set.fread_func;
conn->fread_in = data->set.in;
conn->data->req.upload_fromhere = buf;
if(!dir)
return CURLE_FILE_COULDNT_READ_FILE;
if(!dir[1])
return CURLE_FILE_COULDNT_READ_FILE;
if(data->state.resume_from)
fp = fopen( file->path, "ab" );
else {
int fd;
#ifdef DOS_FILESYSTEM
fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
conn->data->set.new_file_perms);
#else
fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
conn->data->set.new_file_perms);
#endif
if(fd < 0) {
failf(data, "Can't open %s for writing", file->path);
return CURLE_WRITE_ERROR;
}
close(fd);
fp = fopen(file->path, "wb");
}
if(!fp) {
failf(data, "Can't open %s for writing", file->path);
return CURLE_WRITE_ERROR;
}
if(-1 != data->set.infilesize)
Curl_pgrsSetUploadSize(data, data->set.infilesize);
if(data->state.resume_from < 0) {
if(fstat(fileno(fp), &file_stat)) {
fclose(fp);
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) {
int readcount;
res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
if(res)
break;
if(readcount <= 0)
break;
nread = (size_t)readcount;
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;
nwrite = fwrite(buf2, 1, nread, fp);
if(nwrite != nread) {
res = CURLE_SEND_ERROR;
break;
}
bytecount += nread;
Curl_pgrsSetUploadCounter(data, bytecount);
if(Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
else
res = Curl_speedcheck(data, now);
}
if(!res && Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
fclose(fp);
return res;
}
static CURLcode file_do(struct connectdata *conn, bool *done)
{
CURLcode res = CURLE_OK;
struct_stat statbuf;
curl_off_t expected_size=0;
bool fstated=FALSE;
ssize_t nread;
struct SessionHandle *data = conn->data;
char *buf = data->state.buffer;
curl_off_t bytecount = 0;
int fd;
struct timeval now = Curl_tvnow();
*done = TRUE;
Curl_initinfo(data);
Curl_pgrsStartNow(data);
if(data->set.upload)
return file_upload(conn);
fd = conn->data->state.proto.file->fd;
if(-1 != fstat(fd, &statbuf)) {
expected_size = statbuf.st_size;
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(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);
result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
if(result)
return result;
result = Curl_client_write(conn, CLIENTWRITE_BOTH,
(char *)"Accept-ranges: bytes\r\n", 0);
if(result)
return result;
if(fstated) {
time_t filetime = (time_t)statbuf.st_mtime;
struct tm buffer;
const struct tm *tm = &buffer;
result = Curl_gmtime(filetime, &buffer);
if(result)
return result;
snprintf(buf, BUFSIZE-1,
"Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
tm->tm_mday,
Curl_month[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
}
if(fstated)
Curl_pgrsSetDownloadSize(data, expected_size);
return result;
}
file_range(conn);
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(data->req.maxdownload > 0)
expected_size = data->req.maxdownload;
if(fstated && (expected_size == 0))
return CURLE_OK;
if(fstated)
Curl_pgrsSetDownloadSize(data, expected_size);
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) {
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)
buf[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)
return res;
Curl_pgrsSetDownloadCounter(data, bytecount);
if(Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
else
res = Curl_speedcheck(data, now);
}
if(Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
return res;
}
#endif