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/. */
6
7 #include "FileReaderSync.h"
8
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"
32
33 using namespace mozilla;
34 using namespace mozilla::dom;
35 using mozilla::dom::GlobalObject;
36 using mozilla::dom::Optional;
37
38 // static
Constructor(const GlobalObject & aGlobal)39 already_AddRefed<FileReaderSync> FileReaderSync::Constructor(
40 const GlobalObject& aGlobal) {
41 RefPtr<FileReaderSync> frs = new FileReaderSync();
42
43 return frs.forget();
44 }
45
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto,JS::MutableHandle<JSObject * > aReflector)46 bool FileReaderSync::WrapObject(JSContext* aCx,
47 JS::Handle<JSObject*> aGivenProto,
48 JS::MutableHandle<JSObject*> aReflector) {
49 return FileReaderSync_Binding::Wrap(aCx, this, aGivenProto, aReflector);
50 }
51
ReadAsArrayBuffer(JSContext * aCx,JS::Handle<JSObject * > aScopeObj,Blob & aBlob,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)52 void FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
53 JS::Handle<JSObject*> aScopeObj,
54 Blob& aBlob,
55 JS::MutableHandle<JSObject*> aRetval,
56 ErrorResult& aRv) {
57 uint64_t blobSize = aBlob.GetSize(aRv);
58 if (NS_WARN_IF(aRv.Failed())) {
59 return;
60 }
61
62 UniquePtr<char[], JS::FreePolicy> bufferData(
63 js_pod_arena_malloc<char>(js::ArrayBufferContentsArena, blobSize));
64 if (!bufferData) {
65 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
66 return;
67 }
68
69 nsCOMPtr<nsIInputStream> stream;
70 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
71 if (NS_WARN_IF(aRv.Failed())) {
72 return;
73 }
74
75 uint32_t numRead;
76 aRv = SyncRead(stream, bufferData.get(), blobSize, &numRead);
77 if (NS_WARN_IF(aRv.Failed())) {
78 return;
79 }
80
81 // The file is changed in the meantime?
82 if (numRead != blobSize) {
83 aRv.Throw(NS_ERROR_FAILURE);
84 return;
85 }
86
87 JSObject* arrayBuffer =
88 JS::NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
89 if (!arrayBuffer) {
90 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
91 return;
92 }
93 // arrayBuffer takes the ownership when it is not null. Otherwise we
94 // need to release it explicitly.
95 mozilla::Unused << bufferData.release();
96
97 aRetval.set(arrayBuffer);
98 }
99
ReadAsBinaryString(Blob & aBlob,nsAString & aResult,ErrorResult & aRv)100 void FileReaderSync::ReadAsBinaryString(Blob& aBlob, nsAString& aResult,
101 ErrorResult& aRv) {
102 nsCOMPtr<nsIInputStream> stream;
103 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
104 if (NS_WARN_IF(aRv.Failed())) {
105 return;
106 }
107
108 uint32_t numRead;
109 do {
110 char readBuf[4096];
111 aRv = SyncRead(stream, readBuf, sizeof(readBuf), &numRead);
112 if (NS_WARN_IF(aRv.Failed())) {
113 return;
114 }
115
116 uint32_t oldLength = aResult.Length();
117 AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
118 if (aResult.Length() - oldLength != numRead) {
119 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
120 return;
121 }
122 } while (numRead > 0);
123 }
124
ReadAsText(Blob & aBlob,const Optional<nsAString> & aEncoding,nsAString & aResult,ErrorResult & aRv)125 void FileReaderSync::ReadAsText(Blob& aBlob,
126 const Optional<nsAString>& aEncoding,
127 nsAString& aResult, ErrorResult& aRv) {
128 nsCOMPtr<nsIInputStream> stream;
129 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
130 if (NS_WARN_IF(aRv.Failed())) {
131 return;
132 }
133
134 nsCString sniffBuf;
135 if (!sniffBuf.SetLength(3, fallible)) {
136 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
137 return;
138 }
139
140 uint32_t numRead = 0;
141 aRv = SyncRead(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
142 if (NS_WARN_IF(aRv.Failed())) {
143 return;
144 }
145
146 // No data, we don't need to continue.
147 if (numRead == 0) {
148 aResult.Truncate();
149 return;
150 }
151
152 // Try the API argument.
153 const Encoding* encoding =
154 aEncoding.WasPassed() ? Encoding::ForLabel(aEncoding.Value()) : nullptr;
155 if (!encoding) {
156 // API argument failed. Try the type property of the blob.
157 nsAutoString type16;
158 aBlob.GetType(type16);
159 NS_ConvertUTF16toUTF8 type(type16);
160 nsAutoCString specifiedCharset;
161 bool haveCharset;
162 int32_t charsetStart, charsetEnd;
163 NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset,
164 &charsetStart, &charsetEnd);
165 encoding = Encoding::ForLabel(specifiedCharset);
166 if (!encoding) {
167 // Type property failed. Use UTF-8.
168 encoding = UTF_8_ENCODING;
169 }
170 }
171
172 if (numRead < sniffBuf.Length()) {
173 sniffBuf.Truncate(numRead);
174 }
175
176 // Let's recreate the full stream using a:
177 // multiplexStream(syncStream + original stream)
178 // In theory, we could try to see if the inputStream is a nsISeekableStream,
179 // but this doesn't work correctly for nsPipe3 - See bug 1349570.
180
181 nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
182 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
183 if (NS_WARN_IF(!multiplexStream)) {
184 aRv.Throw(NS_ERROR_FAILURE);
185 return;
186 }
187
188 nsCOMPtr<nsIInputStream> sniffStringStream;
189 aRv = NS_NewCStringInputStream(getter_AddRefs(sniffStringStream), sniffBuf);
190 if (NS_WARN_IF(aRv.Failed())) {
191 return;
192 }
193
194 aRv = multiplexStream->AppendStream(sniffStringStream);
195 if (NS_WARN_IF(aRv.Failed())) {
196 return;
197 }
198
199 uint64_t blobSize = aBlob.GetSize(aRv);
200 if (NS_WARN_IF(aRv.Failed())) {
201 return;
202 }
203
204 nsCOMPtr<nsIInputStream> syncStream;
205 aRv = ConvertAsyncToSyncStream(blobSize - sniffBuf.Length(), stream.forget(),
206 getter_AddRefs(syncStream));
207 if (NS_WARN_IF(aRv.Failed())) {
208 return;
209 }
210
211 // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
212 // already closed or there is nothing to read.
213 if (syncStream) {
214 aRv = multiplexStream->AppendStream(syncStream);
215 if (NS_WARN_IF(aRv.Failed())) {
216 return;
217 }
218 }
219
220 nsAutoCString charset;
221 encoding->Name(charset);
222
223 nsCOMPtr<nsIInputStream> multiplex(do_QueryInterface(multiplexStream));
224 aRv = ConvertStream(multiplex, charset.get(), aResult);
225 if (NS_WARN_IF(aRv.Failed())) {
226 return;
227 }
228 }
229
ReadAsDataURL(Blob & aBlob,nsAString & aResult,ErrorResult & aRv)230 void FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
231 ErrorResult& aRv) {
232 nsAutoString scratchResult;
233 scratchResult.AssignLiteral("data:");
234
235 nsString contentType;
236 aBlob.GetType(contentType);
237
238 if (contentType.IsEmpty()) {
239 scratchResult.AppendLiteral("application/octet-stream");
240 } else {
241 scratchResult.Append(contentType);
242 }
243 scratchResult.AppendLiteral(";base64,");
244
245 nsCOMPtr<nsIInputStream> stream;
246 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
247 if (NS_WARN_IF(aRv.Failed())) {
248 return;
249 }
250
251 uint64_t blobSize = aBlob.GetSize(aRv);
252 if (NS_WARN_IF(aRv.Failed())) {
253 return;
254 }
255
256 nsCOMPtr<nsIInputStream> syncStream;
257 aRv = ConvertAsyncToSyncStream(blobSize, stream.forget(),
258 getter_AddRefs(syncStream));
259 if (NS_WARN_IF(aRv.Failed())) {
260 return;
261 }
262
263 MOZ_ASSERT(syncStream);
264
265 uint64_t size;
266 aRv = syncStream->Available(&size);
267 if (NS_WARN_IF(aRv.Failed())) {
268 return;
269 }
270
271 // The file is changed in the meantime?
272 if (blobSize != size) {
273 return;
274 }
275
276 nsAutoString encodedData;
277 aRv = Base64EncodeInputStream(syncStream, encodedData, size);
278 if (NS_WARN_IF(aRv.Failed())) {
279 return;
280 }
281
282 scratchResult.Append(encodedData);
283
284 aResult = scratchResult;
285 }
286
ConvertStream(nsIInputStream * aStream,const char * aCharset,nsAString & aResult)287 nsresult FileReaderSync::ConvertStream(nsIInputStream* aStream,
288 const char* aCharset,
289 nsAString& aResult) {
290 nsCOMPtr<nsIConverterInputStream> converterStream =
291 do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
292 NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
293
294 nsresult rv = converterStream->Init(
295 aStream, aCharset, 8192,
296 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
297 NS_ENSURE_SUCCESS(rv, rv);
298
299 nsCOMPtr<nsIUnicharInputStream> unicharStream = converterStream;
300 NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE);
301
302 uint32_t numChars;
303 nsString result;
304 while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
305 numChars > 0) {
306 uint32_t oldLength = aResult.Length();
307 aResult.Append(result);
308 if (aResult.Length() - oldLength != result.Length()) {
309 return NS_ERROR_OUT_OF_MEMORY;
310 }
311 }
312
313 return rv;
314 }
315
316 namespace {
317
318 // This runnable is used to terminate the sync event loop.
319 class ReadReadyRunnable final : public WorkerSyncRunnable {
320 public:
ReadReadyRunnable(WorkerPrivate * aWorkerPrivate,nsIEventTarget * aSyncLoopTarget)321 ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
322 nsIEventTarget* aSyncLoopTarget)
323 : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget) {}
324
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)325 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
326 aWorkerPrivate->AssertIsOnWorkerThread();
327 MOZ_ASSERT(mSyncLoopTarget);
328
329 nsCOMPtr<nsIEventTarget> syncLoopTarget;
330 mSyncLoopTarget.swap(syncLoopTarget);
331
332 aWorkerPrivate->StopSyncLoop(syncLoopTarget, true);
333 return true;
334 }
335
336 private:
337 ~ReadReadyRunnable() = default;
338 };
339
340 // This class implements nsIInputStreamCallback and it will be called when the
341 // stream is ready to be read.
342 class ReadCallback final : public nsIInputStreamCallback {
343 public:
344 NS_DECL_THREADSAFE_ISUPPORTS
345
ReadCallback(WorkerPrivate * aWorkerPrivate,nsIEventTarget * aEventTarget)346 ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
347 : mWorkerPrivate(aWorkerPrivate), mEventTarget(aEventTarget) {}
348
349 NS_IMETHOD
OnInputStreamReady(nsIAsyncInputStream * aStream)350 OnInputStreamReady(nsIAsyncInputStream* aStream) override {
351 // I/O Thread. Now we need to block the sync event loop.
352 RefPtr<ReadReadyRunnable> runnable =
353 new ReadReadyRunnable(mWorkerPrivate, mEventTarget);
354 return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
355 }
356
357 private:
358 ~ReadCallback() = default;
359
360 // The worker is kept alive because of the sync event loop.
361 WorkerPrivate* mWorkerPrivate;
362 nsCOMPtr<nsIEventTarget> mEventTarget;
363 };
364
365 NS_IMPL_ADDREF(ReadCallback);
366 NS_IMPL_RELEASE(ReadCallback);
367
368 NS_INTERFACE_MAP_BEGIN(ReadCallback)
369 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
370 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
371 NS_INTERFACE_MAP_END
372
373 } // namespace
374
SyncRead(nsIInputStream * aStream,char * aBuffer,uint32_t aBufferSize,uint32_t * aTotalBytesRead)375 nsresult FileReaderSync::SyncRead(nsIInputStream* aStream, char* aBuffer,
376 uint32_t aBufferSize,
377 uint32_t* aTotalBytesRead) {
378 MOZ_ASSERT(aStream);
379 MOZ_ASSERT(aBuffer);
380 MOZ_ASSERT(aTotalBytesRead);
381
382 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
383 MOZ_ASSERT(workerPrivate);
384
385 *aTotalBytesRead = 0;
386
387 nsCOMPtr<nsIAsyncInputStream> asyncStream;
388 nsCOMPtr<nsIEventTarget> target;
389
390 while (*aTotalBytesRead < aBufferSize) {
391 uint32_t currentBytesRead = 0;
392
393 // Let's read something.
394 nsresult rv =
395 aStream->Read(aBuffer + *aTotalBytesRead,
396 aBufferSize - *aTotalBytesRead, ¤tBytesRead);
397
398 // Nothing else to read.
399 if (rv == NS_BASE_STREAM_CLOSED ||
400 (NS_SUCCEEDED(rv) && currentBytesRead == 0)) {
401 return NS_OK;
402 }
403
404 // An error.
405 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
406 return rv;
407 }
408
409 // All good.
410 if (NS_SUCCEEDED(rv)) {
411 *aTotalBytesRead += currentBytesRead;
412 continue;
413 }
414
415 // We need to proceed async.
416 if (!asyncStream) {
417 asyncStream = do_QueryInterface(aStream);
418 if (!asyncStream) {
419 return rv;
420 }
421 }
422
423 AutoSyncLoopHolder syncLoop(workerPrivate, Canceling);
424
425 nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
426 if (!syncLoopTarget) {
427 // SyncLoop creation can fail if the worker is shutting down.
428 return NS_ERROR_DOM_INVALID_STATE_ERR;
429 }
430
431 RefPtr<ReadCallback> callback =
432 new ReadCallback(workerPrivate, syncLoopTarget);
433
434 if (!target) {
435 target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
436 MOZ_ASSERT(target);
437 }
438
439 rv = asyncStream->AsyncWait(callback, 0, aBufferSize - *aTotalBytesRead,
440 target);
441 if (NS_WARN_IF(NS_FAILED(rv))) {
442 return rv;
443 }
444
445 if (!syncLoop.Run()) {
446 return NS_ERROR_DOM_INVALID_STATE_ERR;
447 }
448 }
449
450 return NS_OK;
451 }
452
ConvertAsyncToSyncStream(uint64_t aStreamSize,already_AddRefed<nsIInputStream> aAsyncStream,nsIInputStream ** aSyncStream)453 nsresult FileReaderSync::ConvertAsyncToSyncStream(
454 uint64_t aStreamSize, already_AddRefed<nsIInputStream> aAsyncStream,
455 nsIInputStream** aSyncStream) {
456 nsCOMPtr<nsIInputStream> asyncInputStream = std::move(aAsyncStream);
457
458 // If the stream is not async, we just need it to be bufferable.
459 nsCOMPtr<nsIAsyncInputStream> asyncStream =
460 do_QueryInterface(asyncInputStream);
461 if (!asyncStream) {
462 return NS_NewBufferedInputStream(aSyncStream, asyncInputStream.forget(),
463 4096);
464 }
465
466 nsAutoCString buffer;
467 if (!buffer.SetLength(aStreamSize, fallible)) {
468 return NS_ERROR_OUT_OF_MEMORY;
469 }
470
471 uint32_t read;
472 nsresult rv =
473 SyncRead(asyncInputStream, buffer.BeginWriting(), aStreamSize, &read);
474 if (NS_WARN_IF(NS_FAILED(rv))) {
475 return rv;
476 }
477
478 if (read != aStreamSize) {
479 return NS_ERROR_FAILURE;
480 }
481
482 rv = NS_NewCStringInputStream(aSyncStream, std::move(buffer));
483 if (NS_WARN_IF(NS_FAILED(rv))) {
484 return rv;
485 }
486
487 return NS_OK;
488 }
489