Coverage Report

Created: 2018-09-25 14:53

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