Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/file/MultipartBlobImpl.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 "MultipartBlobImpl.h"
8
#include "jsfriendapi.h"
9
#include "mozilla/dom/BlobSet.h"
10
#include "mozilla/dom/FileBinding.h"
11
#include "mozilla/dom/UnionTypes.h"
12
#include "nsIMultiplexInputStream.h"
13
#include "nsRFPService.h"
14
#include "nsStringStream.h"
15
#include "nsTArray.h"
16
#include "nsJSUtils.h"
17
#include "nsContentUtils.h"
18
#include "nsIScriptError.h"
19
#include "nsIXPConnect.h"
20
#include <algorithm>
21
22
using namespace mozilla;
23
using namespace mozilla::dom;
24
25
/* static */ already_AddRefed<MultipartBlobImpl>
26
MultipartBlobImpl::Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
27
                          const nsAString& aName,
28
                          const nsAString& aContentType,
29
                          ErrorResult& aRv)
30
0
{
31
0
  RefPtr<MultipartBlobImpl> blobImpl =
32
0
    new MultipartBlobImpl(std::move(aBlobImpls), aName, aContentType);
33
0
  blobImpl->SetLengthAndModifiedDate(aRv);
34
0
  if (NS_WARN_IF(aRv.Failed())) {
35
0
    return nullptr;
36
0
  }
37
0
38
0
  return blobImpl.forget();
39
0
}
40
41
/* static */ already_AddRefed<MultipartBlobImpl>
42
MultipartBlobImpl::Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
43
                          const nsAString& aContentType,
44
                          ErrorResult& aRv)
45
0
{
46
0
  RefPtr<MultipartBlobImpl> blobImpl =
47
0
    new MultipartBlobImpl(std::move(aBlobImpls), aContentType);
48
0
  blobImpl->SetLengthAndModifiedDate(aRv);
49
0
  if (NS_WARN_IF(aRv.Failed())) {
50
0
    return nullptr;
51
0
  }
52
0
53
0
  return blobImpl.forget();
54
0
}
55
56
void
57
MultipartBlobImpl::CreateInputStream(nsIInputStream** aStream,
58
                                     ErrorResult& aRv)
59
0
{
60
0
  *aStream = nullptr;
61
0
62
0
  uint32_t length = mBlobImpls.Length();
63
0
  if (length == 0) {
64
0
    aRv = NS_NewCStringInputStream(aStream, EmptyCString());
65
0
    return;
66
0
  }
67
0
68
0
  if (length == 1) {
69
0
    BlobImpl* blobImpl = mBlobImpls.ElementAt(0);
70
0
    blobImpl->CreateInputStream(aStream, aRv);
71
0
    return;
72
0
  }
73
0
74
0
  nsCOMPtr<nsIMultiplexInputStream> stream =
75
0
    do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
76
0
  if (NS_WARN_IF(!stream)) {
77
0
    aRv.Throw(NS_ERROR_FAILURE);
78
0
    return;
79
0
  }
80
0
81
0
  uint32_t i;
82
0
  for (i = 0; i < length; i++) {
83
0
    nsCOMPtr<nsIInputStream> scratchStream;
84
0
    BlobImpl* blobImpl = mBlobImpls.ElementAt(i).get();
85
0
86
0
    blobImpl->CreateInputStream(getter_AddRefs(scratchStream), aRv);
87
0
    if (NS_WARN_IF(aRv.Failed())) {
88
0
      return;
89
0
    }
90
0
91
0
    aRv = stream->AppendStream(scratchStream);
92
0
    if (NS_WARN_IF(aRv.Failed())) {
93
0
      return;
94
0
    }
95
0
  }
96
0
97
0
  CallQueryInterface(stream, aStream);
98
0
}
99
100
already_AddRefed<BlobImpl>
101
MultipartBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
102
                               const nsAString& aContentType,
103
                               ErrorResult& aRv)
104
0
{
105
0
  // If we clamped to nothing we create an empty blob
106
0
  nsTArray<RefPtr<BlobImpl>> blobImpls;
107
0
108
0
  uint64_t length = aLength;
109
0
  uint64_t skipStart = aStart;
110
0
111
0
  // Prune the list of blobs if we can
112
0
  uint32_t i;
113
0
  for (i = 0; length && skipStart && i < mBlobImpls.Length(); i++) {
114
0
    BlobImpl* blobImpl = mBlobImpls[i].get();
115
0
116
0
    uint64_t l = blobImpl->GetSize(aRv);
117
0
    if (NS_WARN_IF(aRv.Failed())) {
118
0
      return nullptr;
119
0
    }
120
0
121
0
    if (skipStart < l) {
122
0
      uint64_t upperBound = std::min<uint64_t>(l - skipStart, length);
123
0
124
0
      RefPtr<BlobImpl> firstBlobImpl =
125
0
        blobImpl->CreateSlice(skipStart, upperBound,
126
0
                              aContentType, aRv);
127
0
      if (NS_WARN_IF(aRv.Failed())) {
128
0
        return nullptr;
129
0
      }
130
0
131
0
      // Avoid wrapping a single blob inside an MultipartBlobImpl
132
0
      if (length == upperBound) {
133
0
        return firstBlobImpl.forget();
134
0
      }
135
0
136
0
      blobImpls.AppendElement(firstBlobImpl);
137
0
      length -= upperBound;
138
0
      i++;
139
0
      break;
140
0
    }
141
0
    skipStart -= l;
142
0
  }
143
0
144
0
  // Now append enough blobs until we're done
145
0
  for (; length && i < mBlobImpls.Length(); i++) {
146
0
    BlobImpl* blobImpl = mBlobImpls[i].get();
147
0
148
0
    uint64_t l = blobImpl->GetSize(aRv);
149
0
    if (NS_WARN_IF(aRv.Failed())) {
150
0
      return nullptr;
151
0
    }
152
0
153
0
    if (length < l) {
154
0
      RefPtr<BlobImpl> lastBlobImpl =
155
0
        blobImpl->CreateSlice(0, length, aContentType, aRv);
156
0
      if (NS_WARN_IF(aRv.Failed())) {
157
0
        return nullptr;
158
0
      }
159
0
160
0
      blobImpls.AppendElement(lastBlobImpl);
161
0
    } else {
162
0
      blobImpls.AppendElement(blobImpl);
163
0
    }
164
0
    length -= std::min<uint64_t>(l, length);
165
0
  }
166
0
167
0
  // we can create our blob now
168
0
  RefPtr<BlobImpl> impl = Create(std::move(blobImpls), aContentType, aRv);
169
0
  if (NS_WARN_IF(aRv.Failed())) {
170
0
    return nullptr;
171
0
  }
172
0
173
0
  return impl.forget();
174
0
}
175
176
void
177
MultipartBlobImpl::InitializeBlob(ErrorResult& aRv)
178
0
{
179
0
  SetLengthAndModifiedDate(aRv);
180
0
  NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
181
0
}
182
183
void
184
MultipartBlobImpl::InitializeBlob(const Sequence<Blob::BlobPart>& aData,
185
                                  const nsAString& aContentType,
186
                                  bool aNativeEOL,
187
                                  ErrorResult& aRv)
188
0
{
189
0
  mContentType = aContentType;
190
0
  BlobSet blobSet;
191
0
192
0
  for (uint32_t i = 0, len = aData.Length(); i < len; ++i) {
193
0
    const Blob::BlobPart& data = aData[i];
194
0
195
0
    if (data.IsBlob()) {
196
0
      RefPtr<Blob> blob = data.GetAsBlob().get();
197
0
      aRv = blobSet.AppendBlobImpl(blob->Impl());
198
0
      if (aRv.Failed()) {
199
0
        return;
200
0
      }
201
0
    }
202
0
203
0
    else if (data.IsUSVString()) {
204
0
      aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL);
205
0
      if (aRv.Failed()) {
206
0
        return;
207
0
      }
208
0
    }
209
0
210
0
    else if (data.IsArrayBuffer()) {
211
0
      const ArrayBuffer& buffer = data.GetAsArrayBuffer();
212
0
      buffer.ComputeLengthAndData();
213
0
      aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
214
0
      if (aRv.Failed()) {
215
0
        return;
216
0
      }
217
0
    }
218
0
219
0
    else if (data.IsArrayBufferView()) {
220
0
      const ArrayBufferView& buffer = data.GetAsArrayBufferView();
221
0
      buffer.ComputeLengthAndData();
222
0
      aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
223
0
      if (aRv.Failed()) {
224
0
        return;
225
0
      }
226
0
    }
227
0
228
0
    else {
229
0
      MOZ_CRASH("Impossible blob data type.");
230
0
    }
231
0
  }
232
0
233
0
234
0
  mBlobImpls = blobSet.GetBlobImpls();
235
0
  SetLengthAndModifiedDate(aRv);
236
0
  NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
237
0
}
238
239
void
240
MultipartBlobImpl::SetLengthAndModifiedDate(ErrorResult& aRv)
241
0
{
242
0
  MOZ_ASSERT(mLength == UINT64_MAX);
243
0
  MOZ_ASSERT(mLastModificationDate == INT64_MAX);
244
0
245
0
  uint64_t totalLength = 0;
246
0
  int64_t lastModified = 0;
247
0
  bool lastModifiedSet = false;
248
0
249
0
  for (uint32_t index = 0, count = mBlobImpls.Length(); index < count; index++) {
250
0
    RefPtr<BlobImpl>& blob = mBlobImpls[index];
251
0
252
#ifdef DEBUG
253
    MOZ_ASSERT(!blob->IsSizeUnknown());
254
    MOZ_ASSERT(!blob->IsDateUnknown());
255
#endif
256
257
0
    uint64_t subBlobLength = blob->GetSize(aRv);
258
0
    if (NS_WARN_IF(aRv.Failed())) {
259
0
      return;
260
0
    }
261
0
262
0
    MOZ_ASSERT(UINT64_MAX - subBlobLength >= totalLength);
263
0
    totalLength += subBlobLength;
264
0
265
0
    if (blob->IsFile()) {
266
0
      int64_t partLastModified = blob->GetLastModified(aRv);
267
0
      if (NS_WARN_IF(aRv.Failed())) {
268
0
        return;
269
0
      }
270
0
271
0
      if (lastModified < partLastModified) {
272
0
        lastModified = partLastModified;
273
0
        lastModifiedSet = true;
274
0
      }
275
0
    }
276
0
  }
277
0
278
0
  mLength = totalLength;
279
0
280
0
  if (mIsFile) {
281
0
    // We cannot use PR_Now() because bug 493756 and, for this reason:
282
0
    //   var x = new Date(); var f = new File(...);
283
0
    //   x.getTime() < f.dateModified.getTime()
284
0
    // could fail.
285
0
    mLastModificationDate = nsRFPService::ReduceTimePrecisionAsUSecs(
286
0
      lastModifiedSet ? lastModified * PR_USEC_PER_MSEC : JS_Now(), 0);
287
0
    // mLastModificationDate is an absolute timestamp so we supply a zero context mix-in
288
0
  }
289
0
}
290
291
void
292
MultipartBlobImpl::GetMozFullPathInternal(nsAString& aFilename,
293
                                          ErrorResult& aRv) const
294
0
{
295
0
  if (!mIsFromNsIFile || mBlobImpls.Length() == 0) {
296
0
    BaseBlobImpl::GetMozFullPathInternal(aFilename, aRv);
297
0
    return;
298
0
  }
299
0
300
0
  BlobImpl* blobImpl = mBlobImpls.ElementAt(0).get();
301
0
  if (!blobImpl) {
302
0
    BaseBlobImpl::GetMozFullPathInternal(aFilename, aRv);
303
0
    return;
304
0
  }
305
0
306
0
  blobImpl->GetMozFullPathInternal(aFilename, aRv);
307
0
}
308
309
nsresult
310
MultipartBlobImpl::SetMutable(bool aMutable)
311
0
{
312
0
  nsresult rv;
313
0
314
0
  // This looks a little sketchy since BlobImpl objects are supposed to be
315
0
  // threadsafe. However, we try to enforce that all BlobImpl objects must be
316
0
  // set to immutable *before* being passed to another thread, so this should
317
0
  // be safe.
318
0
  if (!aMutable && !mImmutable && !mBlobImpls.IsEmpty()) {
319
0
    for (uint32_t index = 0, count = mBlobImpls.Length();
320
0
         index < count;
321
0
         index++) {
322
0
      rv = mBlobImpls[index]->SetMutable(aMutable);
323
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
324
0
        return rv;
325
0
      }
326
0
    }
327
0
  }
328
0
329
0
  rv = BaseBlobImpl::SetMutable(aMutable);
330
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
331
0
    return rv;
332
0
  }
333
0
334
0
  MOZ_ASSERT_IF(!aMutable, mImmutable);
335
0
336
0
  return NS_OK;
337
0
}
338
339
nsresult
340
MultipartBlobImpl::InitializeChromeFile(nsIFile* aFile,
341
                                        const nsAString& aType,
342
                                        const nsAString& aName,
343
                                        bool aLastModifiedPassed,
344
                                        int64_t aLastModified,
345
                                        bool aIsFromNsIFile)
346
0
{
347
0
  MOZ_ASSERT(!mImmutable, "Something went wrong ...");
348
0
  if (mImmutable) {
349
0
    return NS_ERROR_UNEXPECTED;
350
0
  }
351
0
352
0
  mName = aName;
353
0
  mContentType = aType;
354
0
  mIsFromNsIFile = aIsFromNsIFile;
355
0
356
0
  bool exists;
357
0
  nsresult rv= aFile->Exists(&exists);
358
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
359
0
    return rv;
360
0
  }
361
0
362
0
  if (!exists) {
363
0
    return NS_ERROR_FILE_NOT_FOUND;
364
0
  }
365
0
366
0
  bool isDir;
367
0
  rv = aFile->IsDirectory(&isDir);
368
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
369
0
    return rv;
370
0
  }
371
0
372
0
  if (isDir) {
373
0
    return NS_ERROR_FILE_IS_DIRECTORY;
374
0
  }
375
0
376
0
  if (mName.IsEmpty()) {
377
0
    aFile->GetLeafName(mName);
378
0
  }
379
0
380
0
  RefPtr<File> blob = File::CreateFromFile(nullptr, aFile);
381
0
382
0
  // Pre-cache size.
383
0
  ErrorResult error;
384
0
  blob->GetSize(error);
385
0
  if (NS_WARN_IF(error.Failed())) {
386
0
    return error.StealNSResult();
387
0
  }
388
0
389
0
  // Pre-cache modified date.
390
0
  blob->GetLastModified(error);
391
0
  if (NS_WARN_IF(error.Failed())) {
392
0
    return error.StealNSResult();
393
0
  }
394
0
395
0
  // XXXkhuey this is terrible
396
0
  if (mContentType.IsEmpty()) {
397
0
    blob->GetType(mContentType);
398
0
  }
399
0
400
0
  BlobSet blobSet;
401
0
  rv = blobSet.AppendBlobImpl(static_cast<File*>(blob.get())->Impl());
402
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
403
0
    return rv;
404
0
  }
405
0
406
0
  mBlobImpls = blobSet.GetBlobImpls();
407
0
408
0
  SetLengthAndModifiedDate(error);
409
0
  if (NS_WARN_IF(error.Failed())) {
410
0
    return error.StealNSResult();
411
0
  }
412
0
413
0
  if (aLastModifiedPassed) {
414
0
    SetLastModified(aLastModified);
415
0
  }
416
0
417
0
  return NS_OK;
418
0
}
419
420
bool
421
MultipartBlobImpl::MayBeClonedToOtherThreads() const
422
0
{
423
0
  for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
424
0
    if (!mBlobImpls[i]->MayBeClonedToOtherThreads()) {
425
0
      return false;
426
0
    }
427
0
  }
428
0
429
0
  return true;
430
0
}
431
432
size_t MultipartBlobImpl::GetAllocationSize() const
433
0
{
434
0
  FallibleTArray<BlobImpl*> visitedBlobs;
435
0
436
0
  // We want to report the unique blob allocation, avoiding duplicated blobs in
437
0
  // the multipart blob tree.
438
0
  size_t total = 0;
439
0
  for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
440
0
    total += mBlobImpls[i]->GetAllocationSize(visitedBlobs);
441
0
  }
442
0
443
0
  return total;
444
0
}
445
446
size_t MultipartBlobImpl::GetAllocationSize(FallibleTArray<BlobImpl*>& aVisitedBlobs) const
447
0
{
448
0
  FallibleTArray<BlobImpl*> visitedBlobs;
449
0
450
0
  size_t total = 0;
451
0
  for (BlobImpl* blobImpl : mBlobImpls) {
452
0
    if (!aVisitedBlobs.Contains(blobImpl)) {
453
0
      if (NS_WARN_IF(!aVisitedBlobs.AppendElement(blobImpl, fallible))) {
454
0
        return 0;
455
0
      }
456
0
      total += blobImpl->GetAllocationSize(aVisitedBlobs);
457
0
    }
458
0
  }
459
0
460
0
  return total;
461
0
}