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 "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 "nsComponentManagerUtils.h"
13 #include "nsIGlobalObject.h"
14 #include "nsIMultiplexInputStream.h"
15 #include "nsReadableUtils.h"
16 #include "nsRFPService.h"
17 #include "nsStringStream.h"
19 #include "nsJSUtils.h"
20 #include "nsContentUtils.h"
23 using namespace mozilla
;
24 using namespace mozilla::dom
;
27 already_AddRefed
<MultipartBlobImpl
> MultipartBlobImpl::Create(
28 nsTArray
<RefPtr
<BlobImpl
>>&& aBlobImpls
, const nsAString
& aName
,
29 const nsAString
& aContentType
, RTPCallerType aRTPCallerType
,
31 RefPtr
<MultipartBlobImpl
> blobImpl
=
32 new MultipartBlobImpl(std::move(aBlobImpls
), aName
, aContentType
);
33 blobImpl
->SetLengthAndModifiedDate(Some(aRTPCallerType
), aRv
);
34 if (NS_WARN_IF(aRv
.Failed())) {
38 return blobImpl
.forget();
42 already_AddRefed
<MultipartBlobImpl
> MultipartBlobImpl::Create(
43 nsTArray
<RefPtr
<BlobImpl
>>&& aBlobImpls
, const nsAString
& aContentType
,
45 RefPtr
<MultipartBlobImpl
> blobImpl
=
46 new MultipartBlobImpl(std::move(aBlobImpls
), aContentType
);
47 blobImpl
->SetLengthAndModifiedDate(/* aRTPCallerType */ Nothing(), aRv
);
48 if (NS_WARN_IF(aRv
.Failed())) {
52 return blobImpl
.forget();
55 void MultipartBlobImpl::CreateInputStream(nsIInputStream
** aStream
,
56 ErrorResult
& aRv
) const {
59 uint32_t length
= mBlobImpls
.Length();
60 if (length
== 0 || mLength
== 0) {
61 aRv
= NS_NewCStringInputStream(aStream
, ""_ns
);
66 BlobImpl
* blobImpl
= mBlobImpls
.ElementAt(0);
67 blobImpl
->CreateInputStream(aStream
, aRv
);
71 nsCOMPtr
<nsIMultiplexInputStream
> stream
=
72 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
73 if (NS_WARN_IF(!stream
)) {
74 aRv
.Throw(NS_ERROR_FAILURE
);
79 for (i
= 0; i
< length
; i
++) {
80 nsCOMPtr
<nsIInputStream
> scratchStream
;
81 BlobImpl
* blobImpl
= mBlobImpls
.ElementAt(i
).get();
83 // nsIMultiplexInputStream doesn't work well with empty sub streams. Let's
84 // skip the empty blobs.
85 uint32_t size
= blobImpl
->GetSize(aRv
);
86 if (NS_WARN_IF(aRv
.Failed())) {
94 blobImpl
->CreateInputStream(getter_AddRefs(scratchStream
), aRv
);
95 if (NS_WARN_IF(aRv
.Failed())) {
99 aRv
= stream
->AppendStream(scratchStream
);
100 if (NS_WARN_IF(aRv
.Failed())) {
105 CallQueryInterface(stream
, aStream
);
108 already_AddRefed
<BlobImpl
> MultipartBlobImpl::CreateSlice(
109 uint64_t aStart
, uint64_t aLength
, const nsAString
& aContentType
,
110 ErrorResult
& aRv
) const {
111 // If we clamped to nothing we create an empty blob
112 nsTArray
<RefPtr
<BlobImpl
>> blobImpls
;
114 uint64_t length
= aLength
;
115 uint64_t skipStart
= aStart
;
117 // Prune the list of blobs if we can
119 for (i
= 0; length
&& skipStart
&& i
< mBlobImpls
.Length(); i
++) {
120 BlobImpl
* blobImpl
= mBlobImpls
[i
].get();
122 uint64_t l
= blobImpl
->GetSize(aRv
);
123 if (NS_WARN_IF(aRv
.Failed())) {
128 uint64_t upperBound
= std::min
<uint64_t>(l
- skipStart
, length
);
130 RefPtr
<BlobImpl
> firstBlobImpl
=
131 blobImpl
->CreateSlice(skipStart
, upperBound
, aContentType
, aRv
);
132 if (NS_WARN_IF(aRv
.Failed())) {
136 // Avoid wrapping a single blob inside an MultipartBlobImpl
137 if (length
== upperBound
) {
138 return firstBlobImpl
.forget();
141 blobImpls
.AppendElement(firstBlobImpl
);
142 length
-= upperBound
;
149 // Now append enough blobs until we're done
150 for (; length
&& i
< mBlobImpls
.Length(); i
++) {
151 BlobImpl
* blobImpl
= mBlobImpls
[i
].get();
153 uint64_t l
= blobImpl
->GetSize(aRv
);
154 if (NS_WARN_IF(aRv
.Failed())) {
159 RefPtr
<BlobImpl
> lastBlobImpl
=
160 blobImpl
->CreateSlice(0, length
, aContentType
, aRv
);
161 if (NS_WARN_IF(aRv
.Failed())) {
165 blobImpls
.AppendElement(lastBlobImpl
);
167 blobImpls
.AppendElement(blobImpl
);
169 length
-= std::min
<uint64_t>(l
, length
);
172 // we can create our blob now
173 RefPtr
<BlobImpl
> impl
= Create(std::move(blobImpls
), aContentType
, aRv
);
174 if (NS_WARN_IF(aRv
.Failed())) {
178 return impl
.forget();
181 void MultipartBlobImpl::InitializeBlob(RTPCallerType aRTPCallerType
,
183 SetLengthAndModifiedDate(Some(aRTPCallerType
), aRv
);
184 NS_WARNING_ASSERTION(!aRv
.Failed(), "SetLengthAndModifiedDate failed");
187 void MultipartBlobImpl::InitializeBlob(const Sequence
<Blob::BlobPart
>& aData
,
188 const nsAString
& aContentType
,
190 RTPCallerType aRTPCallerType
,
192 mContentType
= aContentType
;
195 for (uint32_t i
= 0, len
= aData
.Length(); i
< len
; ++i
) {
196 const Blob::BlobPart
& data
= aData
[i
];
199 RefPtr
<Blob
> blob
= data
.GetAsBlob().get();
200 aRv
= blobSet
.AppendBlobImpl(blob
->Impl());
206 else if (data
.IsUTF8String()) {
207 aRv
= blobSet
.AppendUTF8String(data
.GetAsUTF8String(), aNativeEOL
);
214 auto blobData
= CreateFromTypedArrayData
<Vector
<uint8_t>>(data
);
215 if (blobData
.isNothing()) {
216 MOZ_CRASH("Impossible blob data type.");
219 if (!blobData
.ref()) {
220 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
225 aRv
= blobSet
.AppendVector(blobData
.ref().extract());
232 mBlobImpls
= blobSet
.GetBlobImpls();
233 SetLengthAndModifiedDate(Some(aRTPCallerType
), aRv
);
234 NS_WARNING_ASSERTION(!aRv
.Failed(), "SetLengthAndModifiedDate failed");
237 void MultipartBlobImpl::SetLengthAndModifiedDate(
238 const Maybe
<RTPCallerType
>& aRTPCallerType
, ErrorResult
& aRv
) {
239 MOZ_ASSERT(mLength
== MULTIPARTBLOBIMPL_UNKNOWN_LENGTH
);
240 MOZ_ASSERT_IF(mIsFile
, IsLastModificationDateUnset());
242 uint64_t totalLength
= 0;
243 int64_t lastModified
= 0;
244 bool lastModifiedSet
= false;
246 for (uint32_t index
= 0, count
= mBlobImpls
.Length(); index
< count
;
248 RefPtr
<BlobImpl
>& blob
= mBlobImpls
[index
];
250 uint64_t subBlobLength
= blob
->GetSize(aRv
);
251 if (NS_WARN_IF(aRv
.Failed())) {
255 MOZ_ASSERT(UINT64_MAX
- subBlobLength
>= totalLength
);
256 totalLength
+= subBlobLength
;
258 if (blob
->IsFile()) {
259 int64_t partLastModified
= blob
->GetLastModified(aRv
);
260 if (NS_WARN_IF(aRv
.Failed())) {
264 if (lastModified
< partLastModified
) {
265 lastModified
= partLastModified
* PR_USEC_PER_MSEC
;
266 lastModifiedSet
= true;
271 mLength
= totalLength
;
274 if (lastModifiedSet
) {
275 SetLastModificationDatePrecisely(lastModified
);
277 MOZ_ASSERT(aRTPCallerType
.isSome());
279 // We cannot use PR_Now() because bug 493756 and, for this reason:
280 // var x = new Date(); var f = new File(...);
281 // x.getTime() < f.dateModified.getTime()
283 SetLastModificationDate(aRTPCallerType
.value(), JS_Now());
288 size_t MultipartBlobImpl::GetAllocationSize() const {
289 FallibleTArray
<BlobImpl
*> visitedBlobs
;
291 // We want to report the unique blob allocation, avoiding duplicated blobs in
292 // the multipart blob tree.
294 for (uint32_t i
= 0; i
< mBlobImpls
.Length(); ++i
) {
295 total
+= mBlobImpls
[i
]->GetAllocationSize(visitedBlobs
);
301 size_t MultipartBlobImpl::GetAllocationSize(
302 FallibleTArray
<BlobImpl
*>& aVisitedBlobs
) const {
303 FallibleTArray
<BlobImpl
*> visitedBlobs
;
306 for (BlobImpl
* blobImpl
: mBlobImpls
) {
307 if (!aVisitedBlobs
.Contains(blobImpl
)) {
308 if (NS_WARN_IF(!aVisitedBlobs
.AppendElement(blobImpl
, fallible
))) {
311 total
+= blobImpl
->GetAllocationSize(aVisitedBlobs
);
318 void MultipartBlobImpl::GetBlobImplType(nsAString
& aBlobImplType
) const {
319 aBlobImplType
.AssignLiteral("MultipartBlobImpl[");
321 StringJoinAppend(aBlobImplType
, u
", "_ns
, mBlobImpls
,
322 [](nsAString
& dest
, BlobImpl
* subBlobImpl
) {
323 nsAutoString blobImplType
;
324 subBlobImpl
->GetBlobImplType(blobImplType
);
326 dest
.Append(blobImplType
);
329 aBlobImplType
.AppendLiteral("]");
332 void MultipartBlobImpl::SetLastModified(int64_t aLastModified
) {
333 SetLastModificationDatePrecisely(aLastModified
* PR_USEC_PER_MSEC
);