file.c (19162B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 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.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 * SPDX-License-Identifier: curl 22 * 23 ***************************************************************************/ 24 25 #include "curl_setup.h" 26 27 #ifndef CURL_DISABLE_FILE 28 29 #ifdef HAVE_NETINET_IN_H 30 #include <netinet/in.h> 31 #endif 32 #ifdef HAVE_NETDB_H 33 #include <netdb.h> 34 #endif 35 #ifdef HAVE_ARPA_INET_H 36 #include <arpa/inet.h> 37 #endif 38 #ifdef HAVE_NET_IF_H 39 #include <net/if.h> 40 #endif 41 #ifdef HAVE_SYS_IOCTL_H 42 #include <sys/ioctl.h> 43 #endif 44 45 #ifdef HAVE_SYS_PARAM_H 46 #include <sys/param.h> 47 #endif 48 49 #ifdef HAVE_FCNTL_H 50 #include <fcntl.h> 51 #endif 52 53 #ifdef HAVE_SYS_TYPES_H 54 #include <sys/types.h> 55 #endif 56 57 #ifdef HAVE_DIRENT_H 58 #include <dirent.h> 59 #endif 60 61 #include "urldata.h" 62 #include <curl/curl.h> 63 #include "progress.h" 64 #include "sendf.h" 65 #include "escape.h" 66 #include "file.h" 67 #include "speedcheck.h" 68 #include "getinfo.h" 69 #include "multiif.h" 70 #include "transfer.h" 71 #include "url.h" 72 #include "parsedate.h" /* for the week day and month names */ 73 #include "curlx/warnless.h" 74 #include "curl_range.h" 75 /* The last 3 #include files should be in this order */ 76 #include "curl_printf.h" 77 #include "curl_memory.h" 78 #include "memdebug.h" 79 80 #if defined(_WIN32) || defined(MSDOS) 81 #define DOS_FILESYSTEM 1 82 #elif defined(__amigaos4__) 83 #define AMIGA_FILESYSTEM 1 84 #endif 85 86 /* meta key for storing protocol meta at easy handle */ 87 #define CURL_META_FILE_EASY "meta:proto:file:easy" 88 89 struct FILEPROTO { 90 char *path; /* the path we operate on */ 91 char *freepath; /* pointer to the allocated block we must free, this might 92 differ from the 'path' pointer */ 93 int fd; /* open file descriptor to read from! */ 94 }; 95 96 /* 97 * Forward declarations. 98 */ 99 100 static CURLcode file_do(struct Curl_easy *data, bool *done); 101 static CURLcode file_done(struct Curl_easy *data, 102 CURLcode status, bool premature); 103 static CURLcode file_connect(struct Curl_easy *data, bool *done); 104 static CURLcode file_disconnect(struct Curl_easy *data, 105 struct connectdata *conn, 106 bool dead_connection); 107 static CURLcode file_setup_connection(struct Curl_easy *data, 108 struct connectdata *conn); 109 110 /* 111 * FILE scheme handler. 112 */ 113 114 const struct Curl_handler Curl_handler_file = { 115 "file", /* scheme */ 116 file_setup_connection, /* setup_connection */ 117 file_do, /* do_it */ 118 file_done, /* done */ 119 ZERO_NULL, /* do_more */ 120 file_connect, /* connect_it */ 121 ZERO_NULL, /* connecting */ 122 ZERO_NULL, /* doing */ 123 ZERO_NULL, /* proto_getsock */ 124 ZERO_NULL, /* doing_getsock */ 125 ZERO_NULL, /* domore_getsock */ 126 ZERO_NULL, /* perform_getsock */ 127 file_disconnect, /* disconnect */ 128 ZERO_NULL, /* write_resp */ 129 ZERO_NULL, /* write_resp_hd */ 130 ZERO_NULL, /* connection_check */ 131 ZERO_NULL, /* attach connection */ 132 ZERO_NULL, /* follow */ 133 0, /* defport */ 134 CURLPROTO_FILE, /* protocol */ 135 CURLPROTO_FILE, /* family */ 136 PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */ 137 }; 138 139 140 static void file_cleanup(struct FILEPROTO *file) 141 { 142 Curl_safefree(file->freepath); 143 file->path = NULL; 144 if(file->fd != -1) { 145 close(file->fd); 146 file->fd = -1; 147 } 148 } 149 150 static void file_easy_dtor(void *key, size_t klen, void *entry) 151 { 152 struct FILEPROTO *file = entry; 153 (void)key; 154 (void)klen; 155 file_cleanup(file); 156 free(file); 157 } 158 159 static CURLcode file_setup_connection(struct Curl_easy *data, 160 struct connectdata *conn) 161 { 162 struct FILEPROTO *filep; 163 (void)conn; 164 /* allocate the FILE specific struct */ 165 filep = calloc(1, sizeof(*filep)); 166 if(!filep || 167 Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor)) 168 return CURLE_OUT_OF_MEMORY; 169 170 return CURLE_OK; 171 } 172 173 /* 174 * file_connect() gets called from Curl_protocol_connect() to allow us to 175 * do protocol-specific actions at connect-time. We emulate a 176 * connect-then-transfer protocol and "connect" to the file here 177 */ 178 static CURLcode file_connect(struct Curl_easy *data, bool *done) 179 { 180 char *real_path; 181 struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); 182 int fd; 183 #ifdef DOS_FILESYSTEM 184 size_t i; 185 char *actual_path; 186 #endif 187 size_t real_path_len; 188 CURLcode result; 189 190 if(!file) 191 return CURLE_FAILED_INIT; 192 193 if(file->path) { 194 /* already connected. 195 * the handler->connect_it() is normally only called once, but 196 * FILE does a special check on setting up the connection which 197 * calls this explicitly. */ 198 *done = TRUE; 199 return CURLE_OK; 200 } 201 202 result = Curl_urldecode(data->state.up.path, 0, &real_path, 203 &real_path_len, REJECT_ZERO); 204 if(result) 205 return result; 206 207 #ifdef DOS_FILESYSTEM 208 /* If the first character is a slash, and there is 209 something that looks like a drive at the beginning of 210 the path, skip the slash. If we remove the initial 211 slash in all cases, paths without drive letters end up 212 relative to the current directory which is not how 213 browsers work. 214 215 Some browsers accept | instead of : as the drive letter 216 separator, so we do too. 217 218 On other platforms, we need the slash to indicate an 219 absolute pathname. On Windows, absolute paths start 220 with a drive letter. 221 */ 222 actual_path = real_path; 223 if((actual_path[0] == '/') && 224 actual_path[1] && 225 (actual_path[2] == ':' || actual_path[2] == '|')) { 226 actual_path[2] = ':'; 227 actual_path++; 228 real_path_len--; 229 } 230 231 /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ 232 for(i = 0; i < real_path_len; ++i) 233 if(actual_path[i] == '/') 234 actual_path[i] = '\\'; 235 else if(!actual_path[i]) { /* binary zero */ 236 Curl_safefree(real_path); 237 return CURLE_URL_MALFORMAT; 238 } 239 240 fd = open(actual_path, O_RDONLY|CURL_O_BINARY); 241 file->path = actual_path; 242 #else 243 if(memchr(real_path, 0, real_path_len)) { 244 /* binary zeroes indicate foul play */ 245 Curl_safefree(real_path); 246 return CURLE_URL_MALFORMAT; 247 } 248 249 #ifdef AMIGA_FILESYSTEM 250 /* 251 * A leading slash in an AmigaDOS path denotes the parent 252 * directory, and hence we block this as it is relative. 253 * Absolute paths start with 'volumename:', so we check for 254 * this first. Failing that, we treat the path as a real Unix 255 * path, but only if the application was compiled with -lunix. 256 */ 257 fd = -1; 258 file->path = real_path; 259 260 if(real_path[0] == '/') { 261 extern int __unix_path_semantics; 262 if(strchr(real_path + 1, ':')) { 263 /* Amiga absolute path */ 264 fd = open(real_path + 1, O_RDONLY); 265 file->path++; 266 } 267 else if(__unix_path_semantics) { 268 /* -lunix fallback */ 269 fd = open(real_path, O_RDONLY); 270 } 271 } 272 #else 273 fd = open(real_path, O_RDONLY); 274 file->path = real_path; 275 #endif 276 #endif 277 free(file->freepath); 278 file->freepath = real_path; /* free this when done */ 279 280 file->fd = fd; 281 if(!data->state.upload && (fd == -1)) { 282 failf(data, "Couldn't open file %s", data->state.up.path); 283 file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE); 284 return CURLE_FILE_COULDNT_READ_FILE; 285 } 286 *done = TRUE; 287 288 return CURLE_OK; 289 } 290 291 static CURLcode file_done(struct Curl_easy *data, 292 CURLcode status, bool premature) 293 { 294 struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); 295 (void)status; /* not used */ 296 (void)premature; /* not used */ 297 298 if(file) 299 file_cleanup(file); 300 301 return CURLE_OK; 302 } 303 304 static CURLcode file_disconnect(struct Curl_easy *data, 305 struct connectdata *conn, 306 bool dead_connection) 307 { 308 (void)dead_connection; /* not used */ 309 (void)conn; 310 return file_done(data, CURLE_OK, FALSE); 311 } 312 313 #ifdef DOS_FILESYSTEM 314 #define DIRSEP '\\' 315 #else 316 #define DIRSEP '/' 317 #endif 318 319 static CURLcode file_upload(struct Curl_easy *data, 320 struct FILEPROTO *file) 321 { 322 const char *dir = strchr(file->path, DIRSEP); 323 int fd; 324 int mode; 325 CURLcode result = CURLE_OK; 326 char *xfer_ulbuf; 327 size_t xfer_ulblen; 328 curl_off_t bytecount = 0; 329 struct_stat file_stat; 330 const char *sendbuf; 331 bool eos = FALSE; 332 333 /* 334 * Since FILE: does not do the full init, we need to provide some extra 335 * assignments here. 336 */ 337 338 if(!dir) 339 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ 340 341 if(!dir[1]) 342 return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ 343 344 mode = O_WRONLY|O_CREAT|CURL_O_BINARY; 345 if(data->state.resume_from) 346 mode |= O_APPEND; 347 else 348 mode |= O_TRUNC; 349 350 #if (defined(ANDROID) || defined(__ANDROID__)) && \ 351 (defined(__i386__) || defined(__arm__)) 352 fd = open(file->path, mode, (mode_t)data->set.new_file_perms); 353 #else 354 fd = open(file->path, mode, data->set.new_file_perms); 355 #endif 356 if(fd < 0) { 357 failf(data, "cannot open %s for writing", file->path); 358 return CURLE_WRITE_ERROR; 359 } 360 361 if(-1 != data->state.infilesize) 362 /* known size of data to "upload" */ 363 Curl_pgrsSetUploadSize(data, data->state.infilesize); 364 365 /* treat the negative resume offset value as the case of "-" */ 366 if(data->state.resume_from < 0) { 367 if(fstat(fd, &file_stat)) { 368 close(fd); 369 failf(data, "cannot get the size of %s", file->path); 370 return CURLE_WRITE_ERROR; 371 } 372 data->state.resume_from = (curl_off_t)file_stat.st_size; 373 } 374 375 result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); 376 if(result) 377 goto out; 378 379 while(!result && !eos) { 380 size_t nread; 381 ssize_t nwrite; 382 size_t readcount; 383 384 result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos); 385 if(result) 386 break; 387 388 if(!readcount) 389 break; 390 391 nread = readcount; 392 393 /* skip bytes before resume point */ 394 if(data->state.resume_from) { 395 if((curl_off_t)nread <= data->state.resume_from) { 396 data->state.resume_from -= nread; 397 nread = 0; 398 sendbuf = xfer_ulbuf; 399 } 400 else { 401 sendbuf = xfer_ulbuf + data->state.resume_from; 402 nread -= (size_t)data->state.resume_from; 403 data->state.resume_from = 0; 404 } 405 } 406 else 407 sendbuf = xfer_ulbuf; 408 409 /* write the data to the target */ 410 nwrite = write(fd, sendbuf, nread); 411 if((size_t)nwrite != nread) { 412 result = CURLE_SEND_ERROR; 413 break; 414 } 415 416 bytecount += nread; 417 418 Curl_pgrsSetUploadCounter(data, bytecount); 419 420 if(Curl_pgrsUpdate(data)) 421 result = CURLE_ABORTED_BY_CALLBACK; 422 else 423 result = Curl_speedcheck(data, curlx_now()); 424 } 425 if(!result && Curl_pgrsUpdate(data)) 426 result = CURLE_ABORTED_BY_CALLBACK; 427 428 out: 429 close(fd); 430 Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); 431 432 return result; 433 } 434 435 /* 436 * file_do() is the protocol-specific function for the do-phase, separated 437 * from the connect-phase above. Other protocols merely setup the transfer in 438 * the do-phase, to have it done in the main transfer loop but since some 439 * platforms we support do not allow select()ing etc on file handles (as 440 * opposed to sockets) we instead perform the whole do-operation in this 441 * function. 442 */ 443 static CURLcode file_do(struct Curl_easy *data, bool *done) 444 { 445 /* This implementation ignores the hostname in conformance with 446 RFC 1738. Only local files (reachable via the standard file system) 447 are supported. This means that files on remotely mounted directories 448 (via NFS, Samba, NT sharing) can be accessed through a file:// URL 449 */ 450 struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); 451 CURLcode result = CURLE_OK; 452 struct_stat statbuf; /* struct_stat instead of struct stat just to allow the 453 Windows version to have a different struct without 454 having to redefine the simple word 'stat' */ 455 curl_off_t expected_size = -1; 456 bool size_known; 457 bool fstated = FALSE; 458 int fd; 459 char *xfer_buf; 460 size_t xfer_blen; 461 462 *done = TRUE; /* unconditionally */ 463 if(!file) 464 return CURLE_FAILED_INIT; 465 466 if(data->state.upload) 467 return file_upload(data, file); 468 469 /* get the fd from the connection phase */ 470 fd = file->fd; 471 472 /* VMS: This only works reliable for STREAMLF files */ 473 if(-1 != fstat(fd, &statbuf)) { 474 if(!S_ISDIR(statbuf.st_mode)) 475 expected_size = statbuf.st_size; 476 /* and store the modification time */ 477 data->info.filetime = statbuf.st_mtime; 478 fstated = TRUE; 479 } 480 481 if(fstated && !data->state.range && data->set.timecondition && 482 !Curl_meets_timecondition(data, data->info.filetime)) 483 return CURLE_OK; 484 485 if(fstated) { 486 time_t filetime; 487 struct tm buffer; 488 const struct tm *tm = &buffer; 489 char header[80]; 490 int headerlen; 491 static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" }; 492 if(expected_size >= 0) { 493 headerlen = 494 msnprintf(header, sizeof(header), "Content-Length: %" FMT_OFF_T "\r\n", 495 expected_size); 496 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); 497 if(result) 498 return result; 499 500 result = Curl_client_write(data, CLIENTWRITE_HEADER, 501 accept_ranges, sizeof(accept_ranges) - 1); 502 if(result != CURLE_OK) 503 return result; 504 } 505 506 filetime = (time_t)statbuf.st_mtime; 507 result = Curl_gmtime(filetime, &buffer); 508 if(result) 509 return result; 510 511 /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ 512 headerlen = 513 msnprintf(header, sizeof(header), 514 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", 515 Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], 516 tm->tm_mday, 517 Curl_month[tm->tm_mon], 518 tm->tm_year + 1900, 519 tm->tm_hour, 520 tm->tm_min, 521 tm->tm_sec); 522 result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); 523 if(!result) 524 /* end of headers */ 525 result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); 526 if(result) 527 return result; 528 /* set the file size to make it available post transfer */ 529 Curl_pgrsSetDownloadSize(data, expected_size); 530 if(data->req.no_body) 531 return CURLE_OK; 532 } 533 534 /* Check whether file range has been specified */ 535 result = Curl_range(data); 536 if(result) 537 return result; 538 539 /* Adjust the start offset in case we want to get the N last bytes 540 * of the stream if the filesize could be determined */ 541 if(data->state.resume_from < 0) { 542 if(!fstated) { 543 failf(data, "cannot get the size of file."); 544 return CURLE_READ_ERROR; 545 } 546 data->state.resume_from += (curl_off_t)statbuf.st_size; 547 } 548 549 if(data->state.resume_from > 0) { 550 /* We check explicitly if we have a start offset, because 551 * expected_size may be -1 if we do not know how large the file is, 552 * in which case we should not adjust it. */ 553 if(data->state.resume_from <= expected_size) 554 expected_size -= data->state.resume_from; 555 else { 556 failf(data, "failed to resume file:// transfer"); 557 return CURLE_BAD_DOWNLOAD_RESUME; 558 } 559 } 560 561 /* A high water mark has been specified so we obey... */ 562 if(data->req.maxdownload > 0) 563 expected_size = data->req.maxdownload; 564 565 if(!fstated || (expected_size <= 0)) 566 size_known = FALSE; 567 else 568 size_known = TRUE; 569 570 /* The following is a shortcut implementation of file reading 571 this is both more efficient than the former call to download() and 572 it avoids problems with select() and recv() on file descriptors 573 in Winsock */ 574 if(size_known) 575 Curl_pgrsSetDownloadSize(data, expected_size); 576 577 if(data->state.resume_from) { 578 if(!S_ISDIR(statbuf.st_mode)) { 579 #if defined(__AMIGA__) || defined(__MINGW32CE__) 580 if(data->state.resume_from != 581 lseek(fd, (off_t)data->state.resume_from, SEEK_SET)) 582 #else 583 if(data->state.resume_from != 584 lseek(fd, data->state.resume_from, SEEK_SET)) 585 #endif 586 return CURLE_BAD_DOWNLOAD_RESUME; 587 } 588 else { 589 return CURLE_BAD_DOWNLOAD_RESUME; 590 } 591 } 592 593 result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); 594 if(result) 595 goto out; 596 597 if(!S_ISDIR(statbuf.st_mode)) { 598 while(!result) { 599 ssize_t nread; 600 /* Do not fill a whole buffer if we want less than all data */ 601 size_t bytestoread; 602 603 if(size_known) { 604 bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? 605 curlx_sotouz(expected_size) : (xfer_blen-1); 606 } 607 else 608 bytestoread = xfer_blen-1; 609 610 nread = read(fd, xfer_buf, bytestoread); 611 612 if(nread > 0) 613 xfer_buf[nread] = 0; 614 615 if(nread <= 0 || (size_known && (expected_size == 0))) 616 break; 617 618 if(size_known) 619 expected_size -= nread; 620 621 result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); 622 if(result) 623 goto out; 624 625 if(Curl_pgrsUpdate(data)) 626 result = CURLE_ABORTED_BY_CALLBACK; 627 else 628 result = Curl_speedcheck(data, curlx_now()); 629 if(result) 630 goto out; 631 } 632 } 633 else { 634 #ifdef HAVE_OPENDIR 635 DIR *dir = opendir(file->path); 636 struct dirent *entry; 637 638 if(!dir) { 639 result = CURLE_READ_ERROR; 640 goto out; 641 } 642 else { 643 while((entry = readdir(dir))) { 644 if(entry->d_name[0] != '.') { 645 result = Curl_client_write(data, CLIENTWRITE_BODY, 646 entry->d_name, strlen(entry->d_name)); 647 if(result) 648 break; 649 result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); 650 if(result) 651 break; 652 } 653 } 654 closedir(dir); 655 } 656 #else 657 failf(data, "Directory listing not yet implemented on this platform."); 658 result = CURLE_READ_ERROR; 659 #endif 660 } 661 662 if(Curl_pgrsUpdate(data)) 663 result = CURLE_ABORTED_BY_CALLBACK; 664 665 out: 666 Curl_multi_xfer_buf_release(data, xfer_buf); 667 return result; 668 } 669 670 #endif