summaryrefslogtreecommitdiffstats
path: root/mozglue/linker/XZStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/linker/XZStream.cpp')
-rw-r--r--mozglue/linker/XZStream.cpp213
1 files changed, 213 insertions, 0 deletions
diff --git a/mozglue/linker/XZStream.cpp b/mozglue/linker/XZStream.cpp
new file mode 100644
index 000000000..bd71655f5
--- /dev/null
+++ b/mozglue/linker/XZStream.cpp
@@ -0,0 +1,213 @@
+#include "XZStream.h"
+
+#include <algorithm>
+#include "mozilla/Assertions.h"
+#include "Logging.h"
+
+// LZMA dictionary size, should have a minimum size for the given compression
+// rate, see XZ Utils docs for details.
+static const uint32_t kDictSize = 1 << 24;
+
+static const size_t kFooterSize = 12;
+
+// Parses a variable-length integer (VLI),
+// see http://tukaani.org/xz/xz-file-format.txt for details.
+static size_t
+ParseVarLenInt(const uint8_t* aBuf, size_t aBufSize, uint64_t* aValue)
+{
+ if (!aBufSize) {
+ return 0;
+ }
+ aBufSize = std::min(9u, aBufSize);
+
+ *aValue = aBuf[0] & 0x7F;
+ size_t i = 0;
+
+ while (aBuf[i++] & 0x80) {
+ if (i >= aBufSize || aBuf[i] == 0x0) {
+ return 0;
+ }
+ *aValue |= static_cast<uint64_t>(aBuf[i] & 0x7F) << (i * 7);
+ }
+ return i;
+}
+
+/* static */ bool
+XZStream::IsXZ(const void* aBuf, size_t aBufSize)
+{
+ static const uint8_t kXzMagic[] = {0xfd, '7', 'z', 'X', 'Z', 0x0};
+ MOZ_ASSERT(aBuf);
+ return aBufSize > sizeof(kXzMagic) &&
+ !memcmp(reinterpret_cast<const void*>(kXzMagic), aBuf, sizeof(kXzMagic));
+}
+
+XZStream::XZStream(const void* aInBuf, size_t aInSize)
+ : mInBuf(static_cast<const uint8_t*>(aInBuf))
+ , mUncompSize(0)
+ , mDec(nullptr)
+{
+ mBuffers.in = mInBuf;
+ mBuffers.in_pos = 0;
+ mBuffers.in_size = aInSize;
+}
+
+XZStream::~XZStream()
+{
+ xz_dec_end(mDec);
+}
+
+bool
+XZStream::Init()
+{
+#ifdef XZ_USE_CRC64
+ xz_crc64_init();
+#endif
+ xz_crc32_init();
+
+ mDec = xz_dec_init(XZ_DYNALLOC, kDictSize);
+
+ if (!mDec) {
+ return false;
+ }
+
+ mUncompSize = ParseUncompressedSize();
+
+ return true;
+}
+
+size_t
+XZStream::Decode(void* aOutBuf, size_t aOutSize)
+{
+ if (!mDec) {
+ return 0;
+ }
+
+ mBuffers.out = static_cast<uint8_t*>(aOutBuf);
+ mBuffers.out_pos = 0;
+ mBuffers.out_size = aOutSize;
+
+ while (mBuffers.in_pos < mBuffers.in_size &&
+ mBuffers.out_pos < mBuffers.out_size) {
+ const xz_ret ret = xz_dec_run(mDec, &mBuffers);
+
+ switch (ret) {
+ case XZ_STREAM_END:
+ // Stream ended, the next loop iteration should terminate.
+ MOZ_ASSERT(mBuffers.in_pos == mBuffers.in_size);
+ MOZ_FALLTHROUGH;
+#ifdef XZ_DEC_ANY_CHECK
+ case XZ_UNSUPPORTED_CHECK:
+ // Ignore unsupported check.
+ MOZ_FALLTHROUGH;
+#endif
+ case XZ_OK:
+ // Chunk decoded, proceed.
+ break;
+
+ case XZ_MEM_ERROR:
+ ERROR("XZ decoding: memory allocation failed");
+ return 0;
+
+ case XZ_MEMLIMIT_ERROR:
+ ERROR("XZ decoding: memory usage limit reached");
+ return 0;
+
+ case XZ_FORMAT_ERROR:
+ ERROR("XZ decoding: invalid stream format");
+ return 0;
+
+ case XZ_OPTIONS_ERROR:
+ ERROR("XZ decoding: unsupported header options");
+ return 0;
+
+ case XZ_DATA_ERROR:
+ MOZ_FALLTHROUGH;
+ case XZ_BUF_ERROR:
+ ERROR("XZ decoding: corrupt input stream");
+ return 0;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("XZ decoding: unknown error condition");
+ return 0;
+ }
+ }
+ return mBuffers.out_pos;
+}
+
+size_t
+XZStream::RemainingInput() const
+{
+ return mBuffers.in_size - mBuffers.in_pos;
+}
+
+size_t
+XZStream::Size() const
+{
+ return mBuffers.in_size;
+}
+
+size_t
+XZStream::UncompressedSize() const
+{
+ return mUncompSize;
+}
+
+size_t
+XZStream::ParseIndexSize() const
+{
+ static const uint8_t kFooterMagic[] = {'Y', 'Z'};
+
+ const uint8_t* footer = mInBuf + mBuffers.in_size - kFooterSize;
+ // The magic bytes are at the end of the footer.
+ if (memcmp(reinterpret_cast<const void*>(kFooterMagic),
+ footer + kFooterSize - sizeof(kFooterMagic),
+ sizeof(kFooterMagic))) {
+ // Not a valid footer at stream end.
+ return 0;
+ }
+ // Backward size is a 32 bit LE integer field positioned after the 32 bit CRC32
+ // code. It encodes the index size as a multiple of 4 bytes with a minimum
+ // size of 4 bytes.
+ const uint32_t backwardSize = *(footer + 4);
+ return (backwardSize + 1) * 4;
+}
+
+size_t
+XZStream::ParseUncompressedSize() const
+{
+ static const uint8_t kIndexIndicator[] = {0x0};
+
+ const size_t indexSize = ParseIndexSize();
+ if (!indexSize) {
+ return 0;
+ }
+ // The footer follows directly the index, so we can use it as a reference.
+ const uint8_t* end = mInBuf + mBuffers.in_size;
+ const uint8_t* index = end - kFooterSize - indexSize;
+
+ // The index consists of a one byte indicator followed by a VLI field for the
+ // number of records (1 expected) followed by a list of records. One record
+ // contains a VLI field for unpadded size followed by a VLI field for
+ // uncompressed size.
+ if (memcmp(reinterpret_cast<const void*>(kIndexIndicator),
+ index, sizeof(kIndexIndicator))) {
+ // Not a valid index.
+ return 0;
+ }
+
+ index += sizeof(kIndexIndicator);
+ uint64_t numRecords = 0;
+ index += ParseVarLenInt(index, end - index, &numRecords);
+ if (!numRecords) {
+ return 0;
+ }
+ uint64_t unpaddedSize = 0;
+ index += ParseVarLenInt(index, end - index, &unpaddedSize);
+ if (!unpaddedSize) {
+ return 0;
+ }
+ uint64_t uncompressedSize = 0;
+ index += ParseVarLenInt(index, end - index, &uncompressedSize);
+
+ return uncompressedSize;
+}