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