no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / quota / DecryptingInputStream_impl.h
blob48100f114e2a85c31334f2c6b3e89aac66a5e825
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_quota_DecryptingInputStream_impl_h
8 #define mozilla_dom_quota_DecryptingInputStream_impl_h
10 #include "DecryptingInputStream.h"
12 #include <algorithm>
13 #include <cstdio>
14 #include <type_traits>
15 #include <utility>
16 #include "CipherStrategy.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/Result.h"
20 #include "mozilla/ResultExtensions.h"
21 #include "mozilla/Span.h"
22 #include "mozilla/fallible.h"
23 #include "nsDebug.h"
24 #include "nsError.h"
25 #include "nsFileStreams.h"
26 #include "nsID.h"
27 #include "nsIFileStreams.h"
29 namespace mozilla::dom::quota {
31 template <typename CipherStrategy>
32 DecryptingInputStream<CipherStrategy>::DecryptingInputStream(
33 MovingNotNull<nsCOMPtr<nsIInputStream>> aBaseStream, size_t aBlockSize,
34 typename CipherStrategy::KeyType aKey)
35 : DecryptingInputStreamBase(std::move(aBaseStream), aBlockSize),
36 mKey(aKey) {
37 // XXX Move this to a fallible init function.
38 MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Decrypt,
39 CipherStrategy::SerializeKey(aKey)));
41 // This implementation only supports sync base streams. Verify this in debug
42 // builds.
43 #ifdef DEBUG
44 bool baseNonBlocking;
45 nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
46 MOZ_ASSERT(NS_SUCCEEDED(rv));
47 MOZ_ASSERT(!baseNonBlocking);
48 #endif
51 template <typename CipherStrategy>
52 DecryptingInputStream<CipherStrategy>::~DecryptingInputStream() {
53 Close();
56 template <typename CipherStrategy>
57 DecryptingInputStream<CipherStrategy>::DecryptingInputStream()
58 : DecryptingInputStreamBase{} {}
60 template <typename CipherStrategy>
61 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Close() {
62 if (!mBaseStream) {
63 return NS_OK;
66 (*mBaseStream)->Close();
67 mBaseStream.destroy();
69 mPlainBuffer.Clear();
70 mEncryptedBlock.reset();
72 return NS_OK;
75 template <typename CipherStrategy>
76 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Available(
77 uint64_t* aLengthOut) {
78 if (!mBaseStream) {
79 return NS_BASE_STREAM_CLOSED;
82 int64_t oldPos, endPos;
83 nsresult rv = Tell(&oldPos);
84 if (NS_WARN_IF(NS_FAILED(rv))) {
85 return rv;
88 rv = Seek(SEEK_END, 0);
89 if (NS_WARN_IF(NS_FAILED(rv))) {
90 return rv;
93 rv = Tell(&endPos);
94 if (NS_WARN_IF(NS_FAILED(rv))) {
95 return rv;
98 rv = Seek(SEEK_SET, oldPos);
99 if (NS_WARN_IF(NS_FAILED(rv))) {
100 return rv;
103 *aLengthOut = endPos - oldPos;
104 return NS_OK;
107 template <typename CipherStrategy>
108 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::StreamStatus() {
109 return mBaseStream ? NS_OK : NS_BASE_STREAM_CLOSED;
112 template <typename CipherStrategy>
113 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::ReadSegments(
114 nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
115 uint32_t* aBytesReadOut) {
116 *aBytesReadOut = 0;
118 if (!mBaseStream) {
119 return NS_BASE_STREAM_CLOSED;
122 nsresult rv;
124 // Do not try to use the base stream's ReadSegments here. Its very
125 // unlikely we will get a single buffer that contains all of the encrypted
126 // data and therefore would have to copy into our own buffer anyways.
127 // Instead, focus on making efficient use of the Read() interface.
129 while (aCount > 0) {
130 // We have some decrypted data in our buffer. Provide it to the callers
131 // writer function.
132 if (mPlainBytes > 0) {
133 MOZ_ASSERT(!mPlainBuffer.IsEmpty());
134 uint32_t remaining = PlainLength();
135 uint32_t numToWrite = std::min(aCount, remaining);
136 uint32_t numWritten;
137 rv = aWriter(this, aClosure,
138 reinterpret_cast<const char*>(&mPlainBuffer[mNextByte]),
139 *aBytesReadOut, numToWrite, &numWritten);
141 // As defined in nsIInputputStream.idl, do not pass writer func errors.
142 if (NS_FAILED(rv)) {
143 return NS_OK;
146 // End-of-file
147 if (numWritten == 0) {
148 return NS_OK;
151 *aBytesReadOut += numWritten;
152 mNextByte += numWritten;
153 MOZ_ASSERT(mNextByte <= mPlainBytes);
155 if (mNextByte == mPlainBytes) {
156 mNextByte = 0;
157 mLastBlockLength = mPlainBytes;
158 mPlainBytes = 0;
161 aCount -= numWritten;
163 continue;
166 // Otherwise decrypt the next chunk and loop. Any resulting data
167 // will set mPlainBytes which we check at the top of the loop.
168 uint32_t bytesRead;
169 rv = ParseNextChunk(&bytesRead);
170 if (NS_FAILED(rv)) {
171 return rv;
174 // If we couldn't read anything and there is no more data to provide
175 // to the caller, then this is eof.
176 if (bytesRead == 0 && mPlainBytes == 0) {
177 return NS_OK;
180 mPlainBytes += bytesRead;
183 return NS_OK;
186 template <typename CipherStrategy>
187 nsresult DecryptingInputStream<CipherStrategy>::ParseNextChunk(
188 uint32_t* const aBytesReadOut) {
189 // There must not be any plain data already in mPlainBuffer.
190 MOZ_ASSERT(mPlainBytes == 0);
191 MOZ_ASSERT(mNextByte == 0);
193 *aBytesReadOut = 0;
195 if (!EnsureBuffers()) {
196 return NS_ERROR_OUT_OF_MEMORY;
199 // Read the data to our internal encrypted buffer.
200 auto wholeBlock = mEncryptedBlock->MutableWholeBlock();
201 nsresult rv =
202 ReadAll(AsWritableChars(wholeBlock).Elements(), wholeBlock.Length(),
203 wholeBlock.Length(), aBytesReadOut);
204 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) {
205 return rv;
208 // XXX Do we need to know the actual decrypted size?
209 rv = mCipherStrategy.Cipher(mEncryptedBlock->MutableCipherPrefix(),
210 mEncryptedBlock->Payload(),
211 AsWritableBytes(Span{mPlainBuffer}));
212 if (NS_WARN_IF(NS_FAILED(rv))) {
213 return rv;
216 *aBytesReadOut = mEncryptedBlock->ActualPayloadLength();
218 return NS_OK;
221 template <typename CipherStrategy>
222 nsresult DecryptingInputStream<CipherStrategy>::ReadAll(
223 char* aBuf, uint32_t aCount, uint32_t aMinValidCount,
224 uint32_t* aBytesReadOut) {
225 MOZ_ASSERT(aCount >= aMinValidCount);
226 MOZ_ASSERT(mBaseStream);
228 *aBytesReadOut = 0;
230 uint32_t offset = 0;
231 while (aCount > 0) {
232 uint32_t bytesRead = 0;
233 nsresult rv = (*mBaseStream)->Read(aBuf + offset, aCount, &bytesRead);
234 if (NS_WARN_IF(NS_FAILED(rv))) {
235 return rv;
238 // EOF, but don't immediately return. We need to validate min read bytes
239 // below.
240 if (bytesRead == 0) {
241 break;
244 *aBytesReadOut += bytesRead;
245 offset += bytesRead;
246 aCount -= bytesRead;
249 // Reading zero bytes is not an error. Its the expected EOF condition.
250 // Only compare to the minimum valid count if we read at least one byte.
251 if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) {
252 return NS_ERROR_CORRUPTED_CONTENT;
255 return NS_OK;
258 template <typename CipherStrategy>
259 bool DecryptingInputStream<CipherStrategy>::EnsureBuffers() {
260 // Lazily create our two buffers so we can report OOM during stream
261 // operation. These allocations only happens once. The buffers are reused
262 // until the stream is closed.
263 if (!mEncryptedBlock) {
264 // XXX Do we need to do this fallible (as the comment above suggests)?
265 mEncryptedBlock.emplace(*mBlockSize);
267 MOZ_ASSERT(mPlainBuffer.IsEmpty());
268 if (NS_WARN_IF(!mPlainBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
269 fallible))) {
270 return false;
274 return true;
277 template <typename CipherStrategy>
278 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Tell(
279 int64_t* const aRetval) {
280 MOZ_ASSERT(aRetval);
282 if (!mBaseStream) {
283 return NS_BASE_STREAM_CLOSED;
286 if (!EnsureBuffers()) {
287 return NS_ERROR_OUT_OF_MEMORY;
290 int64_t basePosition;
291 nsresult rv = (*mBaseSeekableStream)->Tell(&basePosition);
292 if (NS_WARN_IF(NS_FAILED(rv))) {
293 return rv;
296 const auto fullBlocks = basePosition / *mBlockSize;
297 MOZ_ASSERT(0 == basePosition % *mBlockSize);
299 *aRetval = (fullBlocks - ((mPlainBytes || mLastBlockLength) ? 1 : 0)) *
300 mEncryptedBlock->MaxPayloadLength() +
301 mNextByte + (mNextByte ? 0 : mLastBlockLength);
302 return NS_OK;
305 template <typename CipherStrategy>
306 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Seek(const int32_t aWhence,
307 int64_t aOffset) {
308 if (!mBaseStream) {
309 return NS_BASE_STREAM_CLOSED;
312 if (!EnsureBuffers()) {
313 return NS_ERROR_OUT_OF_MEMORY;
316 int64_t baseBlocksOffset;
317 int64_t nextByteOffset;
318 switch (aWhence) {
319 case NS_SEEK_CUR:
320 // XXX Simplify this without using Tell.
322 int64_t current;
323 nsresult rv = Tell(&current);
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return rv;
328 aOffset += current;
330 break;
331 case NS_SEEK_SET:
332 break;
334 case NS_SEEK_END:
335 // XXX Simplify this without using Seek/Tell.
337 // XXX The size of the stream could also be queried and stored once
338 // only.
339 nsresult rv = (*mBaseSeekableStream)->Seek(NS_SEEK_SET, 0);
340 if (NS_WARN_IF(NS_FAILED(rv))) {
341 return rv;
344 uint64_t baseStreamSize;
345 rv = (*mBaseStream)->Available(&baseStreamSize);
346 if (NS_WARN_IF(NS_FAILED(rv))) {
347 return rv;
350 auto decryptedStreamSizeOrErr = [baseStreamSize,
351 this]() -> Result<int64_t, nsresult> {
352 if (!baseStreamSize) {
353 return 0;
356 nsresult rv =
357 (*mBaseSeekableStream)
358 ->Seek(NS_SEEK_END, -static_cast<int64_t>(*mBlockSize));
359 if (NS_WARN_IF(NS_FAILED(rv))) {
360 return Err(rv);
363 mNextByte = 0;
364 mPlainBytes = 0;
366 uint32_t bytesRead;
367 rv = ParseNextChunk(&bytesRead);
368 if (NS_WARN_IF(NS_FAILED(rv))) {
369 return Err(rv);
371 MOZ_ASSERT(bytesRead);
373 // XXX Shouldn't ParseNextChunk better update mPlainBytes?
374 mPlainBytes = bytesRead;
376 mNextByte = bytesRead;
378 int64_t current;
379 rv = Tell(&current);
380 if (NS_WARN_IF(NS_FAILED(rv))) {
381 return Err(rv);
384 return current;
385 }();
387 if (decryptedStreamSizeOrErr.isErr()) {
388 return decryptedStreamSizeOrErr.unwrapErr();
391 aOffset += decryptedStreamSizeOrErr.unwrap();
393 break;
395 default:
396 return NS_ERROR_ILLEGAL_VALUE;
399 baseBlocksOffset = aOffset / mEncryptedBlock->MaxPayloadLength();
400 nextByteOffset = aOffset % mEncryptedBlock->MaxPayloadLength();
402 // XXX If we remain in the same block as before, we can skip this.
403 nsresult rv =
404 (*mBaseSeekableStream)->Seek(NS_SEEK_SET, baseBlocksOffset * *mBlockSize);
405 if (NS_WARN_IF(NS_FAILED(rv))) {
406 return rv;
409 mNextByte = 0;
410 mPlainBytes = 0;
412 uint32_t readBytes;
413 rv = ParseNextChunk(&readBytes);
414 if (NS_WARN_IF(NS_FAILED(rv))) {
415 // XXX Do we need to do more here? Restore any previous state?
416 return rv;
419 // We positioned after the last block, we must read that to know its size.
420 // XXX We could know earlier if we positioned us after the last block.
421 if (!readBytes) {
422 if (baseBlocksOffset == 0) {
423 // The stream is empty.
424 return aOffset == 0 ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
427 nsresult rv = (*mBaseSeekableStream)->Seek(NS_SEEK_CUR, -*mBlockSize);
428 if (NS_WARN_IF(NS_FAILED(rv))) {
429 return rv;
432 rv = ParseNextChunk(&readBytes);
433 if (NS_WARN_IF(NS_FAILED(rv))) {
434 // XXX Do we need to do more here? Restore any previous state?
435 return rv;
439 mPlainBytes = readBytes;
440 mNextByte = nextByteOffset;
442 return NS_OK;
445 template <typename CipherStrategy>
446 NS_IMETHODIMP DecryptingInputStream<CipherStrategy>::Clone(
447 nsIInputStream** _retval) {
448 if (!mBaseStream) {
449 return NS_BASE_STREAM_CLOSED;
452 if (!(*mBaseCloneableInputStream)->GetCloneable()) {
453 return NS_ERROR_FAILURE;
456 nsCOMPtr<nsIInputStream> clonedStream;
457 nsresult rv =
458 (*mBaseCloneableInputStream)->Clone(getter_AddRefs(clonedStream));
459 if (NS_WARN_IF(NS_FAILED(rv))) {
460 return rv;
463 *_retval = MakeAndAddRef<DecryptingInputStream>(
464 WrapNotNull(std::move(clonedStream)), *mBlockSize, *mKey)
465 .take();
467 return NS_OK;
470 template <typename CipherStrategy>
471 void DecryptingInputStream<CipherStrategy>::Serialize(
472 mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize,
473 uint32_t* aSizeUsed) {
474 MOZ_ASSERT(mBaseStream);
475 MOZ_ASSERT(mBaseIPCSerializableInputStream);
477 mozilla::ipc::InputStreamParams baseStreamParams;
478 (*mBaseIPCSerializableInputStream)
479 ->Serialize(baseStreamParams, aMaxSize, aSizeUsed);
481 MOZ_ASSERT(baseStreamParams.type() ==
482 mozilla::ipc::InputStreamParams::TFileInputStreamParams);
484 mozilla::ipc::EncryptedFileInputStreamParams encryptedFileInputStreamParams;
485 encryptedFileInputStreamParams.fileInputStreamParams() =
486 std::move(baseStreamParams);
487 encryptedFileInputStreamParams.key().AppendElements(
488 mCipherStrategy.SerializeKey(*mKey));
489 encryptedFileInputStreamParams.blockSize() = *mBlockSize;
491 aParams = std::move(encryptedFileInputStreamParams);
494 template <typename CipherStrategy>
495 bool DecryptingInputStream<CipherStrategy>::Deserialize(
496 const mozilla::ipc::InputStreamParams& aParams) {
497 MOZ_ASSERT(aParams.type() ==
498 mozilla::ipc::InputStreamParams::TEncryptedFileInputStreamParams);
499 const auto& params = aParams.get_EncryptedFileInputStreamParams();
501 nsCOMPtr<nsIFileInputStream> stream;
502 nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream),
503 getter_AddRefs(stream));
504 nsCOMPtr<nsIIPCSerializableInputStream> baseSerializable =
505 do_QueryInterface(stream);
507 if (NS_WARN_IF(
508 !baseSerializable->Deserialize(params.fileInputStreamParams()))) {
509 return false;
512 Init(WrapNotNull<nsCOMPtr<nsIInputStream>>(std::move(stream)),
513 params.blockSize());
515 auto key = mCipherStrategy.DeserializeKey(params.key());
516 if (NS_WARN_IF(!key)) {
517 return false;
520 mKey.init(*key);
521 if (NS_WARN_IF(
522 NS_FAILED(mCipherStrategy.Init(CipherMode::Decrypt, params.key())))) {
523 return false;
526 return true;
529 } // namespace mozilla::dom::quota
531 #endif