Bug 1890277: part 2) Add `require-trusted-types-for` directive to CSP parser, guarded...
[gecko.git] / dom / file / FileReaderSync.cpp
blob04e5325ea3ce267cfaf29d6efbb97fc1d39d2f9d
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FileReaderSync.h"
9 #include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
10 #include "js/RootingAPI.h" // JS::{,Mutable}Handle
11 #include "js/Utility.h" // js::ArrayBufferContentsArena, JS::FreePolicy, js_pod_arena_malloc
12 #include "mozilla/Unused.h"
13 #include "mozilla/Base64.h"
14 #include "mozilla/dom/File.h"
15 #include "mozilla/Encoding.h"
16 #include "mozilla/dom/FileReaderSyncBinding.h"
17 #include "nsCExternalHandlerService.h"
18 #include "nsComponentManagerUtils.h"
19 #include "nsCOMPtr.h"
20 #include "nsError.h"
21 #include "nsIConverterInputStream.h"
22 #include "nsIInputStream.h"
23 #include "nsIMultiplexInputStream.h"
24 #include "nsStreamUtils.h"
25 #include "nsStringStream.h"
26 #include "nsISupportsImpl.h"
27 #include "nsNetUtil.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsIAsyncInputStream.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRunnable.h"
33 using namespace mozilla;
34 using namespace mozilla::dom;
35 using mozilla::dom::GlobalObject;
36 using mozilla::dom::Optional;
38 // static
39 UniquePtr<FileReaderSync> FileReaderSync::Constructor(
40 const GlobalObject& aGlobal) {
41 return MakeUnique<FileReaderSync>();
44 bool FileReaderSync::WrapObject(JSContext* aCx,
45 JS::Handle<JSObject*> aGivenProto,
46 JS::MutableHandle<JSObject*> aReflector) {
47 return FileReaderSync_Binding::Wrap(aCx, this, aGivenProto, aReflector);
50 void FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
51 JS::Handle<JSObject*> aScopeObj,
52 Blob& aBlob,
53 JS::MutableHandle<JSObject*> aRetval,
54 ErrorResult& aRv) {
55 uint64_t blobSize = aBlob.GetSize(aRv);
56 if (NS_WARN_IF(aRv.Failed())) {
57 return;
60 UniquePtr<char[], JS::FreePolicy> bufferData(
61 js_pod_arena_malloc<char>(js::ArrayBufferContentsArena, blobSize));
62 if (!bufferData) {
63 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
64 return;
67 nsCOMPtr<nsIInputStream> stream;
68 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
69 if (NS_WARN_IF(aRv.Failed())) {
70 return;
73 uint32_t numRead;
74 aRv = SyncRead(stream, bufferData.get(), blobSize, &numRead);
75 if (NS_WARN_IF(aRv.Failed())) {
76 return;
79 // The file is changed in the meantime?
80 if (numRead != blobSize) {
81 aRv.Throw(NS_ERROR_FAILURE);
82 return;
85 JSObject* arrayBuffer =
86 JS::NewArrayBufferWithContents(aCx, blobSize, std::move(bufferData));
87 if (!arrayBuffer) {
88 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
89 return;
92 aRetval.set(arrayBuffer);
95 void FileReaderSync::ReadAsBinaryString(Blob& aBlob, nsAString& aResult,
96 ErrorResult& aRv) {
97 nsCOMPtr<nsIInputStream> stream;
98 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
99 if (NS_WARN_IF(aRv.Failed())) {
100 return;
103 uint32_t numRead;
104 do {
105 char readBuf[4096];
106 aRv = SyncRead(stream, readBuf, sizeof(readBuf), &numRead);
107 if (NS_WARN_IF(aRv.Failed())) {
108 return;
111 uint32_t oldLength = aResult.Length();
112 AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
113 if (aResult.Length() - oldLength != numRead) {
114 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
115 return;
117 } while (numRead > 0);
120 void FileReaderSync::ReadAsText(Blob& aBlob,
121 const Optional<nsAString>& aEncoding,
122 nsAString& aResult, ErrorResult& aRv) {
123 nsCOMPtr<nsIInputStream> stream;
124 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
125 if (NS_WARN_IF(aRv.Failed())) {
126 return;
129 nsCString sniffBuf;
130 if (!sniffBuf.SetLength(3, fallible)) {
131 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
132 return;
135 uint32_t numRead = 0;
136 aRv = SyncRead(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
137 if (NS_WARN_IF(aRv.Failed())) {
138 return;
141 // No data, we don't need to continue.
142 if (numRead == 0) {
143 aResult.Truncate();
144 return;
147 // Try the API argument.
148 const Encoding* encoding =
149 aEncoding.WasPassed() ? Encoding::ForLabel(aEncoding.Value()) : nullptr;
150 if (!encoding) {
151 // API argument failed. Try the type property of the blob.
152 nsAutoString type16;
153 aBlob.GetType(type16);
154 NS_ConvertUTF16toUTF8 type(type16);
155 nsAutoCString specifiedCharset;
156 bool haveCharset;
157 int32_t charsetStart, charsetEnd;
158 NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset,
159 &charsetStart, &charsetEnd);
160 encoding = Encoding::ForLabel(specifiedCharset);
161 if (!encoding) {
162 // Type property failed. Use UTF-8.
163 encoding = UTF_8_ENCODING;
167 if (numRead < sniffBuf.Length()) {
168 sniffBuf.Truncate(numRead);
171 // Let's recreate the full stream using a:
172 // multiplexStream(syncStream + original stream)
173 // In theory, we could try to see if the inputStream is a nsISeekableStream,
174 // but this doesn't work correctly for nsPipe3 - See bug 1349570.
176 nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
177 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
178 if (NS_WARN_IF(!multiplexStream)) {
179 aRv.Throw(NS_ERROR_FAILURE);
180 return;
183 nsCOMPtr<nsIInputStream> sniffStringStream;
184 aRv = NS_NewCStringInputStream(getter_AddRefs(sniffStringStream), sniffBuf);
185 if (NS_WARN_IF(aRv.Failed())) {
186 return;
189 aRv = multiplexStream->AppendStream(sniffStringStream);
190 if (NS_WARN_IF(aRv.Failed())) {
191 return;
194 uint64_t blobSize = aBlob.GetSize(aRv);
195 if (NS_WARN_IF(aRv.Failed())) {
196 return;
199 nsCOMPtr<nsIInputStream> syncStream;
200 aRv = ConvertAsyncToSyncStream(blobSize - sniffBuf.Length(), stream.forget(),
201 getter_AddRefs(syncStream));
202 if (NS_WARN_IF(aRv.Failed())) {
203 return;
206 // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
207 // already closed or there is nothing to read.
208 if (syncStream) {
209 aRv = multiplexStream->AppendStream(syncStream);
210 if (NS_WARN_IF(aRv.Failed())) {
211 return;
215 nsAutoCString charset;
216 encoding->Name(charset);
218 nsCOMPtr<nsIInputStream> multiplex(do_QueryInterface(multiplexStream));
219 aRv = ConvertStream(multiplex, charset.get(), aResult);
220 if (NS_WARN_IF(aRv.Failed())) {
221 return;
225 void FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
226 ErrorResult& aRv) {
227 nsAutoString scratchResult;
228 scratchResult.AssignLiteral("data:");
230 nsString contentType;
231 aBlob.GetType(contentType);
233 if (contentType.IsEmpty()) {
234 scratchResult.AppendLiteral("application/octet-stream");
235 } else {
236 scratchResult.Append(contentType);
238 scratchResult.AppendLiteral(";base64,");
240 nsCOMPtr<nsIInputStream> stream;
241 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
242 if (NS_WARN_IF(aRv.Failed())) {
243 return;
246 uint64_t blobSize = aBlob.GetSize(aRv);
247 if (NS_WARN_IF(aRv.Failed())) {
248 return;
251 nsCOMPtr<nsIInputStream> syncStream;
252 aRv = ConvertAsyncToSyncStream(blobSize, stream.forget(),
253 getter_AddRefs(syncStream));
254 if (NS_WARN_IF(aRv.Failed())) {
255 return;
258 MOZ_ASSERT(syncStream);
260 uint64_t size;
261 aRv = syncStream->Available(&size);
262 if (NS_WARN_IF(aRv.Failed())) {
263 return;
266 // The file is changed in the meantime?
267 if (blobSize != size) {
268 return;
271 nsAutoString encodedData;
272 aRv = Base64EncodeInputStream(syncStream, encodedData, size);
273 if (NS_WARN_IF(aRv.Failed())) {
274 return;
277 scratchResult.Append(encodedData);
279 aResult = scratchResult;
282 nsresult FileReaderSync::ConvertStream(nsIInputStream* aStream,
283 const char* aCharset,
284 nsAString& aResult) {
285 nsCOMPtr<nsIConverterInputStream> converterStream =
286 do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
287 NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
289 nsresult rv = converterStream->Init(
290 aStream, aCharset, 8192,
291 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
292 NS_ENSURE_SUCCESS(rv, rv);
294 nsCOMPtr<nsIUnicharInputStream> unicharStream = converterStream;
295 NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE);
297 uint32_t numChars;
298 nsString result;
299 while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
300 numChars > 0) {
301 uint32_t oldLength = aResult.Length();
302 aResult.Append(result);
303 if (aResult.Length() - oldLength != result.Length()) {
304 return NS_ERROR_OUT_OF_MEMORY;
308 return rv;
311 namespace {
313 // This runnable is used to terminate the sync event loop.
314 class ReadReadyRunnable final : public WorkerSyncRunnable {
315 public:
316 ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
317 nsIEventTarget* aSyncLoopTarget)
318 : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget,
319 "ReadReadyRunnable") {}
321 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
322 aWorkerPrivate->AssertIsOnWorkerThread();
323 MOZ_ASSERT(mSyncLoopTarget);
325 nsCOMPtr<nsIEventTarget> syncLoopTarget;
326 mSyncLoopTarget.swap(syncLoopTarget);
328 aWorkerPrivate->StopSyncLoop(syncLoopTarget, NS_OK);
329 return true;
332 private:
333 ~ReadReadyRunnable() override = default;
336 // This class implements nsIInputStreamCallback and it will be called when the
337 // stream is ready to be read.
338 class ReadCallback final : public nsIInputStreamCallback {
339 public:
340 NS_DECL_THREADSAFE_ISUPPORTS
342 ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
343 : mWorkerPrivate(aWorkerPrivate), mEventTarget(aEventTarget) {}
345 NS_IMETHOD
346 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
347 // I/O Thread. Now we need to block the sync event loop.
348 RefPtr<ReadReadyRunnable> runnable =
349 new ReadReadyRunnable(mWorkerPrivate, mEventTarget);
350 return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
353 private:
354 ~ReadCallback() = default;
356 // The worker is kept alive because of the sync event loop.
357 WorkerPrivate* mWorkerPrivate;
358 nsCOMPtr<nsIEventTarget> mEventTarget;
361 NS_IMPL_ADDREF(ReadCallback);
362 NS_IMPL_RELEASE(ReadCallback);
364 NS_INTERFACE_MAP_BEGIN(ReadCallback)
365 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
366 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
367 NS_INTERFACE_MAP_END
369 } // namespace
371 nsresult FileReaderSync::SyncRead(nsIInputStream* aStream, char* aBuffer,
372 uint32_t aBufferSize,
373 uint32_t* aTotalBytesRead) {
374 MOZ_ASSERT(aStream);
375 MOZ_ASSERT(aBuffer);
376 MOZ_ASSERT(aTotalBytesRead);
378 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
379 MOZ_ASSERT(workerPrivate);
381 *aTotalBytesRead = 0;
383 nsCOMPtr<nsIAsyncInputStream> asyncStream;
384 nsCOMPtr<nsIEventTarget> target;
386 while (*aTotalBytesRead < aBufferSize) {
387 uint32_t currentBytesRead = 0;
389 // Let's read something.
390 nsresult rv =
391 aStream->Read(aBuffer + *aTotalBytesRead,
392 aBufferSize - *aTotalBytesRead, &currentBytesRead);
394 // Nothing else to read.
395 if (rv == NS_BASE_STREAM_CLOSED ||
396 (NS_SUCCEEDED(rv) && currentBytesRead == 0)) {
397 return NS_OK;
400 // An error.
401 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
402 return rv;
405 // All good.
406 if (NS_SUCCEEDED(rv)) {
407 *aTotalBytesRead += currentBytesRead;
408 continue;
411 // We need to proceed async.
412 if (!asyncStream) {
413 asyncStream = do_QueryInterface(aStream);
414 if (!asyncStream) {
415 return rv;
419 AutoSyncLoopHolder syncLoop(workerPrivate, Canceling);
421 nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
422 syncLoop.GetSerialEventTarget();
423 if (!syncLoopTarget) {
424 // SyncLoop creation can fail if the worker is shutting down.
425 return NS_ERROR_DOM_INVALID_STATE_ERR;
428 RefPtr<ReadCallback> callback =
429 new ReadCallback(workerPrivate, syncLoopTarget);
431 if (!target) {
432 target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
433 MOZ_ASSERT(target);
436 rv = asyncStream->AsyncWait(callback, 0, aBufferSize - *aTotalBytesRead,
437 target);
438 if (NS_WARN_IF(NS_FAILED(rv))) {
439 return rv;
442 if (NS_WARN_IF(NS_FAILED(syncLoop.Run()))) {
443 return NS_ERROR_DOM_INVALID_STATE_ERR;
447 return NS_OK;
450 nsresult FileReaderSync::ConvertAsyncToSyncStream(
451 uint64_t aStreamSize, already_AddRefed<nsIInputStream> aAsyncStream,
452 nsIInputStream** aSyncStream) {
453 nsCOMPtr<nsIInputStream> asyncInputStream = std::move(aAsyncStream);
455 // If the stream is not async, we just need it to be bufferable.
456 nsCOMPtr<nsIAsyncInputStream> asyncStream =
457 do_QueryInterface(asyncInputStream);
458 if (!asyncStream) {
459 return NS_NewBufferedInputStream(aSyncStream, asyncInputStream.forget(),
460 4096);
463 nsAutoCString buffer;
464 if (!buffer.SetLength(aStreamSize, fallible)) {
465 return NS_ERROR_OUT_OF_MEMORY;
468 uint32_t read;
469 nsresult rv =
470 SyncRead(asyncInputStream, buffer.BeginWriting(), aStreamSize, &read);
471 if (NS_WARN_IF(NS_FAILED(rv))) {
472 return rv;
475 if (read != aStreamSize) {
476 return NS_ERROR_FAILURE;
479 rv = NS_NewCStringInputStream(aSyncStream, std::move(buffer));
480 if (NS_WARN_IF(NS_FAILED(rv))) {
481 return rv;
484 return NS_OK;