1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2012, 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 ***************************************************************************/
23 #include "curl_setup.h"
25 #ifndef CURL_DISABLE_FILE
27 #ifdef HAVE_NETINET_IN_H
28 #include <netinet/in.h>
33 #ifdef HAVE_ARPA_INET_H
34 #include <arpa/inet.h>
39 #ifdef HAVE_SYS_IOCTL_H
40 #include <sys/ioctl.h>
43 #ifdef HAVE_SYS_PARAM_H
44 #include <sys/param.h>
51 #include "strtoofft.h"
53 #include <curl/curl.h>
58 #include "speedcheck.h"
62 #include "curl_memory.h"
63 #include "parsedate.h" /* for the week day and month names */
66 #define _MPRINTF_REPLACE /* use our functions only */
67 #include <curl/mprintf.h>
69 /* The last #include file should be: */
72 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
73 defined(__SYMBIAN32__)
74 #define DOS_FILESYSTEM 1
77 #ifdef OPEN_NEEDS_ARG3
78 # define open_readonly(p,f) open((p),(f),(0))
80 # define open_readonly(p,f) open((p),(f))
84 * Forward declarations.
87 static CURLcode file_do(struct connectdata *, bool *done);
88 static CURLcode file_done(struct connectdata *conn,
89 CURLcode status, bool premature);
90 static CURLcode file_connect(struct connectdata *conn, bool *done);
91 static CURLcode file_disconnect(struct connectdata *conn,
92 bool dead_connection);
96 * FILE scheme handler.
99 const struct Curl_handler Curl_handler_file = {
101 ZERO_NULL, /* setup_connection */
103 file_done, /* done */
104 ZERO_NULL, /* do_more */
105 file_connect, /* connect_it */
106 ZERO_NULL, /* connecting */
107 ZERO_NULL, /* doing */
108 ZERO_NULL, /* proto_getsock */
109 ZERO_NULL, /* doing_getsock */
110 ZERO_NULL, /* domore_getsock */
111 ZERO_NULL, /* perform_getsock */
112 file_disconnect, /* disconnect */
113 ZERO_NULL, /* readwrite */
115 CURLPROTO_FILE, /* protocol */
116 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
121 Check if this is a range download, and if so, set the internal variables
122 properly. This code is copied from the FTP implementation and might as
123 well be factored out.
125 static CURLcode file_range(struct connectdata *conn)
128 curl_off_t totalsize=-1;
131 struct SessionHandle *data = conn->data;
133 if(data->state.use_range && data->state.range) {
134 from=curlx_strtoofft(data->state.range, &ptr, 0);
135 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
137 to=curlx_strtoofft(ptr, &ptr2, 0);
139 /* we didn't get any digit */
142 if((-1 == to) && (from>=0)) {
144 data->state.resume_from = from;
145 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
150 data->req.maxdownload = -from;
151 data->state.resume_from = from;
152 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
158 data->req.maxdownload = totalsize+1; /* include last byte */
159 data->state.resume_from = from;
160 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
161 " getting %" FORMAT_OFF_T " bytes\n",
162 from, data->req.maxdownload));
164 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
165 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
166 from, to, data->req.maxdownload));
169 data->req.maxdownload = -1;
174 * file_connect() gets called from Curl_protocol_connect() to allow us to
175 * do protocol-specific actions at connect-time. We emulate a
176 * connect-then-transfer protocol and "connect" to the file here
178 static CURLcode file_connect(struct connectdata *conn, bool *done)
180 struct SessionHandle *data = conn->data;
182 struct FILEPROTO *file;
184 #ifdef DOS_FILESYSTEM
189 /* If there already is a protocol-specific struct allocated for this
190 sessionhandle, deal with it */
191 Curl_reset_reqproto(conn);
193 real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
195 return CURLE_OUT_OF_MEMORY;
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);
215 #ifdef DOS_FILESYSTEM
216 /* If the first character is a slash, and there's
217 something that looks like a drive at the beginning of
218 the path, skip the slash. If we remove the initial
219 slash in all cases, paths without drive letters end up
220 relative to the current directory which isn't how
223 Some browsers accept | instead of : as the drive letter
224 separator, so we do too.
226 On other platforms, we need the slash to indicate an
227 absolute pathname. On Windows, absolute paths start
230 actual_path = real_path;
231 if((actual_path[0] == '/') &&
233 (actual_path[2] == ':' || actual_path[2] == '|')) {
234 actual_path[2] = ':';
238 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
239 for(i=0; actual_path[i] != '\0'; ++i)
240 if(actual_path[i] == '/')
241 actual_path[i] = '\\';
243 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
244 file->path = actual_path;
246 fd = open_readonly(real_path, O_RDONLY);
247 file->path = real_path;
249 file->freepath = real_path; /* free this when done */
252 if(!data->set.upload && (fd == -1)) {
253 failf(data, "Couldn't open file %s", data->state.path);
254 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
255 return CURLE_FILE_COULDNT_READ_FILE;
262 static CURLcode file_done(struct connectdata *conn,
263 CURLcode status, bool premature)
265 struct FILEPROTO *file = conn->data->state.proto.file;
266 (void)status; /* not used */
267 (void)premature; /* not used */
270 Curl_safefree(file->freepath);
280 static CURLcode file_disconnect(struct connectdata *conn,
281 bool dead_connection)
283 struct FILEPROTO *file = conn->data->state.proto.file;
284 (void)dead_connection; /* not used */
287 Curl_safefree(file->freepath);
297 #ifdef DOS_FILESYSTEM
303 static CURLcode file_upload(struct connectdata *conn)
305 struct FILEPROTO *file = conn->data->state.proto.file;
306 const char *dir = strchr(file->path, DIRSEP);
309 CURLcode res=CURLE_OK;
310 struct SessionHandle *data = conn->data;
311 char *buf = data->state.buffer;
314 curl_off_t bytecount = 0;
315 struct timeval now = Curl_tvnow();
316 struct_stat file_stat;
320 * Since FILE: doesn't do the full init, we need to provide some extra
323 conn->fread_func = data->set.fread_func;
324 conn->fread_in = data->set.in;
325 conn->data->req.upload_fromhere = buf;
328 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
331 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
334 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
336 #define MODE_DEFAULT O_WRONLY|O_CREAT
339 if(data->state.resume_from)
340 mode = MODE_DEFAULT|O_APPEND;
342 mode = MODE_DEFAULT|O_TRUNC;
344 fd = open(file->path, mode, conn->data->set.new_file_perms);
346 failf(data, "Can't open %s for writing", file->path);
347 return CURLE_WRITE_ERROR;
350 if(-1 != data->set.infilesize)
351 /* known size of data to "upload" */
352 Curl_pgrsSetUploadSize(data, data->set.infilesize);
354 /* treat the negative resume offset value as the case of "-" */
355 if(data->state.resume_from < 0) {
356 if(fstat(fd, &file_stat)) {
358 failf(data, "Can't get the size of %s", file->path);
359 return CURLE_WRITE_ERROR;
362 data->state.resume_from = (curl_off_t)file_stat.st_size;
365 while(res == CURLE_OK) {
367 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
371 if(readcount <= 0) /* fix questionable compare error. curlvms */
374 nread = (size_t)readcount;
376 /*skip bytes before resume point*/
377 if(data->state.resume_from) {
378 if((curl_off_t)nread <= data->state.resume_from ) {
379 data->state.resume_from -= nread;
384 buf2 = buf + data->state.resume_from;
385 nread -= (size_t)data->state.resume_from;
386 data->state.resume_from = 0;
392 /* write the data to the target */
393 nwrite = write(fd, buf2, nread);
394 if(nwrite != nread) {
395 res = CURLE_SEND_ERROR;
401 Curl_pgrsSetUploadCounter(data, bytecount);
403 if(Curl_pgrsUpdate(conn))
404 res = CURLE_ABORTED_BY_CALLBACK;
406 res = Curl_speedcheck(data, now);
408 if(!res && Curl_pgrsUpdate(conn))
409 res = CURLE_ABORTED_BY_CALLBACK;
417 * file_do() is the protocol-specific function for the do-phase, separated
418 * from the connect-phase above. Other protocols merely setup the transfer in
419 * the do-phase, to have it done in the main transfer loop but since some
420 * platforms we support don't allow select()ing etc on file handles (as
421 * opposed to sockets) we instead perform the whole do-operation in this
424 static CURLcode file_do(struct connectdata *conn, bool *done)
426 /* This implementation ignores the host name in conformance with
427 RFC 1738. Only local files (reachable via the standard file system)
428 are supported. This means that files on remotely mounted directories
429 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
431 CURLcode res = CURLE_OK;
432 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
433 Windows version to have a different struct without
434 having to redefine the simple word 'stat' */
435 curl_off_t expected_size=0;
438 struct SessionHandle *data = conn->data;
439 char *buf = data->state.buffer;
440 curl_off_t bytecount = 0;
442 struct timeval now = Curl_tvnow();
444 *done = TRUE; /* unconditionally */
447 Curl_pgrsStartNow(data);
450 return file_upload(conn);
452 /* get the fd from the connection phase */
453 fd = conn->data->state.proto.file->fd;
455 /* VMS: This only works reliable for STREAMLF files */
456 if(-1 != fstat(fd, &statbuf)) {
457 /* we could stat it, then read out the size */
458 expected_size = statbuf.st_size;
459 /* and store the modification time */
460 data->info.filetime = (long)statbuf.st_mtime;
464 if(fstated && !data->state.range && data->set.timecondition) {
465 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
471 /* If we have selected NOBODY and HEADER, it means that we only want file
472 information. Which for FILE can't be much more than the file size and
474 if(data->set.opt_no_body && data->set.include_header && fstated) {
476 snprintf(buf, sizeof(data->state.buffer),
477 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
478 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
482 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
483 (char *)"Accept-ranges: bytes\r\n", 0);
488 time_t filetime = (time_t)statbuf.st_mtime;
490 const struct tm *tm = &buffer;
491 result = Curl_gmtime(filetime, &buffer);
495 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
496 snprintf(buf, BUFSIZE-1,
497 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
498 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
500 Curl_month[tm->tm_mon],
505 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
507 /* if we fstat()ed the file, set the file size to make it available post-
510 Curl_pgrsSetDownloadSize(data, expected_size);
514 /* Check whether file range has been specified */
517 /* Adjust the start offset in case we want to get the N last bytes
518 * of the stream iff the filesize could be determined */
519 if(data->state.resume_from < 0) {
521 failf(data, "Can't get the size of file.");
522 return CURLE_READ_ERROR;
525 data->state.resume_from += (curl_off_t)statbuf.st_size;
528 if(data->state.resume_from <= expected_size)
529 expected_size -= data->state.resume_from;
531 failf(data, "failed to resume file:// transfer");
532 return CURLE_BAD_DOWNLOAD_RESUME;
535 /* A high water mark has been specified so we obey... */
536 if(data->req.maxdownload > 0)
537 expected_size = data->req.maxdownload;
539 if(fstated && (expected_size == 0))
542 /* The following is a shortcut implementation of file reading
543 this is both more efficient than the former call to download() and
544 it avoids problems with select() and recv() on file descriptors
547 Curl_pgrsSetDownloadSize(data, expected_size);
549 if(data->state.resume_from) {
550 if(data->state.resume_from !=
551 lseek(fd, data->state.resume_from, SEEK_SET))
552 return CURLE_BAD_DOWNLOAD_RESUME;
555 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
557 while(res == CURLE_OK) {
558 /* Don't fill a whole buffer if we want less than all data */
560 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
561 curlx_sotouz(expected_size) : BUFSIZE - 1;
563 nread = read(fd, buf, bytestoread);
568 if(nread <= 0 || expected_size == 0)
572 expected_size -= nread;
574 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
578 Curl_pgrsSetDownloadCounter(data, bytecount);
580 if(Curl_pgrsUpdate(conn))
581 res = CURLE_ABORTED_BY_CALLBACK;
583 res = Curl_speedcheck(data, now);
585 if(Curl_pgrsUpdate(conn))
586 res = CURLE_ABORTED_BY_CALLBACK;