1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2008, 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.
22 ***************************************************************************/
26 #ifndef CURL_DISABLE_FILE
27 /* -- WIN32 approved -- */
39 #ifdef HAVE_SYS_SOCKET_H
40 #include <sys/socket.h>
42 #ifdef HAVE_NETINET_IN_H
43 #include <netinet/in.h>
45 #ifdef HAVE_SYS_TIME_H
54 #ifdef HAVE_ARPA_INET_H
55 #include <arpa/inet.h>
60 #include <sys/ioctl.h>
62 #ifdef HAVE_SYS_PARAM_H
63 #include <sys/param.h>
72 #include "strtoofft.h"
74 #include <curl/curl.h>
79 #include "speedcheck.h"
84 #include "parsedate.h" /* for the week day and month names */
86 #define _MPRINTF_REPLACE /* use our functions only */
87 #include <curl/mprintf.h>
89 /* The last #include file should be: */
92 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || defined(__SYMBIAN32__)
93 #define DOS_FILESYSTEM 1
97 * Forward declarations.
100 static CURLcode file_do(struct connectdata *, bool *done);
101 static CURLcode file_done(struct connectdata *conn,
102 CURLcode status, bool premature);
103 static CURLcode file_connect(struct connectdata *conn, bool *done);
106 * FILE scheme handler.
109 const struct Curl_handler Curl_handler_file = {
111 ZERO_NULL, /* setup_connection */
113 file_done, /* done */
114 ZERO_NULL, /* do_more */
115 file_connect, /* connect_it */
116 ZERO_NULL, /* connecting */
117 ZERO_NULL, /* doing */
118 ZERO_NULL, /* proto_getsock */
119 ZERO_NULL, /* doing_getsock */
120 ZERO_NULL, /* disconnect */
122 PROT_FILE /* protocol */
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 && *ptr && (isspace((int)*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",
157 data->req.maxdownload = -from;
158 data->state.resume_from = from;
159 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
165 data->req.maxdownload = totalsize+1; /* include last byte */
166 data->state.resume_from = from;
167 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
168 " getting %" FORMAT_OFF_T " bytes\n",
169 from, data->req.maxdownload));
171 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
172 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
173 from, to, data->req.maxdownload));
176 data->req.maxdownload = -1;
181 * file_connect() gets called from Curl_protocol_connect() to allow us to
182 * do protocol-specific actions at connect-time. We emulate a
183 * connect-then-transfer protocol and "connect" to the file here
185 static CURLcode file_connect(struct connectdata *conn, bool *done)
187 struct SessionHandle *data = conn->data;
188 char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
189 struct FILEPROTO *file;
191 #ifdef DOS_FILESYSTEM
197 return CURLE_OUT_OF_MEMORY;
199 /* If there already is a protocol-specific struct allocated for this
200 sessionhandle, deal with it */
201 Curl_reset_reqproto(conn);
203 if(!data->state.proto.file) {
204 file = calloc(sizeof(struct FILEPROTO), 1);
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);
218 file->freepath = NULL;
222 #ifdef DOS_FILESYSTEM
223 /* If the first character is a slash, and there's
224 something that looks like a drive at the beginning of
225 the path, skip the slash. If we remove the initial
226 slash in all cases, paths without drive letters end up
227 relative to the current directory which isn't how
230 Some browsers accept | instead of : as the drive letter
231 separator, so we do too.
233 On other platforms, we need the slash to indicate an
234 absolute pathname. On Windows, absolute paths start
237 actual_path = real_path;
238 if((actual_path[0] == '/') &&
240 (actual_path[2] == ':' || actual_path[2] == '|'))
242 actual_path[2] = ':';
246 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
247 for (i=0; actual_path[i] != '\0'; ++i)
248 if(actual_path[i] == '/')
249 actual_path[i] = '\\';
251 fd = open(actual_path, O_RDONLY | O_BINARY); /* no CR/LF translation! */
252 file->path = actual_path;
254 fd = open(real_path, O_RDONLY);
255 file->path = real_path;
257 file->freepath = real_path; /* free this when done */
260 if(!data->set.upload && (fd == -1)) {
261 failf(data, "Couldn't open file %s", data->state.path);
262 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
263 return CURLE_FILE_COULDNT_READ_FILE;
270 static CURLcode file_done(struct connectdata *conn,
271 CURLcode status, bool premature)
273 struct FILEPROTO *file = conn->data->state.proto.file;
274 (void)status; /* not used */
275 (void)premature; /* not used */
276 Curl_safefree(file->freepath);
284 #ifdef DOS_FILESYSTEM
290 static CURLcode file_upload(struct connectdata *conn)
292 struct FILEPROTO *file = conn->data->state.proto.file;
293 const char *dir = strchr(file->path, DIRSEP);
295 CURLcode res=CURLE_OK;
296 struct SessionHandle *data = conn->data;
297 char *buf = data->state.buffer;
300 curl_off_t bytecount = 0;
301 struct timeval now = Curl_tvnow();
302 struct_stat file_stat;
306 * Since FILE: doesn't do the full init, we need to provide some extra
309 conn->fread_func = data->set.fread_func;
310 conn->fread_in = data->set.in;
311 conn->data->req.upload_fromhere = buf;
314 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
317 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
319 if(data->state.resume_from)
320 fp = fopen( file->path, "ab" );
324 #ifdef DOS_FILESYSTEM
325 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
326 conn->data->set.new_file_perms);
328 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
329 conn->data->set.new_file_perms);
332 failf(data, "Can't open %s for writing", file->path);
333 return CURLE_WRITE_ERROR;
336 fp = fopen(file->path, "wb");
340 failf(data, "Can't open %s for writing", file->path);
341 return CURLE_WRITE_ERROR;
344 if(-1 != data->set.infilesize)
345 /* known size of data to "upload" */
346 Curl_pgrsSetUploadSize(data, data->set.infilesize);
348 /* treat the negative resume offset value as the case of "-" */
349 if(data->state.resume_from < 0) {
350 if(fstat(fileno(fp), &file_stat)) {
352 failf(data, "Can't get the size of %s", file->path);
353 return CURLE_WRITE_ERROR;
356 data->state.resume_from = (curl_off_t)file_stat.st_size;
359 while(res == CURLE_OK) {
361 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
365 if(readcount <= 0) /* fix questionable compare error. curlvms */
368 nread = (size_t)readcount;
370 /*skip bytes before resume point*/
371 if(data->state.resume_from) {
372 if( (curl_off_t)nread <= data->state.resume_from ) {
373 data->state.resume_from -= nread;
378 buf2 = buf + data->state.resume_from;
379 nread -= (size_t)data->state.resume_from;
380 data->state.resume_from = 0;
386 /* write the data to the target */
387 nwrite = fwrite(buf2, 1, nread, fp);
388 if(nwrite != nread) {
389 res = CURLE_SEND_ERROR;
395 Curl_pgrsSetUploadCounter(data, bytecount);
397 if(Curl_pgrsUpdate(conn))
398 res = CURLE_ABORTED_BY_CALLBACK;
400 res = Curl_speedcheck(data, now);
402 if(!res && Curl_pgrsUpdate(conn))
403 res = CURLE_ABORTED_BY_CALLBACK;
411 * file_do() is the protocol-specific function for the do-phase, separated
412 * from the connect-phase above. Other protocols merely setup the transfer in
413 * the do-phase, to have it done in the main transfer loop but since some
414 * platforms we support don't allow select()ing etc on file handles (as
415 * opposed to sockets) we instead perform the whole do-operation in this
418 static CURLcode file_do(struct connectdata *conn, bool *done)
420 /* This implementation ignores the host name in conformance with
421 RFC 1738. Only local files (reachable via the standard file system)
422 are supported. This means that files on remotely mounted directories
423 (via NFS, Samba, NT sharing) can be accessed through a file:// URL
425 CURLcode res = CURLE_OK;
426 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
427 Windows version to have a different struct without
428 having to redefine the simple word 'stat' */
429 curl_off_t expected_size=0;
433 struct SessionHandle *data = conn->data;
434 char *buf = data->state.buffer;
435 curl_off_t bytecount = 0;
437 struct timeval now = Curl_tvnow();
439 *done = TRUE; /* unconditionally */
442 Curl_pgrsStartNow(data);
445 return file_upload(conn);
447 /* get the fd from the connection phase */
448 fd = conn->data->state.proto.file->fd;
450 /* VMS: This only works reliable for STREAMLF files */
451 if( -1 != fstat(fd, &statbuf)) {
452 /* we could stat it, then read out the size */
453 expected_size = statbuf.st_size;
454 /* and store the modification time */
455 data->info.filetime = (long)statbuf.st_mtime;
459 /* If we have selected NOBODY and HEADER, it means that we only want file
460 information. Which for FILE can't be much more than the file size and
462 if(data->set.opt_no_body && data->set.include_header && fstated) {
464 snprintf(buf, sizeof(data->state.buffer),
465 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
466 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
470 result = Curl_client_write(conn, CLIENTWRITE_BOTH,
471 (char *)"Accept-ranges: bytes\r\n", 0);
477 time_t filetime = (time_t)statbuf.st_mtime;
480 tm = (const struct tm *)gmtime_r(&filetime, &buffer);
482 tm = gmtime(&filetime);
484 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
485 snprintf(buf, BUFSIZE-1,
486 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
487 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
489 Curl_month[tm->tm_mon],
494 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
499 /* Check whether file range has been specified */
502 /* Adjust the start offset in case we want to get the N last bytes
503 * of the stream iff the filesize could be determined */
504 if(data->state.resume_from < 0) {
506 failf(data, "Can't get the size of file.");
507 return CURLE_READ_ERROR;
510 data->state.resume_from += (curl_off_t)statbuf.st_size;
513 if(data->state.resume_from <= expected_size)
514 expected_size -= data->state.resume_from;
516 failf(data, "failed to resume file:// transfer");
517 return CURLE_BAD_DOWNLOAD_RESUME;
520 /* A high water mark has been specified so we obey... */
521 if (data->req.maxdownload > 0)
522 expected_size = data->req.maxdownload;
524 if(fstated && (expected_size == 0))
527 /* The following is a shortcut implementation of file reading
528 this is both more efficient than the former call to download() and
529 it avoids problems with select() and recv() on file descriptors
532 Curl_pgrsSetDownloadSize(data, expected_size);
534 if(data->state.resume_from) {
535 if(data->state.resume_from !=
536 lseek(fd, data->state.resume_from, SEEK_SET))
537 return CURLE_BAD_DOWNLOAD_RESUME;
540 Curl_pgrsTime(data, TIMER_STARTTRANSFER);
542 while(res == CURLE_OK) {
543 /* Don't fill a whole buffer if we want less than all data */
544 bytestoread = (expected_size < BUFSIZE-1)?(size_t)expected_size:BUFSIZE-1;
545 nread = read(fd, buf, bytestoread);
550 if (nread <= 0 || expected_size == 0)
554 expected_size -= nread;
556 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
560 Curl_pgrsSetDownloadCounter(data, bytecount);
562 if(Curl_pgrsUpdate(conn))
563 res = CURLE_ABORTED_BY_CALLBACK;
565 res = Curl_speedcheck(data, now);
567 if(Curl_pgrsUpdate(conn))
568 res = CURLE_ABORTED_BY_CALLBACK;