blob: 5cf5c3ad4ebf0ba49e6691ce0c6030df037f17a3 [file] [log] [blame] [edit]
// Copyright 2024 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 <cstdint>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#include "src/tools/singlejar/diag.h"
#include "src/tools/singlejar/log4j2_plugin_dat_combiner.h"
#include "src/tools/singlejar/transient_bytes.h"
#include "src/tools/singlejar/zip_headers.h"
#include "src/tools/singlejar/zlib_interface.h"
#include <zlib.h>
// Swaps the byte order of the input value to ensure it matches the host
// system's endianness.
//
// In the context of this code, byte order swapping is required because the
// Log4j2 plugin cache file format stores multi-byte values (e.g., integers,
// strings) in big-endian order. Many systems, including most modern desktop and
// server processors, use little-endian byte order internally.
//
// When reading data from the cache file, the byte order of the stored values
// must be converted to the host system's endianness for correct interpretation.
// Similarly, when writing data to the cache file, the values must be converted
// to big-endian order to maintain compatibility with the file format
// specification.
template <typename T>
inline static T swapByteOrder(const T &val) {
int totalBytes = sizeof(val);
T swapped = (T)0;
for (int i = 0; i < totalBytes; ++i) {
swapped |= (val >> (8 * (totalBytes - i - 1)) & 0xFF) << (8 * i);
}
return swapped;
}
bool readBool(std::istringstream &stream) {
bool value;
stream.read(reinterpret_cast<char *>(&value), sizeof(value));
return value;
}
uint32_t readInt(std::istringstream &stream) {
uint32_t value;
stream.read(reinterpret_cast<char *>(&value), sizeof(value));
return swapByteOrder(value);
}
std::string readUTFString(std::istringstream &stream) {
uint16_t length;
stream.read(reinterpret_cast<char *>(&length), sizeof(length));
length = swapByteOrder(length); // Convert to host byte order
std::string result(length, '\0');
stream.read(&result[0], length);
return result;
}
void writeBoolean(std::vector<uint8_t> &buffer, bool value) {
uint8_t byte = value ? 1 : 0;
buffer.push_back(byte);
}
void writeInt(std::vector<uint8_t> &buffer, int value) {
value = swapByteOrder(value);
const uint8_t *data = reinterpret_cast<const uint8_t *>(&value);
buffer.insert(buffer.end(), data, data + sizeof(value));
}
void writeUTFString(std::vector<uint8_t> &buffer, const std::string &str) {
uint16_t length = swapByteOrder(static_cast<uint16_t>(str.size()));
const uint8_t *lengthData = reinterpret_cast<const uint8_t *>(&length);
buffer.insert(buffer.end(), lengthData, lengthData + sizeof(length));
buffer.insert(buffer.end(), str.begin(), str.end());
}
// Write Log4j2 plugin cache file.
//
// Modeled after the Java canonical implementation here:
// https://github.com/apache/logging-log4j2/blob/8573ef778d2fad2bbec50a687955dccd2a616cc5/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java#L66-L85
std::vector<uint8_t> writeLog4j2PluginCacheFile(
const std::map<std::string, std::map<std::string, PluginEntry>>
&categories) {
std::vector<uint8_t> buffer;
writeInt(buffer, static_cast<int>(categories.size()));
for (const auto &categoryPair : categories) {
writeUTFString(buffer, categoryPair.first);
writeInt(buffer, static_cast<int>(categoryPair.second.size()));
for (const auto &pluginPair : categoryPair.second) {
const PluginEntry &plugin = pluginPair.second;
writeUTFString(buffer, plugin.key);
writeUTFString(buffer, plugin.className);
writeUTFString(buffer, plugin.name);
writeBoolean(buffer, plugin.printable);
writeBoolean(buffer, plugin.defer);
}
}
return buffer;
}
// Load Log4j2 plugin .cache file.
//
// Modeled after the Java canonical implementation here:
// https://github.com/apache/logging-log4j2/blob/8573ef778d2fad2bbec50a687955dccd2a616cc5/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java#L93-L124
std::map<std::string, std::map<std::string, PluginEntry>>
loadLog4j2PluginCacheFile(TransientBytes &transientBytes) {
uint64_t data_size = transientBytes.data_size();
std::vector<uint8_t> byteData(data_size);
uint32_t checksum = 0;
transientBytes.CopyOut(byteData.data(), &checksum);
std::istringstream buffer(std::string(byteData.begin(), byteData.end()));
std::map<std::string, std::map<std::string, PluginEntry>> categories;
uint32_t categoriesCount = readInt(buffer);
for (uint32_t i = 0; i < categoriesCount; ++i) {
std::string category = readUTFString(buffer);
uint32_t entries = readInt(buffer);
for (uint32_t j = 0; j < entries; ++j) {
std::string key = readUTFString(buffer);
std::string className = readUTFString(buffer);
std::string name = readUTFString(buffer);
bool printable = readBool(buffer);
bool defer = readBool(buffer);
PluginEntry entry(key, className, name, printable, defer, category);
categories[category].insert({key, entry});
}
}
return categories;
}
Log4J2PluginDatCombiner::~Log4J2PluginDatCombiner() {}
bool Log4J2PluginDatCombiner::Merge(const CDH *cdh, const LH *lh) {
TransientBytes bytes_;
if (lh->compression_method() == Z_NO_COMPRESSION) {
bytes_.ReadEntryContents(cdh, lh);
} else if (lh->compression_method() == Z_DEFLATED) {
if (!inflater_) {
inflater_.reset(new Inflater());
}
bytes_.DecompressEntryContents(cdh, lh, inflater_.get());
} else {
diag_errx(2, "neither stored nor deflated");
}
auto newCategories = loadLog4j2PluginCacheFile(bytes_);
for (const auto &newCategoryPair : newCategories) {
auto newCategoryId = newCategoryPair.first;
auto newPlugins = newCategoryPair.second;
auto existingCategoryPair = categories_.find(newCategoryId);
if (existingCategoryPair != categories_.end()) {
for (const auto &pluginPair : newPlugins) {
auto newPluginKey = pluginPair.first;
auto newPlugin = pluginPair.second;
auto existingPluginKey = categories_[newCategoryId].find(newPluginKey);
if (existingPluginKey != categories_[newCategoryId].end() &&
no_duplicates_) {
diag_errx(1, "%s:%d: Log4J2 plugin %s.%s is present in multiple jars",
__FILE__, __LINE__, newCategoryId.c_str(),
newPluginKey.c_str());
}
categories_[newCategoryId].insert(pluginPair);
}
} else {
categories_[newCategoryId] = newPlugins;
}
}
return true;
}
void *Log4J2PluginDatCombiner::OutputEntry(bool compress) {
if (categories_.empty()) {
return nullptr;
}
auto buffer = writeLog4j2PluginCacheFile(categories_);
concatenator_->Append(reinterpret_cast<const char *>(buffer.data()),
buffer.size());
return concatenator_->OutputEntry(compress);
}