blob: 33fc7440cc1c9231f57c7fd239ea152bdbd35a16 [file] [log] [blame]
// 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/combiners.h"
#include <cctype>
#include "src/tools/singlejar/diag.h"
Combiner::~Combiner() {}
Concatenator::~Concatenator() {}
bool Concatenator::Merge(const CDH *cdh, const LH *lh) {
if (insert_newlines_ && buffer_.get() && buffer_->data_size() &&
'\n' != buffer_->last_byte()) {
Append("\n", 1);
}
CreateBuffer();
if (Z_NO_COMPRESSION == lh->compression_method()) {
buffer_->ReadEntryContents(lh);
} else if (Z_DEFLATED == lh->compression_method()) {
if (!inflater_) {
inflater_.reset(new Inflater());
}
buffer_->DecompressEntryContents(cdh, lh, inflater_.get());
} else {
diag_errx(2, "%s is neither stored nor deflated", filename_.c_str());
}
return true;
}
void *Concatenator::OutputEntry(bool compress) {
if (!buffer_) {
return nullptr;
}
// Allocate a contiguous buffer for the local file header and
// deflated data. We assume that deflate decreases the size, so if
// the deflater reports overflow, we just save original data.
size_t deflated_buffer_size =
sizeof(LH) + filename_.size() + buffer_->data_size();
// Huge entry (>4GB) needs Zip64 extension field with 64-bit original
// and compressed size values.
uint8_t
zip64_extension_buffer[sizeof(Zip64ExtraField) + 2 * sizeof(uint64_t)];
bool huge_buffer = ziph::zfield_needs_ext64(buffer_->data_size());
if (huge_buffer) {
deflated_buffer_size += sizeof(zip64_extension_buffer);
}
LH *lh = reinterpret_cast<LH *>(malloc(deflated_buffer_size));
if (lh == nullptr) {
return nullptr;
}
lh->signature();
lh->version(20);
lh->bit_flag(0x0);
lh->last_mod_file_time(1); // 00:00:01
lh->last_mod_file_date(30 << 9 | 1 << 5 | 1); // 2010-01-01
lh->crc32(0x12345678);
lh->compressed_file_size32(0);
lh->file_name(filename_.c_str(), filename_.size());
if (huge_buffer) {
// Add Z64 extension if this is a huge entry.
lh->uncompressed_file_size32(0xFFFFFFFF);
Zip64ExtraField *z64 =
reinterpret_cast<Zip64ExtraField *>(zip64_extension_buffer);
z64->signature();
z64->payload_size(2 * sizeof(uint64_t));
z64->attr64(0, buffer_->data_size());
lh->extra_fields(reinterpret_cast<uint8_t *>(z64), z64->size());
} else {
lh->uncompressed_file_size32(buffer_->data_size());
lh->extra_fields(nullptr, 0);
}
uint32_t checksum;
uint64_t compressed_size;
uint16_t method;
if (compress) {
method = buffer_->CompressOut(lh->data(), &checksum, &compressed_size);
} else {
buffer_->CopyOut(lh->data(), &checksum);
method = Z_NO_COMPRESSION;
compressed_size = buffer_->data_size();
}
lh->crc32(checksum);
lh->compression_method(method);
if (huge_buffer) {
lh->compressed_file_size32(ziph::zfield_needs_ext64(compressed_size)
? 0xFFFFFFFF
: compressed_size);
// Not sure if this has to be written in the small case, but it shouldn't
// hurt.
const_cast<Zip64ExtraField *>(lh->zip64_extra_field())
->attr64(1, compressed_size);
} else {
// If original data is <4GB, the compressed one is, too.
lh->compressed_file_size32(compressed_size);
}
return reinterpret_cast<void *>(lh);
}
NullCombiner::~NullCombiner() {}
bool NullCombiner::Merge(const CDH * /*cdh*/, const LH * /*lh*/) {
return true;
}
void *NullCombiner::OutputEntry(bool /*compress*/) { return nullptr; }
XmlCombiner::~XmlCombiner() {}
bool XmlCombiner::Merge(const CDH *cdh, const LH *lh) {
if (!concatenator_) {
concatenator_.reset(new Concatenator(filename_, false));
concatenator_->Append(start_tag_);
concatenator_->Append("\n");
}
// To ensure xml concatentation is idempotent, read in the entry being added
// and remove the start and end tags if they are present.
TransientBytes bytes_;
if (Z_NO_COMPRESSION == lh->compression_method()) {
bytes_.ReadEntryContents(lh);
} else if (Z_DEFLATED == lh->compression_method()) {
if (!inflater_) {
inflater_.reset(new Inflater());
}
bytes_.DecompressEntryContents(cdh, lh, inflater_.get());
} else {
diag_errx(2, "%s is neither stored nor deflated", filename_.c_str());
}
uint32_t checksum;
char *buf = reinterpret_cast<char *>(malloc(bytes_.data_size()));
// TODO(b/37631490): optimize this to avoid copying the bytes twice
bytes_.CopyOut(reinterpret_cast<uint8_t *>(buf), &checksum);
int start_offset = 0;
if (strncmp(buf, start_tag_.c_str(), start_tag_.length()) == 0) {
start_offset = start_tag_.length();
}
uint64_t end = bytes_.data_size();
while (end >= end_tag_.length() && std::isspace(buf[end - 1])) end--;
if (strncmp(buf + end - end_tag_.length(), end_tag_.c_str(),
end_tag_.length()) == 0) {
end -= end_tag_.length();
} else {
// Leave trailing whitespace alone if we didn't find a match.
end = bytes_.data_size();
}
concatenator_->Append(buf + start_offset, end - start_offset);
free(buf);
return true;
}
void *XmlCombiner::OutputEntry(bool compress) {
if (!concatenator_) {
return nullptr;
}
concatenator_->Append(end_tag_);
concatenator_->Append("\n");
return concatenator_->OutputEntry(compress);
}
PropertyCombiner::~PropertyCombiner() {}
bool PropertyCombiner::Merge(const CDH * /*cdh*/, const LH * /*lh*/) {
return false; // This should not be called.
}
ManifestCombiner::~ManifestCombiner() {}
static const char *MULTI_RELEASE = "Multi-Release: true";
static const size_t MULTI_RELEASE_LENGTH = strlen(MULTI_RELEASE);
static const char *MULTI_RELEASE_PREFIX = "Multi-Release: ";
static const size_t MULTI_RELEASE_PREFIX_LENGTH = strlen(MULTI_RELEASE_PREFIX);
void ManifestCombiner::AppendLine(const std::string &line) {
if (line.find(MULTI_RELEASE_PREFIX, 0, MULTI_RELEASE_PREFIX_LENGTH) !=
std::string::npos) {
if (line.find("true", MULTI_RELEASE_PREFIX_LENGTH) != std::string::npos) {
multi_release_ = true;
} else if (line.find("false", MULTI_RELEASE_PREFIX_LENGTH) !=
std::string::npos) {
multi_release_ = false;
}
return;
}
concatenator_->Append(line);
if (line[line.size() - 1] != '\n') {
concatenator_->Append("\r\n");
}
}
bool ManifestCombiner::Merge(const CDH *cdh, const LH *lh) {
TransientBytes bytes_;
if (Z_NO_COMPRESSION == lh->compression_method()) {
bytes_.ReadEntryContents(lh);
} else if (Z_DEFLATED == lh->compression_method()) {
if (!inflater_) {
inflater_.reset(new Inflater());
}
bytes_.DecompressEntryContents(cdh, lh, inflater_.get());
} else {
diag_errx(2, "%s is neither stored nor deflated", filename().c_str());
}
uint32_t checksum;
char *buf = reinterpret_cast<char *>(malloc(bytes_.data_size()));
bytes_.CopyOut(reinterpret_cast<uint8_t *>(buf), &checksum);
const char *line_start = buf;
const char *data_end = buf + bytes_.data_size();
while (line_start < data_end && line_start[0] != '\r' &&
line_start[0] != '\n') {
const char *line_end = strchr(line_start, '\n');
// Go past return char to point to next line, or to end of data buffer
line_end = line_end != nullptr ? line_end + 1 : data_end;
if (strncmp(line_start, MULTI_RELEASE, MULTI_RELEASE_LENGTH) == 0) {
// The output jar is a MR-JAR if any of the inputs enable the feature.
// Do not check for `Multi-Release: false` here, since inputs shouldn't
// be allowed to override the configuration for other jars on the
// classpath.
multi_release_ = true;
}
line_start = line_end;
}
free(buf);
return true;
}
void *ManifestCombiner::OutputEntry(bool compress) {
if (multi_release_) {
concatenator_->Append(MULTI_RELEASE);
}
concatenator_->Append("\r\n");
return concatenator_->OutputEntry(compress);
}