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