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