1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2011, 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 ***************************************************************************/
25 #ifndef CURL_DISABLE_FILE
26 /* -- WIN32 approved -- */
38 #ifdef HAVE_SYS_SOCKET_H
39 #include <sys/socket.h>
41 #ifdef HAVE_NETINET_IN_H
42 #include <netinet/in.h>
44 #ifdef HAVE_SYS_TIME_H
53 #ifdef HAVE_ARPA_INET_H
54 #include <arpa/inet.h>
59 #ifdef HAVE_SYS_IOCTL_H
60 #include <sys/ioctl.h>
63 #ifdef HAVE_SYS_PARAM_H
64 #include <sys/param.h>
73 #include "strtoofft.h"
75 #include <curl/curl.h>
80 #include "speedcheck.h"
84 #include "curl_memory.h"
85 #include "parsedate.h" /* for the week day and month names */
87 #define _MPRINTF_REPLACE /* use our functions only */
88 #include <curl/mprintf.h>
90 /* The last #include file should be: */
93 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || defined(__SYMBIAN32__)
94 #define DOS_FILESYSTEM 1
97 #ifdef OPEN_NEEDS_ARG3
98 # define open_readonly(p,f) open((p),(f),(0))
100 # define open_readonly(p,f) open((p),(f))
104 * Forward declarations.
107 static CURLcode file_do(struct connectdata *, bool *done);
108 static CURLcode file_done(struct connectdata *conn,
109 CURLcode status, bool premature);
110 static CURLcode file_connect(struct connectdata *conn, bool *done);
113 * FILE scheme handler.
116 const struct Curl_handler Curl_handler_file = {
118 ZERO_NULL, /* setup_connection */
120 file_done, /* done */
121 ZERO_NULL, /* do_more */
122 file_connect, /* connect_it */
123 ZERO_NULL, /* connecting */
124 ZERO_NULL, /* doing */
125 ZERO_NULL, /* proto_getsock */
126 ZERO_NULL, /* doing_getsock */
127 ZERO_NULL, /* perform_getsock */
128 ZERO_NULL, /* disconnect */
130 PROT_FILE /* protocol */
135 Check if this is a range download, and if so, set the internal variables
136 properly. This code is copied from the FTP implementation and might as
137 well be factored out.
139 static CURLcode file_range(struct connectdata *conn)
142 curl_off_t totalsize=-1;
145 struct SessionHandle *data = conn->data;
147 if(data->state.use_range && data->state.range) {
148 from=curlx_strtoofft(data->state.range, &ptr, 0);
149 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
151 to=curlx_strtoofft(ptr, &ptr2, 0);
153 /* we didn't get any digit */
156 if((-1 == to) && (from>=0)) {
158 data->state.resume_from = from;
159 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
164 data->req.maxdownload = -from;
165 data->state.resume_from = from;
166 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
172 data->req.maxdownload = totalsize+1; /* include last byte */
173 data->state.resume_from = from;
174 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
175 " getting %" FORMAT_OFF_T " bytes\n",
176 from, data->req.maxdownload));
178 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
179 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
180 from, to, data->req.maxdownload));
183 data->req.maxdownload = -1;
188 * file_connect() gets called from Curl_protocol_connect() to allow us to
189 * do protocol-specific actions at connect-time. We emulate a
190 * connect-then-transfer protocol and "connect" to the file here
192 static CURLcode file_connect(struct connectdata *conn, bool *done)
194 struct SessionHandle *data = conn->data;
195 char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
196 struct FILEPROTO *file;
198 #ifdef DOS_FILESYSTEM
204 return CURLE_OUT_OF_MEMORY;
206 /* If there already is a protocol-specific struct allocated for this
207 sessionhandle, deal with it */
208 Curl_reset_reqproto(conn);
210 if(!data->state.proto.file) {
211 file = calloc(1, sizeof(struct FILEPROTO));
214 return CURLE_OUT_OF_MEMORY;
216 data->state.proto.file = file;
219 /* file is not a protocol that can deal with "persistancy" */
220 file = data->state.proto.file;
221 Curl_safefree(file->freepath);
225 file->freepath = NULL;
229 #ifdef DOS_FILESYSTEM
230 /* If the first character is a slash, and there's
231 something that looks like a drive at the beginning of
232 the path, skip the slash. If we remove the initial
233 slash in all cases, paths without drive letters end up
234 relative to the current directory which isn't how
237 Some browsers accept | instead of : as the drive letter
238 separator, so we do too.
240 On other platforms, we need the slash to indicate an
241 absolute pathname. On Windows, absolute paths start
244 actual_path = real_path;
245 if((actual_path[0] == '/') &&
247 (actual_path[2] == ':' || actual_path[2] == '|'))
249 actual_path[2] = ':';
253 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
254 for (i=0; actual_path[i] != '\0'; ++i)
255 if(actual_path[i] == '/')
256 actual_path[i] = '\\';
258 fd = open_readonly(actual_path, O_RDONLY|O_BINARY); /* no CR/LF translation */
259 file->path = actual_path;
261 fd = open_readonly(real_path, O_RDONLY);
262 file->path = real_path;
264 file->freepath = real_path; /* free this when done */
267 if(!data->set.upload && (fd == -1)) {
268 failf(data, "Couldn't open file %s", data->state.path);
269 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
270 return CURLE_FILE_COULDNT_READ_FILE;
277 static CURLcode file_done(struct connectdata *conn,
278 CURLcode status, bool premature)
280 struct FILEPROTO *file = conn->data->state.proto.file;
281 (void)status; /* not used */
282 (void)premature; /* not used */
283 Curl_safefree(file->freepath);
291 #ifdef DOS_FILESYSTEM
297 static CURLcode file_upload(struct connectdata *conn)
299 struct FILEPROTO *file = conn->data->state.proto.file;
300 const char *dir = strchr(file->path, DIRSEP);
302 CURLcode res=CURLE_OK;
303 struct SessionHandle *data = conn->data;
304 char *buf = data->state.buffer;
307 curl_off_t bytecount = 0;
308 struct timeval now = Curl_tvnow();
309 struct_stat file_stat;
313 * Since FILE: doesn't do the full init, we need to provide some extra
316 conn->fread_func = data->set.fread_func;
317 conn->fread_in = data->set.in;
318 conn->data->req.upload_fromhere = buf;
321 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
324 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
326 if(data->state.resume_from)
327 fp = fopen( file->path, "ab" );
331 #ifdef DOS_FILESYSTEM
332 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
333 conn->data->set.new_file_perms);
335 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
336 conn->data->set.new_file_perms);
339 failf(data, "Can't open %s for writing", file->path);
340 return CURLE_WRITE_ERROR;
343 fp = fopen(file->path, "wb");
347 failf(data, "Can't open %s for writing", file->path);
348 return CURLE_WRITE_ERROR;
351 if(-1 != data->set.infilesize)
352 /* known size of data to "upload" */
353 Curl_pgrsSetUploadSize(data, data->set.infilesize);
355 /* treat the negative resume offset value as the case of "-" */
356 if(data->state.resume_from < 0) {
357 if(fstat(fileno(fp), &file_stat)) {
359 failf(data, "Can't get the size of %s", file->path);
360 return CURLE_WRITE_ERROR;
363 data->state.resume_from = (curl_off_t)file_stat.st_size;
366 while(res == CURLE_OK) {
368 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
372 if(readcount <= 0) /* fix questionable compare error. curlvms */
375 nread = (size_t)readcount;
377 /*skip bytes before resume point*/
378 if(data->state.resume_from) {
379 if( (curl_off_t)nread <= data->state.resume_from ) {
380 data->state.resume_from -= nread;
385 buf2 = buf + data->state.resume_from;
386 nread -= (size_t)data->state.resume_from;
387 data->state.resume_from = 0;
393 /* write the data to the target */
394 nwrite = fwrite(buf2, 1, nread, fp);
395 if(nwrite != nread) {
396 res = CURLE_SEND_ERROR;
402 Curl_pgrsSetUploadCounter(data, bytecount);
404 if(Curl_pgrsUpdate(conn))
405 res = CURLE_ABORTED_BY_CALLBACK;
407 res = Curl_speedcheck(data, now);
409 if(!res && Curl_pgrsUpdate(conn))
410 res = CURLE_ABORTED_BY_CALLBACK;
418 * file_do() is the protocol-specific function for the do-phase, separated
419 * from the connect-phase above. Other protocols merely setup the transfer in
420 * the do-phase, to have it done in the main transfer loop but since some
421 * platforms we support don't allow select()ing etc on file handles (as
422 * opposed to sockets) we instead perform the whole do-operation in this
425 static CURLcode file_do(struct connectdata *conn, bool *done)
427 /* This implementation ignores the host name in conformance with
428 RFC 1738. Only local files (reachable via the standard file system)
429 are supported. This means that files on remotely mounted directories
430 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
432 CURLcode res = CURLE_OK;
433 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
434 Windows version to have a different struct without
435 having to redefine the simple word 'stat' */
436 curl_off_t expected_size=0;
440 struct SessionHandle *data = conn->data;
441 char *buf = data->state.buffer;
442 curl_off_t bytecount = 0;
444 struct timeval now = Curl_tvnow();
446 *done = TRUE; /* unconditionally */
449 Curl_pgrsStartNow(data);
452 return file_upload(conn);
454 /* get the fd from the connection phase */
455 fd = conn->data->state.proto.file->fd;
457 /* VMS: This only works reliable for STREAMLF files */
458 if( -1 != fstat(fd, &statbuf)) {
459 /* we could stat it, then read out the size */
460 expected_size = statbuf.st_size;
461 /* and store the modification time */
462 data->info.filetime = (long)statbuf.st_mtime;
466 if(fstated && !data->state.range && data->set.timecondition) {
467 if(!Curl_meets_timecondition(data, data->info.filetime)) {
473 /* If we have selected NOBODY and HEADER, it means that we only want file
474 information. Which for FILE can't be much more than the file size and
476 if(data->set.opt_no_body && data->set.include_header && fstated) {
478 snprintf(buf, sizeof(data->state.buffer),
479 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
480 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
484 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
485 (char *)"Accept-ranges: bytes\r\n", 0);
490 time_t filetime = (time_t)statbuf.st_mtime;
492 const struct tm *tm = &buffer;
493 result = Curl_gmtime(filetime, &buffer);
497 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
498 snprintf(buf, BUFSIZE-1,
499 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
500 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
502 Curl_month[tm->tm_mon],
507 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
509 /* if we fstat()ed the file, set the file size to make it available post-
512 Curl_pgrsSetDownloadSize(data, expected_size);
516 /* Check whether file range has been specified */
519 /* Adjust the start offset in case we want to get the N last bytes
520 * of the stream iff the filesize could be determined */
521 if(data->state.resume_from < 0) {
523 failf(data, "Can't get the size of file.");
524 return CURLE_READ_ERROR;
527 data->state.resume_from += (curl_off_t)statbuf.st_size;
530 if(data->state.resume_from <= expected_size)
531 expected_size -= data->state.resume_from;
533 failf(data, "failed to resume file:// transfer");
534 return CURLE_BAD_DOWNLOAD_RESUME;
537 /* A high water mark has been specified so we obey... */
538 if (data->req.maxdownload > 0)
539 expected_size = data->req.maxdownload;
541 if(fstated && (expected_size == 0))
544 /* The following is a shortcut implementation of file reading
545 this is both more efficient than the former call to download() and
546 it avoids problems with select() and recv() on file descriptors
549 Curl_pgrsSetDownloadSize(data, expected_size);
551 if(data->state.resume_from) {
552 if(data->state.resume_from !=
553 lseek(fd, data->state.resume_from, SEEK_SET))
554 return CURLE_BAD_DOWNLOAD_RESUME;
557 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
559 while(res == CURLE_OK) {
560 /* Don't fill a whole buffer if we want less than all data */
561 bytestoread = (expected_size < BUFSIZE-1)?(size_t)expected_size:BUFSIZE-1;
562 nread = read(fd, buf, bytestoread);
567 if (nread <= 0 || expected_size == 0)
571 expected_size -= nread;
573 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
577 Curl_pgrsSetDownloadCounter(data, bytecount);
579 if(Curl_pgrsUpdate(conn))
580 res = CURLE_ABORTED_BY_CALLBACK;
582 res = Curl_speedcheck(data, now);
584 if(Curl_pgrsUpdate(conn))
585 res = CURLE_ABORTED_BY_CALLBACK;