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