1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * The MIME stream separates headers and a datastream. It also allows
8 * automatic creation of the content-length header.
11 #include "nsMIMEInputStream.h"
15 #include "ipc/IPCMessageUtils.h"
16 #include "mozilla/Mutex.h"
17 #include "mozilla/ipc/InputStreamUtils.h"
19 #include "nsComponentManagerUtils.h"
20 #include "nsIAsyncInputStream.h"
21 #include "nsIClassInfoImpl.h"
22 #include "nsIHttpHeaderVisitor.h"
23 #include "nsIIPCSerializableInputStream.h"
24 #include "nsIInputStreamLength.h"
25 #include "nsIMIMEInputStream.h"
26 #include "nsISeekableStream.h"
29 using namespace mozilla::ipc
;
32 class nsMIMEInputStream
: public nsIMIMEInputStream
,
33 public nsISeekableStream
,
34 public nsIIPCSerializableInputStream
,
35 public nsIAsyncInputStream
,
36 public nsIInputStreamCallback
,
37 public nsIInputStreamLength
,
38 public nsIAsyncInputStreamLength
,
39 public nsIInputStreamLengthCallback
,
40 public nsICloneableInputStream
{
41 virtual ~nsMIMEInputStream() = default;
44 nsMIMEInputStream() = default;
46 NS_DECL_THREADSAFE_ISUPPORTS
47 NS_DECL_NSIINPUTSTREAM
48 NS_DECL_NSIMIMEINPUTSTREAM
49 NS_DECL_NSISEEKABLESTREAM
50 NS_DECL_NSITELLABLESTREAM
51 NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
52 NS_DECL_NSIASYNCINPUTSTREAM
53 NS_DECL_NSIINPUTSTREAMCALLBACK
54 NS_DECL_NSIINPUTSTREAMLENGTH
55 NS_DECL_NSIASYNCINPUTSTREAMLENGTH
56 NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
57 NS_DECL_NSICLONEABLEINPUTSTREAM
60 struct MOZ_STACK_CLASS ReadSegmentsState
{
61 nsCOMPtr
<nsIInputStream
> mThisStream
;
62 nsWriteSegmentFun mWriter
{nullptr};
63 void* mClosure
{nullptr};
65 static nsresult
ReadSegCb(nsIInputStream
* aIn
, void* aClosure
,
66 const char* aFromRawSegment
, uint32_t aToOffset
,
67 uint32_t aCount
, uint32_t* aWriteCount
);
69 bool IsSeekableInputStream() const;
70 bool IsAsyncInputStream() const;
71 bool IsInputStreamLength() const;
72 bool IsAsyncInputStreamLength() const;
73 bool IsCloneableInputStream() const;
75 nsTArray
<HeaderEntry
> mHeaders
;
77 nsCOMPtr
<nsIInputStream
> mStream
;
78 mozilla::Atomic
<bool, mozilla::Relaxed
> mStartedReading
{false};
80 mozilla::Mutex mMutex
{"nsMIMEInputStream::mMutex"};
81 nsCOMPtr
<nsIInputStreamCallback
> mAsyncWaitCallback
MOZ_GUARDED_BY(mMutex
);
83 // This is protected by mutex.
84 nsCOMPtr
<nsIInputStreamLengthCallback
> mAsyncInputStreamLengthCallback
;
87 NS_IMPL_ADDREF(nsMIMEInputStream
)
88 NS_IMPL_RELEASE(nsMIMEInputStream
)
90 NS_IMPL_CLASSINFO(nsMIMEInputStream
, nullptr, nsIClassInfo::THREADSAFE
,
91 NS_MIMEINPUTSTREAM_CID
)
93 NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream
)
94 NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream
)
95 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream
, nsIMIMEInputStream
)
96 NS_INTERFACE_MAP_ENTRY(nsITellableStream
)
97 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream
, IsSeekableInputStream())
98 NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream
)
99 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream
, IsAsyncInputStream())
100 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback
,
101 IsAsyncInputStream())
102 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIMIMEInputStream
)
103 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength
,
104 IsInputStreamLength())
105 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength
,
106 IsAsyncInputStreamLength())
107 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback
,
108 IsAsyncInputStreamLength())
109 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream
,
110 IsCloneableInputStream())
111 NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream
)
114 NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream
, nsIMIMEInputStream
,
115 nsIAsyncInputStream
, nsIInputStream
,
116 nsISeekableStream
, nsITellableStream
)
119 nsMIMEInputStream::AddHeader(const char* aName
, const char* aValue
) {
120 NS_ENSURE_FALSE(mStartedReading
, NS_ERROR_FAILURE
);
122 HeaderEntry
* entry
= mHeaders
.AppendElement();
123 entry
->name().Append(aName
);
124 entry
->value().Append(aValue
);
130 nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor
* visitor
) {
133 for (auto& header
: mHeaders
) {
134 rv
= visitor
->VisitHeader(header
.name(), header
.value());
143 nsMIMEInputStream::SetData(nsIInputStream
* aStream
) {
144 NS_ENSURE_FALSE(mStartedReading
, NS_ERROR_FAILURE
);
151 nsMIMEInputStream::GetData(nsIInputStream
** aStream
) {
152 NS_ENSURE_ARG_POINTER(aStream
);
153 *aStream
= do_AddRef(mStream
).take();
157 #define INITSTREAMS \
158 if (!mStartedReading) { \
159 NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
160 mStartedReading = true; \
163 // Reset mStartedReading when Seek-ing to start
165 nsMIMEInputStream::Seek(int32_t whence
, int64_t offset
) {
166 NS_ENSURE_TRUE(mStream
, NS_ERROR_UNEXPECTED
);
169 nsCOMPtr
<nsISeekableStream
> stream
= do_QueryInterface(mStream
);
171 if (whence
== NS_SEEK_SET
&& offset
== 0) {
172 rv
= stream
->Seek(whence
, offset
);
173 if (NS_SUCCEEDED(rv
)) mStartedReading
= false;
176 rv
= stream
->Seek(whence
, offset
);
182 // Proxy ReadSegments since we need to be a good little nsIInputStream
183 NS_IMETHODIMP
nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter
,
184 void* aClosure
, uint32_t aCount
,
187 ReadSegmentsState state
;
188 // Disambiguate ambiguous nsIInputStream.
190 static_cast<nsIInputStream
*>(static_cast<nsIMIMEInputStream
*>(this));
191 state
.mWriter
= aWriter
;
192 state
.mClosure
= aClosure
;
193 return mStream
->ReadSegments(ReadSegCb
, &state
, aCount
, _retval
);
196 nsresult
nsMIMEInputStream::ReadSegCb(nsIInputStream
* aIn
, void* aClosure
,
197 const char* aFromRawSegment
,
198 uint32_t aToOffset
, uint32_t aCount
,
199 uint32_t* aWriteCount
) {
200 ReadSegmentsState
* state
= (ReadSegmentsState
*)aClosure
;
201 return (state
->mWriter
)(state
->mThisStream
, state
->mClosure
, aFromRawSegment
,
202 aToOffset
, aCount
, aWriteCount
);
206 * Forward everything else to the mStream after calling INITSTREAMS
210 NS_IMETHODIMP
nsMIMEInputStream::Close(void) {
212 return mStream
->Close();
214 NS_IMETHODIMP
nsMIMEInputStream::Available(uint64_t* _retval
) {
216 return mStream
->Available(_retval
);
218 NS_IMETHODIMP
nsMIMEInputStream::StreamStatus() {
220 return mStream
->StreamStatus();
222 NS_IMETHODIMP
nsMIMEInputStream::Read(char* buf
, uint32_t count
,
225 return mStream
->Read(buf
, count
, _retval
);
227 NS_IMETHODIMP
nsMIMEInputStream::IsNonBlocking(bool* aNonBlocking
) {
229 return mStream
->IsNonBlocking(aNonBlocking
);
232 // nsIAsyncInputStream
234 nsMIMEInputStream::CloseWithStatus(nsresult aStatus
) {
236 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
= do_QueryInterface(mStream
);
237 return asyncStream
->CloseWithStatus(aStatus
);
241 nsMIMEInputStream::AsyncWait(nsIInputStreamCallback
* aCallback
, uint32_t aFlags
,
242 uint32_t aRequestedCount
,
243 nsIEventTarget
* aEventTarget
) {
245 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
= do_QueryInterface(mStream
);
246 if (NS_WARN_IF(!asyncStream
)) {
247 return NS_ERROR_FAILURE
;
250 nsCOMPtr
<nsIInputStreamCallback
> callback
= aCallback
? this : nullptr;
252 mozilla::MutexAutoLock
lock(mMutex
);
253 if (NS_WARN_IF(mAsyncWaitCallback
&& aCallback
&&
254 mAsyncWaitCallback
!= aCallback
)) {
255 return NS_ERROR_FAILURE
;
258 mAsyncWaitCallback
= aCallback
;
261 return asyncStream
->AsyncWait(callback
, aFlags
, aRequestedCount
,
265 // nsIInputStreamCallback
268 nsMIMEInputStream::OnInputStreamReady(nsIAsyncInputStream
* aStream
) {
269 nsCOMPtr
<nsIInputStreamCallback
> callback
;
272 mozilla::MutexAutoLock
lock(mMutex
);
274 // We have been canceled in the meanwhile.
275 if (!mAsyncWaitCallback
) {
279 callback
.swap(mAsyncWaitCallback
);
282 MOZ_ASSERT(callback
);
283 return callback
->OnInputStreamReady(this);
287 NS_IMETHODIMP
nsMIMEInputStream::Tell(int64_t* _retval
) {
289 nsCOMPtr
<nsITellableStream
> stream
= do_QueryInterface(mStream
);
290 return stream
->Tell(_retval
);
294 NS_IMETHODIMP
nsMIMEInputStream::SetEOF(void) {
296 nsCOMPtr
<nsISeekableStream
> stream
= do_QueryInterface(mStream
);
297 return stream
->SetEOF();
301 * Factory method used by do_CreateInstance
304 nsresult
nsMIMEInputStreamConstructor(REFNSIID iid
, void** result
) {
307 RefPtr
<nsMIMEInputStream
> inst
= new nsMIMEInputStream();
308 if (!inst
) return NS_ERROR_OUT_OF_MEMORY
;
310 return inst
->QueryInterface(iid
, result
);
313 void nsMIMEInputStream::SerializedComplexity(uint32_t aMaxSize
,
316 uint32_t* aTransferables
) {
317 if (nsCOMPtr
<nsIIPCSerializableInputStream
> serializable
=
318 do_QueryInterface(mStream
)) {
319 InputStreamHelper::SerializedComplexity(mStream
, aMaxSize
, aSizeUsed
,
320 aPipes
, aTransferables
);
326 void nsMIMEInputStream::Serialize(InputStreamParams
& aParams
, uint32_t aMaxSize
,
327 uint32_t* aSizeUsed
) {
328 MOZ_ASSERT(aSizeUsed
);
331 MIMEInputStreamParams params
;
332 params
.headers() = mHeaders
.Clone();
333 params
.startedReading() = mStartedReading
;
340 InputStreamParams wrappedParams
;
342 if (nsCOMPtr
<nsIIPCSerializableInputStream
> serializable
=
343 do_QueryInterface(mStream
)) {
344 InputStreamHelper::SerializeInputStream(mStream
, wrappedParams
, aMaxSize
,
347 // Falling back to sending the underlying stream over a pipe when
348 // sending an nsMIMEInputStream over IPC is potentially wasteful
349 // if it is sent several times. This can possibly happen with
350 // fission. There are two ways to improve this, see bug 1648369
352 InputStreamHelper::SerializeInputStreamAsPipe(mStream
, wrappedParams
);
355 NS_ASSERTION(wrappedParams
.type() != InputStreamParams::T__None
,
356 "Wrapped stream failed to serialize!");
358 params
.optionalStream().emplace(wrappedParams
);
362 bool nsMIMEInputStream::Deserialize(const InputStreamParams
& aParams
) {
363 if (aParams
.type() != InputStreamParams::TMIMEInputStreamParams
) {
364 NS_ERROR("Received unknown parameters from the other process!");
368 const MIMEInputStreamParams
& params
= aParams
.get_MIMEInputStreamParams();
369 const Maybe
<InputStreamParams
>& wrappedParams
= params
.optionalStream();
371 if (wrappedParams
.isSome()) {
372 nsCOMPtr
<nsIInputStream
> stream
;
373 stream
= InputStreamHelper::DeserializeInputStream(wrappedParams
.ref());
375 NS_WARNING("Failed to deserialize wrapped stream!");
379 MOZ_ALWAYS_SUCCEEDS(SetData(stream
));
382 mHeaders
= params
.headers().Clone();
383 mStartedReading
= params
.startedReading();
389 nsMIMEInputStream::Length(int64_t* aLength
) {
390 nsCOMPtr
<nsIInputStreamLength
> stream
= do_QueryInterface(mStream
);
391 if (NS_WARN_IF(!stream
)) {
392 return NS_ERROR_FAILURE
;
395 return stream
->Length(aLength
);
399 nsMIMEInputStream::AsyncLengthWait(nsIInputStreamLengthCallback
* aCallback
,
400 nsIEventTarget
* aEventTarget
) {
401 nsCOMPtr
<nsIAsyncInputStreamLength
> stream
= do_QueryInterface(mStream
);
402 if (NS_WARN_IF(!stream
)) {
403 return NS_ERROR_FAILURE
;
406 nsCOMPtr
<nsIInputStreamLengthCallback
> callback
= aCallback
? this : nullptr;
408 mozilla::MutexAutoLock
lock(mMutex
);
409 mAsyncInputStreamLengthCallback
= aCallback
;
412 return stream
->AsyncLengthWait(callback
, aEventTarget
);
416 nsMIMEInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength
* aStream
,
418 nsCOMPtr
<nsIInputStreamLengthCallback
> callback
;
420 mozilla::MutexAutoLock
lock(mMutex
);
421 // We have been canceled in the meanwhile.
422 if (!mAsyncInputStreamLengthCallback
) {
426 callback
.swap(mAsyncInputStreamLengthCallback
);
429 MOZ_ASSERT(callback
);
430 return callback
->OnInputStreamLengthReady(this, aLength
);
433 bool nsMIMEInputStream::IsSeekableInputStream() const {
434 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(mStream
);
438 bool nsMIMEInputStream::IsAsyncInputStream() const {
439 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
= do_QueryInterface(mStream
);
440 return !!asyncStream
;
443 bool nsMIMEInputStream::IsInputStreamLength() const {
444 nsCOMPtr
<nsIInputStreamLength
> stream
= do_QueryInterface(mStream
);
448 bool nsMIMEInputStream::IsAsyncInputStreamLength() const {
449 nsCOMPtr
<nsIAsyncInputStreamLength
> stream
= do_QueryInterface(mStream
);
453 bool nsMIMEInputStream::IsCloneableInputStream() const {
454 nsCOMPtr
<nsICloneableInputStream
> stream
= do_QueryInterface(mStream
);
458 // nsICloneableInputStream interface
461 nsMIMEInputStream::GetCloneable(bool* aCloneable
) {
462 nsCOMPtr
<nsICloneableInputStream
> stream
= do_QueryInterface(mStream
);
464 return NS_ERROR_FAILURE
;
467 return stream
->GetCloneable(aCloneable
);
471 nsMIMEInputStream::Clone(nsIInputStream
** aResult
) {
472 nsCOMPtr
<nsICloneableInputStream
> stream
= do_QueryInterface(mStream
);
474 return NS_ERROR_FAILURE
;
477 nsCOMPtr
<nsIInputStream
> clonedStream
;
478 nsresult rv
= stream
->Clone(getter_AddRefs(clonedStream
));
479 if (NS_WARN_IF(NS_FAILED(rv
))) {
483 nsCOMPtr
<nsIMIMEInputStream
> mimeStream
= new nsMIMEInputStream();
485 rv
= mimeStream
->SetData(clonedStream
);
486 if (NS_WARN_IF(NS_FAILED(rv
))) {
490 for (const HeaderEntry
& entry
: mHeaders
) {
491 rv
= mimeStream
->AddHeader(entry
.name().get(), entry
.value().get());
492 MOZ_ASSERT(NS_SUCCEEDED(rv
));
495 static_cast<nsMIMEInputStream
*>(mimeStream
.get())->mStartedReading
=
496 static_cast<bool>(mStartedReading
);
498 mimeStream
.forget(aResult
);