blob: ca41b443522ab2c230a8417e61b0090edd0af9d0 [file] [edit]
// 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;
}