|
17 | 17 | #include <zip.h> |
18 | 18 |
|
19 | 19 | #include "hphp/runtime/base/array-init.h" |
| 20 | +#include "hphp/runtime/base/file-util.h" |
20 | 21 | #include "hphp/runtime/base/preg.h" |
21 | 22 | #include "hphp/runtime/base/stream-wrapper-registry.h" |
22 | 23 | #include "hphp/runtime/ext/extension.h" |
@@ -650,25 +651,103 @@ static bool HHVM_METHOD(ZipArchive, deleteName, const String& name) { |
650 | 651 | return true; |
651 | 652 | } |
652 | 653 |
|
| 654 | +// Make the path relative to "." by flattening. |
| 655 | +// This function is named the same and similar in implementation to that in |
| 656 | +// php-src:php_zip.c |
| 657 | +// One difference is that we canonicalize here whereas php-src is already |
| 658 | +// assumed passed a canonicalized path. |
| 659 | +static std::string make_relative_path(const std::string& path) { |
| 660 | + if (path.empty()) { |
| 661 | + return path; |
| 662 | + } |
| 663 | + |
| 664 | + // First get the path to a state where we don't have .. in the middle of it |
| 665 | + // etc. canonicalize handles Windows paths too. |
| 666 | + std::string canonical(FileUtil::canonicalize(path)); |
| 667 | + |
| 668 | + // If we have a slash at the beginning, then just remove it and we are |
| 669 | + // relative. This check will hold because we have canonicalized the |
| 670 | + // path above to remove .. from the path, so we know we can be sure |
| 671 | + // we are at a good place for this check. |
| 672 | + if (FileUtil::isDirSeparator(canonical[0])) { |
| 673 | + return canonical.substr(1); |
| 674 | + } |
| 675 | + |
| 676 | + // If we get here, canonical looks something like: |
| 677 | + // a/b/c |
| 678 | + |
| 679 | + // Search through the path and if we find a place where we have a slash |
| 680 | + // and a "." just before that slash, then cut the path off right there |
| 681 | + // and just take everything after the slash. |
| 682 | + std::string relative(canonical); |
| 683 | + int idx = canonical.length() - 1; |
| 684 | + while (1) { |
| 685 | + while (idx > 0 && !(FileUtil::isDirSeparator(canonical[idx]))) { |
| 686 | + idx--; |
| 687 | + } |
| 688 | + // If we ever get to idx == 0, then there were no other slashes to deal with |
| 689 | + if (idx == 0) { |
| 690 | + return canonical; |
| 691 | + } |
| 692 | + if (idx >= 1 && (canonical[idx - 1] == '.' || canonical[idx - 1] == ':')) { |
| 693 | + relative = canonical.substr(idx + 1); |
| 694 | + break; |
| 695 | + } |
| 696 | + idx--; |
| 697 | + } |
| 698 | + return relative; |
| 699 | +} |
| 700 | + |
653 | 701 | static bool extractFileTo(zip* zip, const std::string &file, std::string& to, |
654 | 702 | char* buf, size_t len) { |
655 | | - auto sep = file.rfind('/'); |
| 703 | + |
| 704 | + struct zip_stat zipStat; |
| 705 | + // Verify the file to be extracted is actually in the zip file |
| 706 | + if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) { |
| 707 | + return false; |
| 708 | + } |
| 709 | + |
| 710 | + auto clean_file = file; |
| 711 | + auto sep = std::string::npos; |
| 712 | + // Normally would just use std::string::rfind here, but if we want to be |
| 713 | + // consistent between Windows and Linux, even if techincally Linux won't use |
| 714 | + // backslash for a separator, we are checking for both types. |
| 715 | + int idx = file.length() - 1; |
| 716 | + while (idx >= 0) { |
| 717 | + if (FileUtil::isDirSeparator(file[idx])) { |
| 718 | + sep = idx; |
| 719 | + break; |
| 720 | + } |
| 721 | + idx--; |
| 722 | + } |
656 | 723 | if (sep != std::string::npos) { |
657 | | - auto path = to + file.substr(0, sep); |
| 724 | + // make_relative_path so we do not try to put files or dirs in bad |
| 725 | + // places. This securely "cleans" the file. |
| 726 | + clean_file = make_relative_path(file); |
| 727 | + std::string path = to + clean_file; |
| 728 | + bool is_dir_only = true; |
| 729 | + if (sep < file.length() - 1) { // not just a directory |
| 730 | + auto clean_file_dir = HHVM_FN(dirname)(clean_file); |
| 731 | + path = to + clean_file_dir.toCppString(); |
| 732 | + is_dir_only = false; |
| 733 | + } |
| 734 | + |
| 735 | + // Make sure the directory path to extract to exists or can be created |
658 | 736 | if (!HHVM_FN(is_dir)(path) && !HHVM_FN(mkdir)(path, 0777, true)) { |
659 | 737 | return false; |
660 | 738 | } |
661 | 739 |
|
662 | | - if (sep == file.length() - 1) { |
| 740 | + // If we have a good directory to extract to above, we now check whether |
| 741 | + // the "file" parameter passed in is a directory or actually a file. |
| 742 | + if (is_dir_only) { // directory, like /usr/bin/ |
663 | 743 | return true; |
664 | 744 | } |
| 745 | + // otherwise file is actually a file, so we actually extract. |
665 | 746 | } |
666 | 747 |
|
667 | | - to.append(file); |
668 | | - struct zip_stat zipStat; |
669 | | - if (zip_stat(zip, file.c_str(), 0, &zipStat) != 0) { |
670 | | - return false; |
671 | | - } |
| 748 | + // We have ensured that clean_file will be added to a relative path by the |
| 749 | + // time we get here. |
| 750 | + to.append(clean_file); |
672 | 751 |
|
673 | 752 | auto zipFile = zip_fopen_index(zip, zipStat.index, 0); |
674 | 753 | FAIL_IF_INVALID_PTR(zipFile); |
|
0 commit comments