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