blob: 1bc2b16aacd4f47ef77a85ae0c943a3607bf77a8 [file]
// 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.
#ifndef SRC_TOOLS_SINGLEJAR_COMBINERS_H_
#define SRC_TOOLS_SINGLEJAR_COMBINERS_H_ 1
#include <memory>
#include <string>
#include "src/tools/singlejar/transient_bytes.h"
#include "src/tools/singlejar/zip_headers.h"
#include "src/tools/singlejar/zlib_interface.h"
// An output jar entry consisting of a concatenation of the input jar
// entries. Byte sequences can be appended to it, too.
class Concatenator {
public:
Concatenator(const std::string &filename) : filename_(filename) {}
// Appends the contents of the given input entry.
bool Merge(const CDH *cdh, const LH *lh) {
CreateBuffer();
if (Z_NO_COMPRESSION == lh->compression_method()) {
buffer_->ReadEntryContents(lh);
} else if (Z_DEFLATED == lh->compression_method()) {
if (!inflater_.get()) {
inflater_.reset(new Inflater());
}
buffer_->DecompressEntryContents(cdh, lh, inflater_.get());
} else {
errx(2, "%s is neither stored nor deflated", filename_.c_str());
}
return true;
}
// Returns a point to the buffer containing Local Header followed by the
// payload. The caller is responsible of freeing the buffer.
void *OutputEntry() {
if (!buffer_.get()) {
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 = (buffer_->data_size() >= 0xFFFFFFFF);
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(33); // 1980-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 = buffer_->Write(lh->data(), &checksum, &compressed_size);
lh->crc32(checksum);
lh->compression_method(method);
if (huge_buffer) {
lh->compressed_file_size32(compressed_size < 0xFFFFFFFF ? compressed_size
: 0xFFFFFFFF);
// 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);
}
void Append(const char *s, size_t n) {
CreateBuffer();
buffer_->Append(reinterpret_cast<const uint8_t *>(s), n);
}
void Append(const char *s) { Append(s, strlen(s)); }
void Append(const std::string &str) { Append(str.c_str(), str.size()); }
const std::string &filename() const { return filename_; }
private:
void CreateBuffer() {
if (!buffer_.get()) {
buffer_.reset(new TransientBytes());
}
}
const std::string filename_;
std::unique_ptr<TransientBytes> buffer_;
std::unique_ptr<Inflater> inflater_;
};
// Combines the contents of the multiple input entries which are XML
// files into a single XML output entry with given top level XML tag.
class XmlCombiner {
public:
XmlCombiner(const std::string &filename, const char *xml_tag)
: filename_(filename), xml_tag_(xml_tag) {}
bool Merge(const CDH *cdh, const LH *lh) {
if (!concatenator_.get()) {
concatenator_.reset(new Concatenator(filename_));
concatenator_->Append("<");
concatenator_->Append(xml_tag_);
concatenator_->Append(">\n");
}
return concatenator_->Merge(cdh, lh);
}
// Returns a pointer to the buffer containing LocalHeader for the entry,
// immediately followed by entry payload. The caller is responsible for
// freeing the buffer.
void *OutputEntry() {
if (!concatenator_.get()) {
return nullptr;
}
concatenator_->Append("</");
concatenator_->Append(xml_tag_);
concatenator_->Append(">\n");
return concatenator_->OutputEntry();
}
const std::string filename() const { return filename_; }
private:
const std::string filename_;
const char *xml_tag_;
std::unique_ptr<Concatenator> concatenator_;
std::unique_ptr<Inflater> inflater_;
};
// A wrapper around Concatenator allowing to append
// NAME=VALUE
// lines to the contents.
class PropertyCombiner : public Concatenator {
public:
PropertyCombiner(const std::string &filename) : Concatenator(filename) {}
void AddProperty(const char *key, const char *value) {
// TODO(asmundak): deduplicate properties.
Append(key);
Append("=", 1);
Append(value);
Append("\n", 1);
}
void AddProperty(const std::string &key, const std::string &value) {
// TODO(asmundak): deduplicate properties.
Append(key);
Append("=", 1);
Append(value);
Append("\n", 1);
}
};
#endif // SRC_TOOLS_SINGLEJAR_COMBINERS_H_