blob: 7a8e0ccef6efbe23f8c7c02bf77e3ac2d43de589 [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 <iostream>
#include <iterator>
#include <sstream>
#include <string>
#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 char *MULTI_RELEASE_PREFIX = "Multi-Release: ";
static const size_t MULTI_RELEASE_PREFIX_LENGTH = strlen(MULTI_RELEASE_PREFIX);
static const char *ADD_EXPORTS_PREFIX = "Add-Exports: ";
static const size_t ADD_EXPORTS_PREFIX_LENGTH = strlen(ADD_EXPORTS_PREFIX);
static const char *ADD_OPENS_PREFIX = "Add-Opens: ";
static const size_t ADD_OPENS_PREFIX_LENGTH = strlen(ADD_OPENS_PREFIX);
void ManifestCombiner::EnableMultiRelease() { multi_release_ = true; }
void ManifestCombiner::AddExports(const std::vector<std::string> &add_exports) {
add_exports_.insert(std::end(add_exports_), std::begin(add_exports),
std::end(add_exports));
}
void ManifestCombiner::AddOpens(const std::vector<std::string> &add_opens) {
add_opens_.insert(std::end(add_opens_), std::begin(add_opens),
std::end(add_opens));
}
bool ManifestCombiner::HandleModuleFlags(std::vector<std::string> &output,
const char *key, size_t key_length,
std::string line) {
if (line.find(key, 0, key_length) == std::string::npos) {
return false;
}
std::istringstream iss(line.substr(key_length));
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(), std::back_inserter(output));
return true;
}
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;
}
// Handle 'Add-Exports:' and 'Add-Opens:' lines in --deploy_manifest_lines and
// merge them with the --add_exports= and --add_opens= flags.
if (HandleModuleFlags(add_exports_, ADD_EXPORTS_PREFIX,
ADD_EXPORTS_PREFIX_LENGTH, line)) {
return;
}
if (HandleModuleFlags(add_opens_, ADD_OPENS_PREFIX, ADD_OPENS_PREFIX_LENGTH,
line)) {
return;
}
concatenator_->Append(line);
if (line[line.size() - 1] != '\n') {
concatenator_->Append("\r\n");
}
}
bool ManifestCombiner::Merge(const CDH *cdh, const LH *lh) {
// Ignore Multi-Release attributes in inputs: we write the manifest first,
// before inputs are processed, so we reply on deploy_manifest_lines to
// create Multi-Release jars instead of doing it automatically based on
// the inputs.
return true;
}
void ManifestCombiner::OutputModuleFlags(std::vector<std::string> &flags,
const char *key) {
std::sort(flags.begin(), flags.end());
flags.erase(std::unique(flags.begin(), flags.end()), flags.end());
if (!flags.empty()) {
concatenator_->Append(key);
bool first = true;
for (const auto &flag : flags) {
if (!first) {
concatenator_->Append("\r\n ");
}
concatenator_->Append(flag);
first = false;
}
concatenator_->Append("\r\n");
}
}
void *ManifestCombiner::OutputEntry(bool compress) {
if (multi_release_) {
concatenator_->Append(MULTI_RELEASE);
concatenator_->Append("\r\n");
}
OutputModuleFlags(add_exports_, ADD_EXPORTS_PREFIX);
OutputModuleFlags(add_opens_, ADD_OPENS_PREFIX);
concatenator_->Append("\r\n");
return concatenator_->OutputEntry(compress);
}