| // Copyright 2016 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/tools/singlejar/input_jar.h" | |
| #include <cinttypes> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <string> | |
| #include "src/tools/singlejar/diag.h" | |
| #include "src/tools/singlejar/zip_headers.h" | |
| bool InputJar::Open(const std::string& path) { | |
| if (!path_.empty()) { | |
| diag_errx(1, "%s:%d: This instance is already handling %s\n", __FILE__, | |
| __LINE__, path_.c_str()); | |
| } | |
| if (!mapped_file_.Open(path)) { | |
| diag_warn("%s:%d: Cannot open input jar %s", __FILE__, __LINE__, | |
| path.c_str()); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| return LocateCentralDirectory(path); | |
| } | |
| bool InputJar::Open(const std::string& path, unsigned char* data, | |
| size_t length) { | |
| if (!path_.empty()) { | |
| diag_errx(1, "%s:%d: This instance is already handling %s\n", __FILE__, | |
| __LINE__, path_.c_str()); | |
| } | |
| if (path.empty()) { | |
| diag_errx(1, "%s:%d: A non-empty path is required\n", __FILE__, __LINE__); | |
| } | |
| mapped_file_.MapExisting(data, data + length); | |
| return LocateCentralDirectory(path); | |
| } | |
| bool InputJar::LocateCentralDirectory(const std::string& path) { | |
| if (mapped_file_.size() < sizeof(ECD)) { | |
| diag_warnx( | |
| "%s:%d: %s is only 0x%zx" | |
| " bytes long, should be at least 0x%zx bytes long", | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.size(), sizeof(ECD)); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| // Now locate End of Central Directory (ECD) record. | |
| size_t max_back = 65536 + sizeof(ECD); | |
| const uint8_t* ecd_min = mapped_file_.size() > max_back | |
| ? mapped_file_.end() - max_back | |
| : mapped_file_.start(); | |
| const ECD* ecd = nullptr; | |
| for (const uint8_t* ecd_ptr = mapped_file_.end() - sizeof(ECD); | |
| ecd_ptr >= ecd_min; --ecd_ptr) { | |
| auto candidate = reinterpret_cast<const ECD*>(ecd_ptr); | |
| if (candidate->is() && | |
| ecd_ptr + sizeof(ECD) + candidate->comment_length() <= | |
| mapped_file_.end()) { | |
| ecd = candidate; | |
| break; | |
| } | |
| } | |
| if (ecd == nullptr) { | |
| diag_warnx("%s:%d: Cannot locate ECD record in %s", __FILE__, __LINE__, | |
| path.c_str()); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| /* Find Central Directory and preamble size. We want to handle the case | |
| * where a Jar/Zip file contains a preamble (an arbitrary data before the | |
| * first entry) and 'zip -A' was not called to adjust the offsets, so all | |
| * the offsets are off by the preamble size. In the 32-bit case (that is, | |
| * there is no ECD64Locator+ECD64), ECD immediately follows the last CDH, | |
| * ECD immediately follows the Central Directory, and contains its size, so | |
| * Central Directory can be found reliably. We then use its stated location, | |
| * which ECD contains, too, to calculate the preamble size. In the 64-bit | |
| * case, there are ECD64 and ECD64Locator records between the end of the | |
| * Central Directory and the ECD, the calculation is similar, with the | |
| * exception of the logic to find the actual start of the ECD64. | |
| * ECD64Locator contains only its position in the file, which is off by | |
| * preamble size, but does not contain the actual size of ECD64, which in | |
| * theory is variable (the fixed fields may be followed by some custom data, | |
| * with the total size saved in ECD64::remaining_size and thus unavailable | |
| * until we find ECD64. We assume that the custom data is missing. | |
| */ | |
| // First, consistency check the directory. | |
| uint32_t cen_position = ecd->cen_offset32(); | |
| if (!ziph::zfield_has_ext64(cen_position)) { | |
| if (!mapped_file_.mapped(mapped_file_.address(cen_position))) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory location 0x%" PRIx32 | |
| " is invalid", | |
| __FILE__, __LINE__, path.c_str(), cen_position); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| if (mapped_file_.offset(ecd) < cen_position) { | |
| diag_warnx("%s:%d: %s is corrupt: End of Central Directory at 0x%" PRIx64 | |
| " precedes Central Directory at 0x%" PRIx32, | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.offset(ecd), | |
| cen_position); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| } | |
| uint32_t cen_size = ecd->cen_size32(); | |
| if (!ziph::zfield_has_ext64(cen_size)) { | |
| if (cen_size > mapped_file_.offset(ecd)) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory size 0x%" PRIx32 | |
| " is too large", | |
| __FILE__, __LINE__, path.c_str(), cen_size); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| } | |
| if (cen_size == 0) { | |
| if (ziph::zfield_has_ext64(cen_position)) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory location 0x%" PRIx32 | |
| " is invalid", | |
| __FILE__, __LINE__, path.c_str(), cen_position); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| // Empty archive, let cdh_ point to End of Central Directory. | |
| cdh_ = reinterpret_cast<const CDH*>(ecd); | |
| cen_end_ = ziph::byte_ptr(ecd); | |
| preamble_size_ = mapped_file_.offset(cdh_) - cen_position; | |
| } else { | |
| const ECD64Locator* ecd64loc = nullptr; | |
| if (static_cast<size_t>(mapped_file_.offset(ecd)) >= sizeof(ECD64Locator)) { | |
| auto candidate = reinterpret_cast<const ECD64Locator*>( | |
| ziph::byte_ptr(ecd) - sizeof(ECD64Locator)); | |
| if (candidate->is()) { | |
| ecd64loc = candidate; | |
| } | |
| } | |
| if (ecd64loc != nullptr) { | |
| if (static_cast<size_t>(mapped_file_.offset(ecd64loc)) < sizeof(ECD64)) { | |
| diag_warnx( | |
| "%s:%d: %s is corrupt, expected ECD64 record before offset " | |
| "0x%" PRIx64 " is missing", | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.offset(ecd64loc)); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| auto ecd64 = reinterpret_cast<const ECD64*>(ziph::byte_ptr(ecd64loc) - | |
| sizeof(ECD64)); | |
| if (!ecd64->is()) { | |
| diag_warnx( | |
| "%s:%d: %s is corrupt, expected ECD64 record at offset 0x%" PRIx64 | |
| " is missing", | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.offset(ecd64)); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| uint64_t cen_size64 = ecd64->cen_size(); | |
| uint64_t ecd64_offset = mapped_file_.offset(ecd64); | |
| if (cen_size64 > ecd64_offset) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory size 0x%" PRIx64 | |
| " is too large", | |
| __FILE__, __LINE__, path.c_str(), cen_size64); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| cdh_ = reinterpret_cast<const CDH*>(ziph::byte_ptr(ecd64) - cen_size64); | |
| cen_end_ = ziph::byte_ptr(ecd64); | |
| uint64_t cdh_offset = mapped_file_.offset(cdh_); | |
| if (ecd64->cen_offset() > cdh_offset) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory offset 0x%" PRIx64 | |
| " exceeds position 0x%" PRIx64, | |
| __FILE__, __LINE__, path.c_str(), ecd64->cen_offset(), | |
| cdh_offset); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| preamble_size_ = cdh_offset - ecd64->cen_offset(); | |
| // Find CEN and preamble size. | |
| } else { | |
| if (ziph::zfield_has_ext64(cen_size) || | |
| ziph::zfield_has_ext64(cen_position)) { | |
| diag_warnx( | |
| "%s:%d: %s is corrupt, expected ECD64 locator record before " | |
| "offset 0x%" PRIx64 " is missing", | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.offset(ecd)); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| cdh_ = reinterpret_cast<const CDH*>(ziph::byte_ptr(ecd) - cen_size); | |
| cen_end_ = ziph::byte_ptr(ecd); | |
| uint64_t cdh_offset = mapped_file_.offset(cdh_); | |
| if (cen_position > cdh_offset) { | |
| diag_warnx("%s:%d: %s is corrupt: Central Directory offset 0x%" PRIx32 | |
| " exceeds position 0x%" PRIx64, | |
| __FILE__, __LINE__, path.c_str(), cen_position, cdh_offset); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| preamble_size_ = cdh_offset - cen_position; | |
| } | |
| if (ziph::byte_ptr(cdh_) < cen_end_) { | |
| if (static_cast<size_t>(cen_end_ - ziph::byte_ptr(cdh_)) < sizeof(CDH) || | |
| !cdh_->is()) { | |
| diag_warnx( | |
| "%s:%d: In %s, expected central file header signature at " | |
| "offset0x%" PRIx64, | |
| __FILE__, __LINE__, path.c_str(), mapped_file_.offset(cdh_)); | |
| mapped_file_.Close(); | |
| return false; | |
| } | |
| } | |
| } | |
| path_ = path; | |
| return true; | |
| } | |
| bool InputJar::Close() { | |
| mapped_file_.Close(); | |
| path_.clear(); | |
| cdh_ = nullptr; | |
| cen_end_ = nullptr; | |
| preamble_size_ = 0; | |
| return true; | |
| } |