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 ***************************************************************************/
25 #ifndef CURL_DISABLE_FILE
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
39 #ifdef HAVE_ARPA_INET_H
40 #include <arpa/inet.h>
45 #ifdef HAVE_SYS_IOCTL_H
46 #include <sys/ioctl.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
57 #include "strtoofft.h"
59 #include <curl/curl.h>
64 #include "speedcheck.h"
68 #include "curl_memory.h"
69 #include "parsedate.h" /* for the week day and month names */
72 #define _MPRINTF_REPLACE /* use our functions only */
73 #include <curl/mprintf.h>
75 /* The last #include file should be: */
78 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
79 defined(__SYMBIAN32__)
80 #define DOS_FILESYSTEM 1
83 #ifdef OPEN_NEEDS_ARG3
84 # define open_readonly(p,f) open((p),(f),(0))
86 # define open_readonly(p,f) open((p),(f))
90 * Forward declarations.
93 static CURLcode file_do(struct connectdata *, bool *done);
94 static CURLcode file_done(struct connectdata *conn,
95 CURLcode status, bool premature);
96 static CURLcode file_connect(struct connectdata *conn, bool *done);
97 static CURLcode file_disconnect(struct connectdata *conn,
98 bool dead_connection);
102 * FILE scheme handler.
105 const struct Curl_handler Curl_handler_file = {
107 ZERO_NULL, /* setup_connection */
109 file_done, /* done */
110 ZERO_NULL, /* do_more */
111 file_connect, /* connect_it */
112 ZERO_NULL, /* connecting */
113 ZERO_NULL, /* doing */
114 ZERO_NULL, /* proto_getsock */
115 ZERO_NULL, /* doing_getsock */
116 ZERO_NULL, /* domore_getsock */
117 ZERO_NULL, /* perform_getsock */
118 file_disconnect, /* disconnect */
119 ZERO_NULL, /* readwrite */
121 CURLPROTO_FILE, /* protocol */
122 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
127 Check if this is a range download, and if so, set the internal variables
128 properly. This code is copied from the FTP implementation and might as
129 well be factored out.
131 static CURLcode file_range(struct connectdata *conn)
134 curl_off_t totalsize=-1;
137 struct SessionHandle *data = conn->data;
139 if(data->state.use_range && data->state.range) {
140 from=curlx_strtoofft(data->state.range, &ptr, 0);
141 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
143 to=curlx_strtoofft(ptr, &ptr2, 0);
145 /* we didn't get any digit */
148 if((-1 == to) && (from>=0)) {
150 data->state.resume_from = from;
151 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
156 data->req.maxdownload = -from;
157 data->state.resume_from = from;
158 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
164 data->req.maxdownload = totalsize+1; /* include last byte */
165 data->state.resume_from = from;
166 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
167 " getting %" FORMAT_OFF_T " bytes\n",
168 from, data->req.maxdownload));
170 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
171 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
172 from, to, data->req.maxdownload));
175 data->req.maxdownload = -1;
180 * file_connect() gets called from Curl_protocol_connect() to allow us to
181 * do protocol-specific actions at connect-time. We emulate a
182 * connect-then-transfer protocol and "connect" to the file here
184 static CURLcode file_connect(struct connectdata *conn, bool *done)
186 struct SessionHandle *data = conn->data;
188 struct FILEPROTO *file;
190 #ifdef DOS_FILESYSTEM
195 /* If there already is a protocol-specific struct allocated for this
196 sessionhandle, deal with it */
197 Curl_reset_reqproto(conn);
199 real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
201 return CURLE_OUT_OF_MEMORY;
203 if(!data->state.proto.file) {
204 file = calloc(1, sizeof(struct FILEPROTO));
207 return CURLE_OUT_OF_MEMORY;
209 data->state.proto.file = file;
212 /* file is not a protocol that can deal with "persistancy" */
213 file = data->state.proto.file;
214 Curl_safefree(file->freepath);
221 #ifdef DOS_FILESYSTEM
222 /* If the first character is a slash, and there's
223 something that looks like a drive at the beginning of
224 the path, skip the slash. If we remove the initial
225 slash in all cases, paths without drive letters end up
226 relative to the current directory which isn't how
229 Some browsers accept | instead of : as the drive letter
230 separator, so we do too.
232 On other platforms, we need the slash to indicate an
233 absolute pathname. On Windows, absolute paths start
236 actual_path = real_path;
237 if((actual_path[0] == '/') &&
239 (actual_path[2] == ':' || actual_path[2] == '|')) {
240 actual_path[2] = ':';
244 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
245 for(i=0; actual_path[i] != '\0'; ++i)
246 if(actual_path[i] == '/')
247 actual_path[i] = '\\';
249 fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
250 file->path = actual_path;
252 fd = open_readonly(real_path, O_RDONLY);
253 file->path = real_path;
255 file->freepath = real_path; /* free this when done */
258 if(!data->set.upload && (fd == -1)) {
259 failf(data, "Couldn't open file %s", data->state.path);
260 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
261 return CURLE_FILE_COULDNT_READ_FILE;
268 static CURLcode file_done(struct connectdata *conn,
269 CURLcode status, bool premature)
271 struct FILEPROTO *file = conn->data->state.proto.file;
272 (void)status; /* not used */
273 (void)premature; /* not used */
276 Curl_safefree(file->freepath);
286 static CURLcode file_disconnect(struct connectdata *conn,
287 bool dead_connection)
289 struct FILEPROTO *file = conn->data->state.proto.file;
290 (void)dead_connection; /* not used */
293 Curl_safefree(file->freepath);
303 #ifdef DOS_FILESYSTEM
309 static CURLcode file_upload(struct connectdata *conn)
311 struct FILEPROTO *file = conn->data->state.proto.file;
312 const char *dir = strchr(file->path, DIRSEP);
315 CURLcode res=CURLE_OK;
316 struct SessionHandle *data = conn->data;
317 char *buf = data->state.buffer;
320 curl_off_t bytecount = 0;
321 struct timeval now = Curl_tvnow();
322 struct_stat file_stat;
326 * Since FILE: doesn't do the full init, we need to provide some extra
329 conn->fread_func = data->set.fread_func;
330 conn->fread_in = data->set.in;
331 conn->data->req.upload_fromhere = buf;
334 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
337 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
340 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
342 #define MODE_DEFAULT O_WRONLY|O_CREAT
345 if(data->state.resume_from)
346 mode = MODE_DEFAULT|O_APPEND;
348 mode = MODE_DEFAULT|O_TRUNC;
350 fd = open(file->path, mode, conn->data->set.new_file_perms);
352 failf(data, "Can't open %s for writing", file->path);
353 return CURLE_WRITE_ERROR;
356 if(-1 != data->set.infilesize)
357 /* known size of data to "upload" */
358 Curl_pgrsSetUploadSize(data, data->set.infilesize);
360 /* treat the negative resume offset value as the case of "-" */
361 if(data->state.resume_from < 0) {
362 if(fstat(fd, &file_stat)) {
364 failf(data, "Can't get the size of %s", file->path);
365 return CURLE_WRITE_ERROR;
368 data->state.resume_from = (curl_off_t)file_stat.st_size;
371 while(res == CURLE_OK) {
373 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
377 if(readcount <= 0) /* fix questionable compare error. curlvms */
380 nread = (size_t)readcount;
382 /*skip bytes before resume point*/
383 if(data->state.resume_from) {
384 if((curl_off_t)nread <= data->state.resume_from ) {
385 data->state.resume_from -= nread;
390 buf2 = buf + data->state.resume_from;
391 nread -= (size_t)data->state.resume_from;
392 data->state.resume_from = 0;
398 /* write the data to the target */
399 nwrite = write(fd, buf2, nread);
400 if(nwrite != nread) {
401 res = CURLE_SEND_ERROR;
407 Curl_pgrsSetUploadCounter(data, bytecount);
409 if(Curl_pgrsUpdate(conn))
410 res = CURLE_ABORTED_BY_CALLBACK;
412 res = Curl_speedcheck(data, now);
414 if(!res && Curl_pgrsUpdate(conn))
415 res = CURLE_ABORTED_BY_CALLBACK;
423 * file_do() is the protocol-specific function for the do-phase, separated
424 * from the connect-phase above. Other protocols merely setup the transfer in
425 * the do-phase, to have it done in the main transfer loop but since some
426 * platforms we support don't allow select()ing etc on file handles (as
427 * opposed to sockets) we instead perform the whole do-operation in this
430 static CURLcode file_do(struct connectdata *conn, bool *done)
432 /* This implementation ignores the host name in conformance with
433 RFC 1738. Only local files (reachable via the standard file system)
434 are supported. This means that files on remotely mounted directories
435 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
437 CURLcode res = CURLE_OK;
438 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
439 Windows version to have a different struct without
440 having to redefine the simple word 'stat' */
441 curl_off_t expected_size=0;
444 struct SessionHandle *data = conn->data;
445 char *buf = data->state.buffer;
446 curl_off_t bytecount = 0;
448 struct timeval now = Curl_tvnow();
450 *done = TRUE; /* unconditionally */
453 Curl_pgrsStartNow(data);
456 return file_upload(conn);
458 /* get the fd from the connection phase */
459 fd = conn->data->state.proto.file->fd;
461 /* VMS: This only works reliable for STREAMLF files */
462 if(-1 != fstat(fd, &statbuf)) {
463 /* we could stat it, then read out the size */
464 expected_size = statbuf.st_size;
465 /* and store the modification time */
466 data->info.filetime = (long)statbuf.st_mtime;
470 if(fstated && !data->state.range && data->set.timecondition) {
471 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
477 /* If we have selected NOBODY and HEADER, it means that we only want file
478 information. Which for FILE can't be much more than the file size and
480 if(data->set.opt_no_body && data->set.include_header && fstated) {
482 snprintf(buf, sizeof(data->state.buffer),
483 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
484 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
488 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
489 (char *)"Accept-ranges: bytes\r\n", 0);
494 time_t filetime = (time_t)statbuf.st_mtime;
496 const struct tm *tm = &buffer;
497 result = Curl_gmtime(filetime, &buffer);
501 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
502 snprintf(buf, BUFSIZE-1,
503 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
504 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
506 Curl_month[tm->tm_mon],
511 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
513 /* if we fstat()ed the file, set the file size to make it available post-
516 Curl_pgrsSetDownloadSize(data, expected_size);
520 /* Check whether file range has been specified */
523 /* Adjust the start offset in case we want to get the N last bytes
524 * of the stream iff the filesize could be determined */
525 if(data->state.resume_from < 0) {
527 failf(data, "Can't get the size of file.");
528 return CURLE_READ_ERROR;
531 data->state.resume_from += (curl_off_t)statbuf.st_size;
534 if(data->state.resume_from <= expected_size)
535 expected_size -= data->state.resume_from;
537 failf(data, "failed to resume file:// transfer");
538 return CURLE_BAD_DOWNLOAD_RESUME;
541 /* A high water mark has been specified so we obey... */
542 if(data->req.maxdownload > 0)
543 expected_size = data->req.maxdownload;
545 if(fstated && (expected_size == 0))
548 /* The following is a shortcut implementation of file reading
549 this is both more efficient than the former call to download() and
550 it avoids problems with select() and recv() on file descriptors
553 Curl_pgrsSetDownloadSize(data, expected_size);
555 if(data->state.resume_from) {
556 if(data->state.resume_from !=
557 lseek(fd, data->state.resume_from, SEEK_SET))
558 return CURLE_BAD_DOWNLOAD_RESUME;
561 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
563 while(res == CURLE_OK) {
564 /* Don't fill a whole buffer if we want less than all data */
566 (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
567 curlx_sotouz(expected_size) : BUFSIZE - 1;
569 nread = read(fd, buf, bytestoread);
574 if(nread <= 0 || expected_size == 0)
578 expected_size -= nread;
580 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
584 Curl_pgrsSetDownloadCounter(data, bytecount);
586 if(Curl_pgrsUpdate(conn))
587 res = CURLE_ABORTED_BY_CALLBACK;
589 res = Curl_speedcheck(data, now);
591 if(Curl_pgrsUpdate(conn))
592 res = CURLE_ABORTED_BY_CALLBACK;