|  | // 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); | 
|  | } |