1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 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. 13 * 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. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ***************************************************************************/ 22 23#include "setup.h" 24 25#ifndef CURL_DISABLE_FILE 26/* -- WIN32 approved -- */ 27#include <stdio.h> 28#include <string.h> 29#include <stdarg.h> 30#include <stdlib.h> 31#include <ctype.h> 32 33#ifdef WIN32 34#include <time.h> 35#include <io.h> 36#include <fcntl.h> 37#else 38#ifdef HAVE_SYS_SOCKET_H 39#include <sys/socket.h> 40#endif 41#ifdef HAVE_NETINET_IN_H 42#include <netinet/in.h> 43#endif 44#ifdef HAVE_SYS_TIME_H 45#include <sys/time.h> 46#endif 47#ifdef HAVE_UNISTD_H 48#include <unistd.h> 49#endif 50#ifdef HAVE_NETDB_H 51#include <netdb.h> 52#endif 53#ifdef HAVE_ARPA_INET_H 54#include <arpa/inet.h> 55#endif 56#ifdef HAVE_NET_IF_H 57#include <net/if.h> 58#endif 59#ifdef HAVE_SYS_IOCTL_H 60#include <sys/ioctl.h> 61#endif 62 63#ifdef HAVE_SYS_PARAM_H 64#include <sys/param.h> 65#endif 66 67#ifdef HAVE_FCNTL_H 68#include <fcntl.h> 69#endif 70 71#endif /* WIN32 */ 72 73#include "strtoofft.h" 74#include "urldata.h" 75#include <curl/curl.h> 76#include "progress.h" 77#include "sendf.h" 78#include "escape.h" 79#include "file.h" 80#include "speedcheck.h" 81#include "getinfo.h" 82#include "transfer.h" 83#include "url.h" 84#include "curl_memory.h" 85#include "parsedate.h" /* for the week day and month names */ 86 87#define _MPRINTF_REPLACE /* use our functions only */ 88#include <curl/mprintf.h> 89 90/* The last #include file should be: */ 91#include "memdebug.h" 92 93#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \ 94 defined(__SYMBIAN32__) 95#define DOS_FILESYSTEM 1 96#endif 97 98#ifdef OPEN_NEEDS_ARG3 99# define open_readonly(p,f) open((p),(f),(0)) 100#else 101# define open_readonly(p,f) open((p),(f)) 102#endif 103 104/* 105 * Forward declarations. 106 */ 107 108static CURLcode file_do(struct connectdata *, bool *done); 109static CURLcode file_done(struct connectdata *conn, 110 CURLcode status, bool premature); 111static CURLcode file_connect(struct connectdata *conn, bool *done); 112 113/* 114 * FILE scheme handler. 115 */ 116 117const struct Curl_handler Curl_handler_file = { 118 "FILE", /* scheme */ 119 ZERO_NULL, /* setup_connection */ 120 file_do, /* do_it */ 121 file_done, /* done */ 122 ZERO_NULL, /* do_more */ 123 file_connect, /* connect_it */ 124 ZERO_NULL, /* connecting */ 125 ZERO_NULL, /* doing */ 126 ZERO_NULL, /* proto_getsock */ 127 ZERO_NULL, /* doing_getsock */ 128 ZERO_NULL, /* perform_getsock */ 129 ZERO_NULL, /* disconnect */ 130 ZERO_NULL, /* readwrite */ 131 0, /* defport */ 132 CURLPROTO_FILE, /* protocol */ 133 PROTOPT_NONETWORK /* flags */ 134}; 135 136 137 /* 138 Check if this is a range download, and if so, set the internal variables 139 properly. This code is copied from the FTP implementation and might as 140 well be factored out. 141 */ 142static CURLcode file_range(struct connectdata *conn) 143{ 144 curl_off_t from, to; 145 curl_off_t totalsize=-1; 146 char *ptr; 147 char *ptr2; 148 struct SessionHandle *data = conn->data; 149 150 if(data->state.use_range && data->state.range) { 151 from=curlx_strtoofft(data->state.range, &ptr, 0); 152 while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) 153 ptr++; 154 to=curlx_strtoofft(ptr, &ptr2, 0); 155 if(ptr == ptr2) { 156 /* we didn't get any digit */ 157 to=-1; 158 } 159 if((-1 == to) && (from>=0)) { 160 /* X - */ 161 data->state.resume_from = from; 162 DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n", 163 from)); 164 } 165 else if(from < 0) { 166 /* -Y */ 167 data->req.maxdownload = -from; 168 data->state.resume_from = from; 169 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n", 170 -from)); 171 } 172 else { 173 /* X-Y */ 174 totalsize = to-from; 175 data->req.maxdownload = totalsize+1; /* include last byte */ 176 data->state.resume_from = from; 177 DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T 178 " getting %" FORMAT_OFF_T " bytes\n", 179 from, data->req.maxdownload)); 180 } 181 DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T 182 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", 183 from, to, data->req.maxdownload)); 184 } 185 else 186 data->req.maxdownload = -1; 187 return CURLE_OK; 188} 189 190/* 191 * file_connect() gets called from Curl_protocol_connect() to allow us to 192 * do protocol-specific actions at connect-time. We emulate a 193 * connect-then-transfer protocol and "connect" to the file here 194 */ 195static CURLcode file_connect(struct connectdata *conn, bool *done) 196{ 197 struct SessionHandle *data = conn->data; 198 char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL); 199 struct FILEPROTO *file; 200 int fd; 201#ifdef DOS_FILESYSTEM 202 int i; 203 char *actual_path; 204#endif 205 206 if(!real_path) 207 return CURLE_OUT_OF_MEMORY; 208 209 /* If there already is a protocol-specific struct allocated for this 210 sessionhandle, deal with it */ 211 Curl_reset_reqproto(conn); 212 213 if(!data->state.proto.file) { 214 file = calloc(1, sizeof(struct FILEPROTO)); 215 if(!file) { 216 free(real_path); 217 return CURLE_OUT_OF_MEMORY; 218 } 219 data->state.proto.file = file; 220 } 221 else { 222 /* file is not a protocol that can deal with "persistancy" */ 223 file = data->state.proto.file; 224 Curl_safefree(file->freepath); 225 if(file->fd != -1) 226 close(file->fd); 227 file->path = NULL; 228 file->freepath = NULL; 229 file->fd = -1; 230 } 231 232#ifdef DOS_FILESYSTEM 233 /* If the first character is a slash, and there's 234 something that looks like a drive at the beginning of 235 the path, skip the slash. If we remove the initial 236 slash in all cases, paths without drive letters end up 237 relative to the current directory which isn't how 238 browsers work. 239 240 Some browsers accept | instead of : as the drive letter 241 separator, so we do too. 242 243 On other platforms, we need the slash to indicate an 244 absolute pathname. On Windows, absolute paths start 245 with a drive letter. 246 */ 247 actual_path = real_path; 248 if((actual_path[0] == '/') && 249 actual_path[1] && 250 (actual_path[2] == ':' || actual_path[2] == '|')) { 251 actual_path[2] = ':'; 252 actual_path++; 253 } 254 255 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ 256 for(i=0; actual_path[i] != '\0'; ++i) 257 if(actual_path[i] == '/') 258 actual_path[i] = '\\'; 259 260 fd = open_readonly(actual_path, O_RDONLY|O_BINARY); 261 file->path = actual_path; 262#else 263 fd = open_readonly(real_path, O_RDONLY); 264 file->path = real_path; 265#endif 266 file->freepath = real_path; /* free this when done */ 267 268 file->fd = fd; 269 if(!data->set.upload && (fd == -1)) { 270 failf(data, "Couldn't open file %s", data->state.path); 271 file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); 272 return CURLE_FILE_COULDNT_READ_FILE; 273 } 274 *done = TRUE; 275 276 return CURLE_OK; 277} 278 279static CURLcode file_done(struct connectdata *conn, 280 CURLcode status, bool premature) 281{ 282 struct FILEPROTO *file = conn->data->state.proto.file; 283 (void)status; /* not used */ 284 (void)premature; /* not used */ 285 Curl_safefree(file->freepath); 286 287 if(file->fd != -1) 288 close(file->fd); 289 290 return CURLE_OK; 291} 292 293#ifdef DOS_FILESYSTEM 294#define DIRSEP '\\' 295#else 296#define DIRSEP '/' 297#endif 298 299static CURLcode file_upload(struct connectdata *conn) 300{ 301 struct FILEPROTO *file = conn->data->state.proto.file; 302 const char *dir = strchr(file->path, DIRSEP); 303 FILE *fp; 304 CURLcode res=CURLE_OK; 305 struct SessionHandle *data = conn->data; 306 char *buf = data->state.buffer; 307 size_t nread; 308 size_t nwrite; 309 curl_off_t bytecount = 0; 310 struct timeval now = Curl_tvnow(); 311 struct_stat file_stat; 312 const char* buf2; 313 314 /* 315 * Since FILE: doesn't do the full init, we need to provide some extra 316 * assignments here. 317 */ 318 conn->fread_func = data->set.fread_func; 319 conn->fread_in = data->set.in; 320 conn->data->req.upload_fromhere = buf; 321 322 if(!dir) 323 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ 324 325 if(!dir[1]) 326 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ 327 328 if(data->state.resume_from) 329 fp = fopen( file->path, "ab" ); 330 else { 331 int fd; 332 333#ifdef DOS_FILESYSTEM 334 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 335 conn->data->set.new_file_perms); 336#else 337 fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC, 338 conn->data->set.new_file_perms); 339#endif 340 if(fd < 0) { 341 failf(data, "Can't open %s for writing", file->path); 342 return CURLE_WRITE_ERROR; 343 } 344 close(fd); 345 fp = fopen(file->path, "wb"); 346 } 347 348 if(!fp) { 349 failf(data, "Can't open %s for writing", file->path); 350 return CURLE_WRITE_ERROR; 351 } 352 353 if(-1 != data->set.infilesize) 354 /* known size of data to "upload" */ 355 Curl_pgrsSetUploadSize(data, data->set.infilesize); 356 357 /* treat the negative resume offset value as the case of "-" */ 358 if(data->state.resume_from < 0) { 359 if(fstat(fileno(fp), &file_stat)) { 360 fclose(fp); 361 failf(data, "Can't get the size of %s", file->path); 362 return CURLE_WRITE_ERROR; 363 } 364 else 365 data->state.resume_from = (curl_off_t)file_stat.st_size; 366 } 367 368 while(res == CURLE_OK) { 369 int readcount; 370 res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount); 371 if(res) 372 break; 373 374 if(readcount <= 0) /* fix questionable compare error. curlvms */ 375 break; 376 377 nread = (size_t)readcount; 378 379 /*skip bytes before resume point*/ 380 if(data->state.resume_from) { 381 if((curl_off_t)nread <= data->state.resume_from ) { 382 data->state.resume_from -= nread; 383 nread = 0; 384 buf2 = buf; 385 } 386 else { 387 buf2 = buf + data->state.resume_from; 388 nread -= (size_t)data->state.resume_from; 389 data->state.resume_from = 0; 390 } 391 } 392 else 393 buf2 = buf; 394 395 /* write the data to the target */ 396 nwrite = fwrite(buf2, 1, nread, fp); 397 if(nwrite != nread) { 398 res = CURLE_SEND_ERROR; 399 break; 400 } 401 402 bytecount += nread; 403 404 Curl_pgrsSetUploadCounter(data, bytecount); 405 406 if(Curl_pgrsUpdate(conn)) 407 res = CURLE_ABORTED_BY_CALLBACK; 408 else 409 res = Curl_speedcheck(data, now); 410 } 411 if(!res && Curl_pgrsUpdate(conn)) 412 res = CURLE_ABORTED_BY_CALLBACK; 413 414 fclose(fp); 415 416 return res; 417} 418 419/* 420 * file_do() is the protocol-specific function for the do-phase, separated 421 * from the connect-phase above. Other protocols merely setup the transfer in 422 * the do-phase, to have it done in the main transfer loop but since some 423 * platforms we support don't allow select()ing etc on file handles (as 424 * opposed to sockets) we instead perform the whole do-operation in this 425 * function. 426 */ 427static CURLcode file_do(struct connectdata *conn, bool *done) 428{ 429 /* This implementation ignores the host name in conformance with 430 RFC 1738. Only local files (reachable via the standard file system) 431 are supported. This means that files on remotely mounted directories 432 (via NFS, Samba, NT sharing) can be accessed through a file:// URL 433 */ 434 CURLcode res = CURLE_OK; 435 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the 436 Windows version to have a different struct without 437 having to redefine the simple word 'stat' */ 438 curl_off_t expected_size=0; 439 bool fstated=FALSE; 440 ssize_t nread; 441 size_t bytestoread; 442 struct SessionHandle *data = conn->data; 443 char *buf = data->state.buffer; 444 curl_off_t bytecount = 0; 445 int fd; 446 struct timeval now = Curl_tvnow(); 447 448 *done = TRUE; /* unconditionally */ 449 450 Curl_initinfo(data); 451 Curl_pgrsStartNow(data); 452 453 if(data->set.upload) 454 return file_upload(conn); 455 456 /* get the fd from the connection phase */ 457 fd = conn->data->state.proto.file->fd; 458 459 /* VMS: This only works reliable for STREAMLF files */ 460 if(-1 != fstat(fd, &statbuf)) { 461 /* we could stat it, then read out the size */ 462 expected_size = statbuf.st_size; 463 /* and store the modification time */ 464 data->info.filetime = (long)statbuf.st_mtime; 465 fstated = TRUE; 466 } 467 468 if(fstated && !data->state.range && data->set.timecondition) { 469 if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) { 470 *done = TRUE; 471 return CURLE_OK; 472 } 473 } 474 475 /* If we have selected NOBODY and HEADER, it means that we only want file 476 information. Which for FILE can't be much more than the file size and 477 date. */ 478 if(data->set.opt_no_body && data->set.include_header && fstated) { 479 CURLcode result; 480 snprintf(buf, sizeof(data->state.buffer), 481 "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size); 482 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); 483 if(result) 484 return result; 485 486 result = Curl_client_write(conn, CLIENTWRITE_BOTH, 487 (char *)"Accept-ranges: bytes\r\n", 0); 488 if(result) 489 return result; 490 491 if(fstated) { 492 time_t filetime = (time_t)statbuf.st_mtime; 493 struct tm buffer; 494 const struct tm *tm = &buffer; 495 result = Curl_gmtime(filetime, &buffer); 496 if(result) 497 return result; 498 499 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ 500 snprintf(buf, BUFSIZE-1, 501 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", 502 Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], 503 tm->tm_mday, 504 Curl_month[tm->tm_mon], 505 tm->tm_year + 1900, 506 tm->tm_hour, 507 tm->tm_min, 508 tm->tm_sec); 509 result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); 510 } 511 /* if we fstat()ed the file, set the file size to make it available post- 512 transfer */ 513 if(fstated) 514 Curl_pgrsSetDownloadSize(data, expected_size); 515 return result; 516 } 517 518 /* Check whether file range has been specified */ 519 file_range(conn); 520 521 /* Adjust the start offset in case we want to get the N last bytes 522 * of the stream iff the filesize could be determined */ 523 if(data->state.resume_from < 0) { 524 if(!fstated) { 525 failf(data, "Can't get the size of file."); 526 return CURLE_READ_ERROR; 527 } 528 else 529 data->state.resume_from += (curl_off_t)statbuf.st_size; 530 } 531 532 if(data->state.resume_from <= expected_size) 533 expected_size -= data->state.resume_from; 534 else { 535 failf(data, "failed to resume file:// transfer"); 536 return CURLE_BAD_DOWNLOAD_RESUME; 537 } 538 539 /* A high water mark has been specified so we obey... */ 540 if(data->req.maxdownload > 0) 541 expected_size = data->req.maxdownload; 542 543 if(fstated && (expected_size == 0)) 544 return CURLE_OK; 545 546 /* The following is a shortcut implementation of file reading 547 this is both more efficient than the former call to download() and 548 it avoids problems with select() and recv() on file descriptors 549 in Winsock */ 550 if(fstated) 551 Curl_pgrsSetDownloadSize(data, expected_size); 552 553 if(data->state.resume_from) { 554 if(data->state.resume_from != 555 lseek(fd, data->state.resume_from, SEEK_SET)) 556 return CURLE_BAD_DOWNLOAD_RESUME; 557 } 558 559 Curl_pgrsTime(data, TIMER_STARTTRANSFER); 560 561 while(res == CURLE_OK) { 562 /* Don't fill a whole buffer if we want less than all data */ 563 bytestoread = (expected_size < BUFSIZE-1)?(size_t)expected_size:BUFSIZE-1; 564 nread = read(fd, buf, bytestoread); 565 566 if(nread > 0) 567 buf[nread] = 0; 568 569 if(nread <= 0 || expected_size == 0) 570 break; 571 572 bytecount += nread; 573 expected_size -= nread; 574 575 res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); 576 if(res) 577 return res; 578 579 Curl_pgrsSetDownloadCounter(data, bytecount); 580 581 if(Curl_pgrsUpdate(conn)) 582 res = CURLE_ABORTED_BY_CALLBACK; 583 else 584 res = Curl_speedcheck(data, now); 585 } 586 if(Curl_pgrsUpdate(conn)) 587 res = CURLE_ABORTED_BY_CALLBACK; 588 589 return res; 590} 591 592#endif 593