FormatBrotli.cpp (7541B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "FormatBrotli.h" 8 9 #include <memory> 10 11 #include "BaseAlgorithms.h" 12 #include "brotli/decode.h" 13 #include "brotli/encode.h" 14 #include "mozilla/dom/TransformStreamDefaultController.h" 15 16 namespace mozilla::dom::compression { 17 18 NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliCompressionStreamAlgorithms, 19 TransformerAlgorithmsBase) 20 NS_IMPL_ADDREF_INHERITED(BrotliCompressionStreamAlgorithms, 21 TransformerAlgorithmsBase) 22 NS_IMPL_RELEASE_INHERITED(BrotliCompressionStreamAlgorithms, 23 TransformerAlgorithmsBase) 24 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliCompressionStreamAlgorithms) 25 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) 26 27 NS_IMPL_CYCLE_COLLECTION_INHERITED(BrotliDecompressionStreamAlgorithms, 28 TransformerAlgorithmsBase) 29 NS_IMPL_ADDREF_INHERITED(BrotliDecompressionStreamAlgorithms, 30 TransformerAlgorithmsBase) 31 NS_IMPL_RELEASE_INHERITED(BrotliDecompressionStreamAlgorithms, 32 TransformerAlgorithmsBase) 33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrotliDecompressionStreamAlgorithms) 34 NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase) 35 36 inline BrotliEncoderOperation intoBrotliOp(Flush aFlush) { 37 switch (aFlush) { 38 case Flush::No: { 39 return BROTLI_OPERATION_PROCESS; 40 } 41 case Flush::Yes: { 42 return BROTLI_OPERATION_FINISH; 43 } 44 default: { 45 MOZ_ASSERT_UNREACHABLE("Unknown flush mode"); 46 return BROTLI_OPERATION_PROCESS; 47 } 48 } 49 } 50 51 Result<already_AddRefed<BrotliCompressionStreamAlgorithms>, nsresult> 52 BrotliCompressionStreamAlgorithms::Create() { 53 RefPtr<BrotliCompressionStreamAlgorithms> alg = 54 new BrotliCompressionStreamAlgorithms(); 55 MOZ_TRY(alg->Init()); 56 return alg.forget(); 57 } 58 59 [[nodiscard]] nsresult BrotliCompressionStreamAlgorithms::Init() { 60 mState = std::unique_ptr<BrotliEncoderStateStruct, BrotliDeleter>( 61 BrotliEncoderCreateInstance(nullptr, nullptr, nullptr)); 62 if (!mState) { 63 return NS_ERROR_OUT_OF_MEMORY; 64 } 65 return NS_OK; 66 } 67 68 // Shared by: 69 // https://wicg.github.io/compression/#compress-and-enqueue-a-chunk 70 // https://wicg.github.io/compression/#compress-flush-and-enqueue 71 // All data errors throw TypeError by step 2: If this results in an error, 72 // then throw a TypeError. 73 void BrotliCompressionStreamAlgorithms::Compress( 74 JSContext* aCx, Span<const uint8_t> aInput, 75 JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush, 76 ErrorResult& aRv) { 77 size_t inputLength = aInput.Length(); 78 const uint8_t* inputBuffer = aInput.Elements(); 79 80 do { 81 std::unique_ptr<uint8_t[], JS::FreePolicy> buffer( 82 static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); 83 if (!buffer) { 84 aRv.ThrowTypeError("Out of memory"); 85 return; 86 } 87 88 size_t outputLength = kBufferSize; 89 uint8_t* outputBuffer = buffer.get(); 90 bool succeeded = BrotliEncoderCompressStream( 91 mState.get(), intoBrotliOp(aFlush), &inputLength, &inputBuffer, 92 &outputLength, &outputBuffer, nullptr); 93 if (!succeeded) { 94 aRv.ThrowTypeError("Unexpected compression error"); 95 return; 96 } 97 98 // Step 3: If buffer is empty, return. 99 // (We'll implicitly return when the array is empty.) 100 101 // Step 4: Split buffer into one or more non-empty pieces and convert them 102 // into Uint8Arrays. 103 // (The buffer is 'split' by having a fixed sized buffer above.) 104 105 size_t written = kBufferSize - outputLength; 106 if (written > 0) { 107 JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( 108 aCx, written, std::move(buffer))); 109 if (!view || !aOutput.append(view)) { 110 JS_ClearPendingException(aCx); 111 aRv.ThrowTypeError("Out of memory"); 112 return; 113 } 114 } 115 } while (BrotliEncoderHasMoreOutput(mState.get())); 116 } 117 118 void BrotliCompressionStreamAlgorithms::BrotliDeleter::operator()( 119 BrotliEncoderStateStruct* aState) { 120 BrotliEncoderDestroyInstance(aState); 121 } 122 123 Result<already_AddRefed<BrotliDecompressionStreamAlgorithms>, nsresult> 124 BrotliDecompressionStreamAlgorithms::Create() { 125 RefPtr<BrotliDecompressionStreamAlgorithms> alg = 126 new BrotliDecompressionStreamAlgorithms(); 127 MOZ_TRY(alg->Init()); 128 return alg.forget(); 129 } 130 131 [[nodiscard]] nsresult BrotliDecompressionStreamAlgorithms::Init() { 132 mState = std::unique_ptr<BrotliDecoderStateStruct, BrotliDeleter>( 133 BrotliDecoderCreateInstance(nullptr, nullptr, nullptr)); 134 if (!mState) { 135 return NS_ERROR_OUT_OF_MEMORY; 136 } 137 return NS_OK; 138 } 139 140 // Shared by: 141 // https://wicg.github.io/compression/#decompress-and-enqueue-a-chunk 142 // https://wicg.github.io/compression/#decompress-flush-and-enqueue 143 // All data errors throw TypeError by step 2: If this results in an error, 144 // then throw a TypeError. 145 bool BrotliDecompressionStreamAlgorithms::Decompress( 146 JSContext* aCx, Span<const uint8_t> aInput, 147 JS::MutableHandleVector<JSObject*> aOutput, Flush aFlush, 148 ErrorResult& aRv) { 149 size_t inputLength = aInput.Length(); 150 const uint8_t* inputBuffer = aInput.Elements(); 151 152 do { 153 std::unique_ptr<uint8_t[], JS::FreePolicy> buffer( 154 static_cast<uint8_t*>(JS_malloc(aCx, kBufferSize))); 155 if (!buffer) { 156 aRv.ThrowTypeError("Out of memory"); 157 return false; 158 } 159 160 size_t outputLength = kBufferSize; 161 uint8_t* outputBuffer = buffer.get(); 162 BrotliDecoderResult rv = 163 BrotliDecoderDecompressStream(mState.get(), &inputLength, &inputBuffer, 164 &outputLength, &outputBuffer, nullptr); 165 switch (rv) { 166 case BROTLI_DECODER_RESULT_ERROR: 167 aRv.ThrowTypeError("Brotli decompression error: "_ns + 168 nsDependentCString(BrotliDecoderErrorString( 169 BrotliDecoderGetErrorCode(mState.get())))); 170 return false; 171 case BROTLI_DECODER_RESULT_SUCCESS: 172 mObservedStreamEnd = true; 173 break; 174 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 175 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 176 break; 177 default: 178 MOZ_ASSERT_UNREACHABLE("Unexpected decompression error code"); 179 aRv.ThrowTypeError("Unexpected decompression error"); 180 return false; 181 } 182 183 // Step 3: If buffer is empty, return. 184 // (We'll implicitly return when the array is empty.) 185 186 // Step 4: Split buffer into one or more non-empty pieces and convert them 187 // into Uint8Arrays. 188 // (The buffer is 'split' by having a fixed sized buffer above.) 189 190 size_t written = kBufferSize - outputLength; 191 if (written > 0) { 192 JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array( 193 aCx, written, std::move(buffer))); 194 if (!view || !aOutput.append(view)) { 195 JS_ClearPendingException(aCx); 196 aRv.ThrowTypeError("Out of memory"); 197 return false; 198 } 199 } 200 } while (BrotliDecoderHasMoreOutput(mState.get())); 201 202 return inputLength == 0; 203 } 204 205 void BrotliDecompressionStreamAlgorithms::BrotliDeleter::operator()( 206 BrotliDecoderStateStruct* aState) { 207 BrotliDecoderDestroyInstance(aState); 208 } 209 210 } // namespace mozilla::dom::compression