#include "curl_setup.h"
#ifndef CURL_DISABLE_FILE
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.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_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include "urldata.h"
#include <curl/curl.h>
#include "progress.h"
#include "sendf.h"
#include "escape.h"
#include "file.h"
#include "speedcheck.h"
#include "multiif.h"
#include "transfer.h"
#include "url.h"
#include "parsedate.h"
#include "curlx/fopen.h"
#include "curlx/warnless.h"
#include "curl_range.h"
#include "curl_memory.h"
#include "memdebug.h"
#if defined(_WIN32) || defined(MSDOS)
#define DOS_FILESYSTEM 1
#elif defined(__amigaos4__)
#define AMIGA_FILESYSTEM 1
#endif
#define CURL_META_FILE_EASY "meta:proto:file:easy"
struct FILEPROTO {
char *path;
char *freepath;
int fd;
};
static CURLcode file_do(struct Curl_easy *data, bool *done);
static CURLcode file_done(struct Curl_easy *data,
CURLcode status, bool premature);
static CURLcode file_connect(struct Curl_easy *data, bool *done);
static CURLcode file_disconnect(struct Curl_easy *data,
struct connectdata *conn,
bool dead_connection);
static CURLcode file_setup_connection(struct Curl_easy *data,
struct connectdata *conn);
const struct Curl_handler Curl_handler_file = {
"file",
file_setup_connection,
file_do,
file_done,
ZERO_NULL,
file_connect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
file_disconnect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
0,
CURLPROTO_FILE,
CURLPROTO_FILE,
PROTOPT_NONETWORK | PROTOPT_NOURLQUERY
};
static void file_cleanup(struct FILEPROTO *file)
{
Curl_safefree(file->freepath);
file->path = NULL;
if(file->fd != -1) {
close(file->fd);
file->fd = -1;
}
}
static void file_easy_dtor(void *key, size_t klen, void *entry)
{
struct FILEPROTO *file = entry;
(void)key;
(void)klen;
file_cleanup(file);
free(file);
}
static CURLcode file_setup_connection(struct Curl_easy *data,
struct connectdata *conn)
{
struct FILEPROTO *filep;
(void)conn;
filep = calloc(1, sizeof(*filep));
if(!filep ||
Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
static CURLcode file_connect(struct Curl_easy *data, bool *done)
{
char *real_path;
struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
int fd;
#ifdef DOS_FILESYSTEM
size_t i;
char *actual_path;
#endif
size_t real_path_len;
CURLcode result;
if(!file)
return CURLE_FAILED_INIT;
if(file->path) {
*done = TRUE;
return CURLE_OK;
}
result = Curl_urldecode(data->state.up.path, 0, &real_path,
&real_path_len, REJECT_ZERO);
if(result)
return result;
#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++;
real_path_len--;
}
for(i = 0; i < real_path_len; ++i)
if(actual_path[i] == '/')
actual_path[i] = '\\';
else if(!actual_path[i]) {
Curl_safefree(real_path);
return CURLE_URL_MALFORMAT;
}
fd = curlx_open(actual_path, O_RDONLY | CURL_O_BINARY);
file->path = actual_path;
#else
if(memchr(real_path, 0, real_path_len)) {
Curl_safefree(real_path);
return CURLE_URL_MALFORMAT;
}
#ifdef AMIGA_FILESYSTEM
fd = -1;
file->path = real_path;
if(real_path[0] == '/') {
extern int __unix_path_semantics;
if(strchr(real_path + 1, ':')) {
fd = curlx_open(real_path + 1, O_RDONLY);
file->path++;
}
else if(__unix_path_semantics) {
fd = curlx_open(real_path, O_RDONLY);
}
}
#else
fd = curlx_open(real_path, O_RDONLY);
file->path = real_path;
#endif
#endif
free(file->freepath);
file->freepath = real_path;
file->fd = fd;
if(!data->state.upload && (fd == -1)) {
failf(data, "Couldn't open file %s", data->state.up.path);
file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
return CURLE_FILE_COULDNT_READ_FILE;
}
*done = TRUE;
return CURLE_OK;
}
static CURLcode file_done(struct Curl_easy *data,
CURLcode status, bool premature)
{
struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
(void)status;
(void)premature;
if(file)
file_cleanup(file);
return CURLE_OK;
}
static CURLcode file_disconnect(struct Curl_easy *data,
struct connectdata *conn,
bool dead_connection)
{
(void)dead_connection;
(void)conn;
return file_done(data, CURLE_OK, FALSE);
}
#ifdef DOS_FILESYSTEM
#define DIRSEP '\\'
#else
#define DIRSEP '/'
#endif
static CURLcode file_upload(struct Curl_easy *data,
struct FILEPROTO *file)
{
const char *dir = strchr(file->path, DIRSEP);
int fd;
int mode;
CURLcode result = CURLE_OK;
char *xfer_ulbuf;
size_t xfer_ulblen;
curl_off_t bytecount = 0;
struct_stat file_stat;
const char *sendbuf;
bool eos = FALSE;
if(!dir)
return CURLE_FILE_COULDNT_READ_FILE;
if(!dir[1])
return CURLE_FILE_COULDNT_READ_FILE;
mode = O_WRONLY|O_CREAT|CURL_O_BINARY;
if(data->state.resume_from)
mode |= O_APPEND;
else
mode |= O_TRUNC;
#if (defined(ANDROID) || defined(__ANDROID__)) && \
(defined(__i386__) || defined(__arm__))
fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms);
#else
fd = curlx_open(file->path, mode, data->set.new_file_perms);
#endif
if(fd < 0) {
failf(data, "cannot open %s for writing", file->path);
return CURLE_WRITE_ERROR;
}
if(data->state.infilesize != -1)
Curl_pgrsSetUploadSize(data, data->state.infilesize);
if(data->state.resume_from < 0) {
if(fstat(fd, &file_stat)) {
close(fd);
failf(data, "cannot get the size of %s", file->path);
return CURLE_WRITE_ERROR;
}
data->state.resume_from = (curl_off_t)file_stat.st_size;
}
result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
if(result)
goto out;
while(!result && !eos) {
size_t nread;
ssize_t nwrite;
size_t readcount;
result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
if(result)
break;
if(!readcount)
break;
nread = readcount;
if(data->state.resume_from) {
if((curl_off_t)nread <= data->state.resume_from) {
data->state.resume_from -= nread;
nread = 0;
sendbuf = xfer_ulbuf;
}
else {
sendbuf = xfer_ulbuf + data->state.resume_from;
nread -= (size_t)data->state.resume_from;
data->state.resume_from = 0;
}
}
else
sendbuf = xfer_ulbuf;
nwrite = write(fd, sendbuf, nread);
if((size_t)nwrite != nread) {
result = CURLE_SEND_ERROR;
break;
}
bytecount += nread;
Curl_pgrsSetUploadCounter(data, bytecount);
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, curlx_now());
}
if(!result && Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
out:
close(fd);
Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
return result;
}
static CURLcode file_do(struct Curl_easy *data, bool *done)
{
struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
CURLcode result = CURLE_OK;
struct_stat statbuf;
curl_off_t expected_size = -1;
bool size_known;
bool fstated = FALSE;
int fd;
char *xfer_buf;
size_t xfer_blen;
*done = TRUE;
if(!file)
return CURLE_FAILED_INIT;
if(data->state.upload)
return file_upload(data, file);
fd = file->fd;
if(fstat(fd, &statbuf) != -1) {
if(!S_ISDIR(statbuf.st_mode))
expected_size = statbuf.st_size;
data->info.filetime = statbuf.st_mtime;
fstated = TRUE;
}
if(fstated && !data->state.range && data->set.timecondition &&
!Curl_meets_timecondition(data, data->info.filetime))
return CURLE_OK;
if(fstated) {
time_t filetime;
struct tm buffer;
const struct tm *tm = &buffer;
char header[80];
int headerlen;
static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" };
if(expected_size >= 0) {
headerlen =
curl_msnprintf(header, sizeof(header),
"Content-Length: %" FMT_OFF_T "\r\n", expected_size);
result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
if(result)
return result;
result = Curl_client_write(data, CLIENTWRITE_HEADER,
accept_ranges, sizeof(accept_ranges) - 1);
if(result != CURLE_OK)
return result;
}
filetime = (time_t)statbuf.st_mtime;
result = Curl_gmtime(filetime, &buffer);
if(result)
return result;
headerlen =
curl_msnprintf(header, sizeof(header),
"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(data, CLIENTWRITE_HEADER, header, headerlen);
if(!result)
result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
if(result)
return result;
Curl_pgrsSetDownloadSize(data, expected_size);
if(data->req.no_body)
return CURLE_OK;
}
result = Curl_range(data);
if(result)
return result;
if(data->state.resume_from < 0) {
if(!fstated) {
failf(data, "cannot get the size of file.");
return CURLE_READ_ERROR;
}
data->state.resume_from += (curl_off_t)statbuf.st_size;
}
if(data->state.resume_from > 0) {
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))
size_known = FALSE;
else
size_known = TRUE;
if(size_known)
Curl_pgrsSetDownloadSize(data, expected_size);
if(data->state.resume_from) {
if(!S_ISDIR(statbuf.st_mode)) {
#if defined(__AMIGA__) || defined(__MINGW32CE__)
if(data->state.resume_from !=
lseek(fd, (off_t)data->state.resume_from, SEEK_SET))
#else
if(data->state.resume_from !=
lseek(fd, data->state.resume_from, SEEK_SET))
#endif
return CURLE_BAD_DOWNLOAD_RESUME;
}
else {
return CURLE_BAD_DOWNLOAD_RESUME;
}
}
result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
if(result)
goto out;
if(!S_ISDIR(statbuf.st_mode)) {
while(!result) {
ssize_t nread;
size_t bytestoread;
if(size_known) {
bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ?
curlx_sotouz(expected_size) : (xfer_blen-1);
}
else
bytestoread = xfer_blen-1;
nread = read(fd, xfer_buf, bytestoread);
if(nread > 0)
xfer_buf[nread] = 0;
if(nread <= 0 || (size_known && (expected_size == 0)))
break;
if(size_known)
expected_size -= nread;
result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
if(result)
goto out;
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, curlx_now());
if(result)
goto out;
}
}
else {
#ifdef HAVE_OPENDIR
DIR *dir = opendir(file->path);
struct dirent *entry;
if(!dir) {
result = CURLE_READ_ERROR;
goto out;
}
else {
while((entry = readdir(dir))) {
if(entry->d_name[0] != '.') {
result = Curl_client_write(data, CLIENTWRITE_BODY,
entry->d_name, strlen(entry->d_name));
if(result)
break;
result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
if(result)
break;
}
}
closedir(dir);
}
#else
failf(data, "Directory listing not yet implemented on this platform.");
result = CURLE_READ_ERROR;
#endif
}
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
out:
Curl_multi_xfer_buf_release(data, xfer_buf);
return result;
}
#endif