1 /* -*- Mode: C++; tab-width: 2; 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/. */
6 #include "nsMultiMixedConv.h"
7 #include "nsIHttpChannel.h"
8 #include "nsIThreadRetargetableStreamListener.h"
10 #include "nsMimeTypes.h"
11 #include "nsIStringStream.h"
13 #include "nsIHttpChannelInternal.h"
14 #include "nsURLHelper.h"
15 #include "nsIStreamConverterService.h"
17 #include "nsContentSecurityManager.h"
19 #include "nsNetUtil.h"
21 #include "nsHttpHeaderArray.h"
22 #include "mozilla/AutoRestore.h"
23 #include "mozilla/Tokenizer.h"
24 #include "nsComponentManagerUtils.h"
25 #include "mozilla/StaticPrefs_network.h"
27 using namespace mozilla
;
29 nsPartChannel::nsPartChannel(nsIChannel
* aMultipartChannel
, uint32_t aPartID
,
30 bool aIsFirstPart
, nsIStreamListener
* aListener
)
31 : mMultipartChannel(aMultipartChannel
),
34 mIsFirstPart(aIsFirstPart
) {
35 // Inherit the load flags from the original channel...
36 mMultipartChannel
->GetLoadFlags(&mLoadFlags
);
38 mMultipartChannel
->GetLoadGroup(getter_AddRefs(mLoadGroup
));
41 void nsPartChannel::InitializeByteRange(int64_t aStart
, int64_t aEnd
) {
42 mIsByteRangeRequest
= true;
44 mByteRangeStart
= aStart
;
48 nsresult
nsPartChannel::SendOnStartRequest(nsISupports
* aContext
) {
49 return mListener
->OnStartRequest(this);
52 nsresult
nsPartChannel::SendOnDataAvailable(nsISupports
* aContext
,
53 nsIInputStream
* aStream
,
54 uint64_t aOffset
, uint32_t aLen
) {
55 return mListener
->OnDataAvailable(this, aStream
, aOffset
, aLen
);
58 nsresult
nsPartChannel::SendOnStopRequest(nsISupports
* aContext
,
61 nsCOMPtr
<nsIStreamListener
> listener
;
62 listener
.swap(mListener
);
63 return listener
->OnStopRequest(this, aStatus
);
66 void nsPartChannel::SetContentDisposition(
67 const nsACString
& aContentDispositionHeader
) {
68 mContentDispositionHeader
= aContentDispositionHeader
;
70 GetURI(getter_AddRefs(uri
));
71 NS_GetFilenameFromDisposition(mContentDispositionFilename
,
72 mContentDispositionHeader
);
74 NS_GetContentDispositionFromHeader(mContentDispositionHeader
, this);
78 // nsISupports implementation...
81 NS_IMPL_ADDREF(nsPartChannel
)
82 NS_IMPL_RELEASE(nsPartChannel
)
84 NS_INTERFACE_MAP_BEGIN(nsPartChannel
)
85 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIChannel
)
86 NS_INTERFACE_MAP_ENTRY(nsIRequest
)
87 NS_INTERFACE_MAP_ENTRY(nsIChannel
)
88 NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest
)
89 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel
)
93 // nsIRequest implementation...
97 nsPartChannel::GetName(nsACString
& aResult
) {
98 return mMultipartChannel
->GetName(aResult
);
102 nsPartChannel::IsPending(bool* aResult
) {
103 // For now, consider the active lifetime of each part the same as
104 // the underlying multipart channel... This is not exactly right,
105 // but it is good enough :-)
106 return mMultipartChannel
->IsPending(aResult
);
110 nsPartChannel::GetStatus(nsresult
* aResult
) {
113 if (NS_FAILED(mStatus
)) {
116 rv
= mMultipartChannel
->GetStatus(aResult
);
122 NS_IMETHODIMP
nsPartChannel::SetCanceledReason(const nsACString
& aReason
) {
123 return SetCanceledReasonImpl(aReason
);
126 NS_IMETHODIMP
nsPartChannel::GetCanceledReason(nsACString
& aReason
) {
127 return GetCanceledReasonImpl(aReason
);
130 NS_IMETHODIMP
nsPartChannel::CancelWithReason(nsresult aStatus
,
131 const nsACString
& aReason
) {
132 return CancelWithReasonImpl(aStatus
, aReason
);
136 nsPartChannel::Cancel(nsresult aStatus
) {
137 // Cancelling an individual part must not cancel the underlying
138 // multipart channel...
139 // XXX but we should stop sending data for _this_ part channel!
145 nsPartChannel::GetCanceled(bool* aCanceled
) {
146 *aCanceled
= NS_FAILED(mStatus
);
151 nsPartChannel::Suspend(void) {
152 // Suspending an individual part must not suspend the underlying
153 // multipart channel...
159 nsPartChannel::Resume(void) {
160 // Resuming an individual part must not resume the underlying
161 // multipart channel...
167 // nsIChannel implementation
171 nsPartChannel::GetOriginalURI(nsIURI
** aURI
) {
172 return mMultipartChannel
->GetOriginalURI(aURI
);
176 nsPartChannel::SetOriginalURI(nsIURI
* aURI
) {
177 return mMultipartChannel
->SetOriginalURI(aURI
);
181 nsPartChannel::GetURI(nsIURI
** aURI
) { return mMultipartChannel
->GetURI(aURI
); }
184 nsPartChannel::Open(nsIInputStream
** aStream
) {
185 nsCOMPtr
<nsIStreamListener
> listener
;
187 nsContentSecurityManager::doContentSecurityCheck(this, listener
);
188 NS_ENSURE_SUCCESS(rv
, rv
);
190 // This channel cannot be opened!
191 return NS_ERROR_FAILURE
;
195 nsPartChannel::AsyncOpen(nsIStreamListener
* aListener
) {
196 nsCOMPtr
<nsIStreamListener
> listener
= aListener
;
198 nsContentSecurityManager::doContentSecurityCheck(this, listener
);
199 NS_ENSURE_SUCCESS(rv
, rv
);
201 // This channel cannot be opened!
202 return NS_ERROR_FAILURE
;
206 nsPartChannel::GetLoadFlags(nsLoadFlags
* aLoadFlags
) {
207 *aLoadFlags
= mLoadFlags
;
212 nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags
) {
213 mLoadFlags
= aLoadFlags
;
218 nsPartChannel::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
219 return GetTRRModeImpl(aTRRMode
);
223 nsPartChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
224 return SetTRRModeImpl(aTRRMode
);
228 nsPartChannel::GetIsDocument(bool* aIsDocument
) {
229 return NS_GetIsDocumentChannel(this, aIsDocument
);
233 nsPartChannel::GetLoadGroup(nsILoadGroup
** aLoadGroup
) {
234 *aLoadGroup
= do_AddRef(mLoadGroup
).take();
239 nsPartChannel::SetLoadGroup(nsILoadGroup
* aLoadGroup
) {
240 mLoadGroup
= aLoadGroup
;
246 nsPartChannel::GetOwner(nsISupports
** aOwner
) {
247 return mMultipartChannel
->GetOwner(aOwner
);
251 nsPartChannel::SetOwner(nsISupports
* aOwner
) {
252 return mMultipartChannel
->SetOwner(aOwner
);
256 nsPartChannel::GetLoadInfo(nsILoadInfo
** aLoadInfo
) {
257 return mMultipartChannel
->GetLoadInfo(aLoadInfo
);
261 nsPartChannel::SetLoadInfo(nsILoadInfo
* aLoadInfo
) {
262 MOZ_RELEASE_ASSERT(aLoadInfo
, "loadinfo can't be null");
263 return mMultipartChannel
->SetLoadInfo(aLoadInfo
);
267 nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor
** aCallbacks
) {
268 return mMultipartChannel
->GetNotificationCallbacks(aCallbacks
);
272 nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor
* aCallbacks
) {
273 return mMultipartChannel
->SetNotificationCallbacks(aCallbacks
);
277 nsPartChannel::GetSecurityInfo(nsITransportSecurityInfo
** aSecurityInfo
) {
278 return mMultipartChannel
->GetSecurityInfo(aSecurityInfo
);
282 nsPartChannel::GetContentType(nsACString
& aContentType
) {
283 aContentType
= mContentType
;
288 nsPartChannel::SetContentType(const nsACString
& aContentType
) {
290 net_ParseContentType(aContentType
, mContentType
, mContentCharset
, &dummy
);
295 nsPartChannel::GetContentCharset(nsACString
& aContentCharset
) {
296 aContentCharset
= mContentCharset
;
301 nsPartChannel::SetContentCharset(const nsACString
& aContentCharset
) {
302 mContentCharset
= aContentCharset
;
307 nsPartChannel::GetContentLength(int64_t* aContentLength
) {
308 *aContentLength
= mContentLength
;
313 nsPartChannel::SetContentLength(int64_t aContentLength
) {
314 mContentLength
= aContentLength
;
319 nsPartChannel::GetContentDisposition(uint32_t* aContentDisposition
) {
320 if (mContentDispositionHeader
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
322 *aContentDisposition
= mContentDisposition
;
327 nsPartChannel::SetContentDisposition(uint32_t aContentDisposition
) {
328 return NS_ERROR_NOT_AVAILABLE
;
332 nsPartChannel::GetContentDispositionFilename(
333 nsAString
& aContentDispositionFilename
) {
334 if (mContentDispositionFilename
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
336 aContentDispositionFilename
= mContentDispositionFilename
;
341 nsPartChannel::SetContentDispositionFilename(
342 const nsAString
& aContentDispositionFilename
) {
343 return NS_ERROR_NOT_AVAILABLE
;
347 nsPartChannel::GetContentDispositionHeader(
348 nsACString
& aContentDispositionHeader
) {
349 if (mContentDispositionHeader
.IsEmpty()) return NS_ERROR_NOT_AVAILABLE
;
351 aContentDispositionHeader
= mContentDispositionHeader
;
356 nsPartChannel::GetPartID(uint32_t* aPartID
) {
362 nsPartChannel::GetIsFirstPart(bool* aIsFirstPart
) {
363 *aIsFirstPart
= mIsFirstPart
;
368 nsPartChannel::GetIsLastPart(bool* aIsLastPart
) {
369 *aIsLastPart
= mIsLastPart
;
374 // nsIByteRangeRequest implementation...
378 nsPartChannel::GetIsByteRangeRequest(bool* aIsByteRangeRequest
) {
379 *aIsByteRangeRequest
= mIsByteRangeRequest
;
385 nsPartChannel::GetStartRange(int64_t* aStartRange
) {
386 *aStartRange
= mByteRangeStart
;
392 nsPartChannel::GetEndRange(int64_t* aEndRange
) {
393 *aEndRange
= mByteRangeEnd
;
398 nsPartChannel::GetBaseChannel(nsIChannel
** aReturn
) {
399 NS_ENSURE_ARG_POINTER(aReturn
);
401 *aReturn
= do_AddRef(mMultipartChannel
).take();
405 // nsISupports implementation
406 NS_IMPL_ISUPPORTS(nsMultiMixedConv
, nsIStreamConverter
, nsIStreamListener
,
407 nsIThreadRetargetableStreamListener
, nsIRequestObserver
)
409 // nsIStreamConverter implementation
411 // No syncronous conversion at this time.
413 nsMultiMixedConv::Convert(nsIInputStream
* aFromStream
, const char* aFromType
,
414 const char* aToType
, nsISupports
* aCtxt
,
415 nsIInputStream
** _retval
) {
416 return NS_ERROR_NOT_IMPLEMENTED
;
419 // Stream converter service calls this to initialize the actual stream converter
422 nsMultiMixedConv::AsyncConvertData(const char* aFromType
, const char* aToType
,
423 nsIStreamListener
* aListener
,
424 nsISupports
* aCtxt
) {
425 NS_ASSERTION(aListener
&& aFromType
&& aToType
,
426 "null pointer passed into multi mixed converter");
428 // hook up our final listener. this guy gets the various On*() calls we want
431 // WARNING: this listener must be able to handle multiple OnStartRequest,
432 // OnDataAvail() and OnStopRequest() call combinations. We call of series
433 // of these for each sub-part in the raw stream.
434 mFinalListener
= aListener
;
440 nsMultiMixedConv::GetConvertedType(const nsACString
& aFromType
,
441 nsIChannel
* aChannel
, nsACString
& aToType
) {
442 return NS_ERROR_NOT_IMPLEMENTED
;
446 nsMultiMixedConv::MaybeRetarget(nsIRequest
* request
) {
447 return NS_ERROR_NOT_IMPLEMENTED
;
450 // nsIRequestObserver implementation
452 nsMultiMixedConv::OnStartRequest(nsIRequest
* request
) {
453 // we're assuming the content-type is available at this stage
454 NS_ASSERTION(mBoundary
.IsEmpty(), "a second on start???");
459 mChannel
= do_QueryInterface(request
, &rv
);
460 if (NS_FAILED(rv
)) return rv
;
462 nsAutoCString contentType
;
464 // ask the HTTP channel for the content-type and extract the boundary from it.
465 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
, &rv
);
466 if (NS_SUCCEEDED(rv
)) {
467 rv
= httpChannel
->GetResponseHeader("content-type"_ns
, contentType
);
472 rv
= httpChannel
->GetResponseHeader("content-security-policy"_ns
, csp
);
473 if (NS_SUCCEEDED(rv
)) {
474 mRootContentSecurityPolicy
= csp
;
477 // try asking the channel directly
478 rv
= mChannel
->GetContentType(contentType
);
480 return NS_ERROR_FAILURE
;
484 Tokenizer
p(contentType
);
485 p
.SkipUntil(Token::Char(';'));
486 if (!p
.CheckChar(';')) {
487 return NS_ERROR_CORRUPTED_CONTENT
;
490 if (!p
.CheckWord("boundary")) {
491 return NS_ERROR_CORRUPTED_CONTENT
;
494 if (!p
.CheckChar('=')) {
495 return NS_ERROR_CORRUPTED_CONTENT
;
498 Unused
<< p
.ReadUntil(Token::Char(';'), mBoundary
);
500 " \""); // ignoring potential quoted string formatting violations
501 if (mBoundary
.IsEmpty()) {
502 return NS_ERROR_CORRUPTED_CONTENT
;
505 mHeaderTokens
[HEADER_CONTENT_TYPE
] = mTokenizer
.AddCustomToken(
506 "content-type", mTokenizer
.CASE_INSENSITIVE
, false);
507 mHeaderTokens
[HEADER_CONTENT_LENGTH
] = mTokenizer
.AddCustomToken(
508 "content-length", mTokenizer
.CASE_INSENSITIVE
, false);
509 mHeaderTokens
[HEADER_CONTENT_DISPOSITION
] = mTokenizer
.AddCustomToken(
510 "content-disposition", mTokenizer
.CASE_INSENSITIVE
, false);
511 mHeaderTokens
[HEADER_SET_COOKIE
] = mTokenizer
.AddCustomToken(
512 "set-cookie", mTokenizer
.CASE_INSENSITIVE
, false);
513 mHeaderTokens
[HEADER_CONTENT_RANGE
] = mTokenizer
.AddCustomToken(
514 "content-range", mTokenizer
.CASE_INSENSITIVE
, false);
515 mHeaderTokens
[HEADER_RANGE
] =
516 mTokenizer
.AddCustomToken("range", mTokenizer
.CASE_INSENSITIVE
, false);
517 mHeaderTokens
[HEADER_CONTENT_SECURITY_POLICY
] = mTokenizer
.AddCustomToken(
518 "content-security-policy", mTokenizer
.CASE_INSENSITIVE
, false);
520 mLFToken
= mTokenizer
.AddCustomToken("\n", mTokenizer
.CASE_SENSITIVE
, false);
522 mTokenizer
.AddCustomToken("\r\n", mTokenizer
.CASE_SENSITIVE
, false);
524 SwitchToControlParsing();
527 mTokenizer
.AddCustomToken(mBoundary
, mTokenizer
.CASE_SENSITIVE
);
528 mBoundaryTokenWithDashes
=
529 mTokenizer
.AddCustomToken("--"_ns
+ mBoundary
, mTokenizer
.CASE_SENSITIVE
);
534 // nsIStreamListener implementation
536 nsMultiMixedConv::OnDataAvailable(nsIRequest
* request
, nsIInputStream
* inStr
,
537 uint64_t sourceOffset
, uint32_t count
) {
538 // Failing these assertions may indicate that some of the target listeners of
539 // this converter is looping the thead queue, which is harmful to how we
540 // collect the raw (content) data.
541 MOZ_DIAGNOSTIC_ASSERT(!mInOnDataAvailable
,
542 "nsMultiMixedConv::OnDataAvailable reentered!");
543 MOZ_DIAGNOSTIC_ASSERT(
544 !mRawData
, "There are unsent data from the previous tokenizer feed!");
546 if (mInOnDataAvailable
) {
547 // The multipart logic is incapable of being reentered.
548 return NS_ERROR_UNEXPECTED
;
551 mozilla::AutoRestore
<bool> restore(mInOnDataAvailable
);
552 mInOnDataAvailable
= true;
554 nsresult rv_feed
= mTokenizer
.FeedInput(inStr
, count
);
555 // We must do this every time. Regardless if something has failed during the
556 // parsing process. Otherwise the raw data reference would not be thrown
558 nsresult rv_send
= SendData();
560 return NS_FAILED(rv_send
) ? rv_send
: rv_feed
;
564 nsMultiMixedConv::OnDataFinished(nsresult aStatus
) { return NS_OK
; }
567 nsMultiMixedConv::CheckListenerChain() { return NS_ERROR_NOT_IMPLEMENTED
; }
570 nsMultiMixedConv::OnStopRequest(nsIRequest
* request
, nsresult aStatus
) {
574 mPartChannel
->SetIsLastPart();
576 MOZ_DIAGNOSTIC_ASSERT(
577 !mRawData
, "There are unsent data from the previous tokenizer feed!");
579 rv
= mTokenizer
.FinishInput();
580 if (NS_SUCCEEDED(aStatus
)) {
584 if (NS_SUCCEEDED(aStatus
)) {
588 (void)SendStop(aStatus
);
589 } else if (NS_FAILED(aStatus
) && !mRequestListenerNotified
) {
590 // underlying data production problem. we should not be in
591 // the middle of sending data. if we were, mPartChannel,
592 // above, would have been non-null.
594 (void)mFinalListener
->OnStartRequest(request
);
595 (void)mFinalListener
->OnStopRequest(request
, aStatus
);
598 nsCOMPtr
<nsIMultiPartChannelListener
> multiListener
=
599 do_QueryInterface(mFinalListener
);
601 multiListener
->OnAfterLastPart(aStatus
);
607 nsresult
nsMultiMixedConv::ConsumeToken(Token
const& token
) {
610 switch (mParserState
) {
612 if (token
.Equals(mBoundaryTokenWithDashes
)) {
613 // The server first used boundary '--boundary'. Hence, we no longer
614 // accept plain 'boundary' token as a delimiter.
615 mTokenizer
.RemoveCustomToken(mBoundaryToken
);
616 mParserState
= BOUNDARY_CRLF
;
619 if (token
.Equals(mBoundaryToken
)) {
620 // And here the opposite from the just above block...
621 mTokenizer
.RemoveCustomToken(mBoundaryTokenWithDashes
);
622 mParserState
= BOUNDARY_CRLF
;
626 // This is a preamble, just ignore it and wait for the boundary.
630 if (token
.Equals(Token::NewLine())) {
631 mParserState
= HEADER_NAME
;
632 mResponseHeader
= HEADER_UNKNOWN
;
634 SetHeaderTokensEnabled(true);
637 return NS_ERROR_CORRUPTED_CONTENT
;
640 SetHeaderTokensEnabled(false);
641 if (token
.Equals(Token::NewLine())) {
642 mParserState
= BODY_INIT
;
643 SwitchToBodyParsing();
646 for (uint32_t h
= HEADER_CONTENT_TYPE
; h
< HEADER_UNKNOWN
; ++h
) {
647 if (token
.Equals(mHeaderTokens
[h
])) {
648 mResponseHeader
= static_cast<EHeader
>(h
);
652 mParserState
= HEADER_SEP
;
656 if (token
.Equals(Token::Char(':'))) {
657 mParserState
= HEADER_VALUE
;
658 mResponseHeaderValue
.Truncate();
661 if (mResponseHeader
== HEADER_UNKNOWN
) {
662 // If the header is not of any we understand, just pass everything till
666 if (token
.Equals(Token::Whitespace())) {
667 // Accept only header-name traling whitespaces after known headers
670 return NS_ERROR_CORRUPTED_CONTENT
;
673 if (token
.Equals(Token::Whitespace()) && mResponseHeaderValue
.IsEmpty()) {
674 // Eat leading whitespaces
677 if (token
.Equals(Token::NewLine())) {
678 nsresult rv
= ProcessHeader();
682 mParserState
= HEADER_NAME
;
683 mResponseHeader
= HEADER_UNKNOWN
;
684 SetHeaderTokensEnabled(true);
686 mResponseHeaderValue
.Append(token
.Fragment());
699 if (!token
.Equals(mLFToken
) && !token
.Equals(mCRLFToken
)) {
700 if (token
.Equals(mBoundaryTokenWithDashes
) ||
701 token
.Equals(mBoundaryToken
)) {
702 // Allow CRLF to NOT be part of the boundary as well
703 SwitchToControlParsing();
704 mParserState
= TRAIL_DASH1
;
707 AccumulateData(token
);
711 // After CRLF we must explicitly check for boundary. If found,
712 // that CRLF is part of the boundary and must not be send to the
715 if (!mTokenizer
.Next(token2
)) {
716 // Note: this will give us the CRLF token again when more data
717 // or OnStopRequest arrive. I.e. we will enter BODY case in
718 // the very same state as we are now and start this block over.
719 mTokenizer
.NeedMoreInput();
722 if (token2
.Equals(mBoundaryTokenWithDashes
) ||
723 token2
.Equals(mBoundaryToken
)) {
724 SwitchToControlParsing();
725 mParserState
= TRAIL_DASH1
;
729 AccumulateData(token
);
730 AccumulateData(token2
);
735 if (token
.Equals(Token::NewLine())) {
736 rv
= SendStop(NS_OK
);
740 mParserState
= BOUNDARY_CRLF
;
741 mTokenizer
.Rollback();
744 if (token
.Equals(Token::Char('-'))) {
745 mParserState
= TRAIL_DASH2
;
748 return NS_ERROR_CORRUPTED_CONTENT
;
751 if (token
.Equals(Token::Char('-'))) {
752 mPartChannel
->SetIsLastPart();
753 // SendStop calls SendData first.
754 rv
= SendStop(NS_OK
);
758 mParserState
= EPILOGUE
;
761 return NS_ERROR_CORRUPTED_CONTENT
;
768 MOZ_ASSERT(false, "Missing parser state handling branch");
775 void nsMultiMixedConv::SetHeaderTokensEnabled(bool aEnable
) {
776 for (uint32_t h
= HEADER_FIRST
; h
< HEADER_UNKNOWN
; ++h
) {
777 mTokenizer
.EnableCustomToken(mHeaderTokens
[h
], aEnable
);
781 void nsMultiMixedConv::SwitchToBodyParsing() {
782 mTokenizer
.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY
);
783 mTokenizer
.EnableCustomToken(mLFToken
, true);
784 mTokenizer
.EnableCustomToken(mCRLFToken
, true);
785 mTokenizer
.EnableCustomToken(mBoundaryTokenWithDashes
, true);
786 mTokenizer
.EnableCustomToken(mBoundaryToken
, true);
789 void nsMultiMixedConv::SwitchToControlParsing() {
790 mTokenizer
.SetTokenizingMode(Tokenizer::Mode::FULL
);
791 mTokenizer
.EnableCustomToken(mLFToken
, false);
792 mTokenizer
.EnableCustomToken(mCRLFToken
, false);
793 mTokenizer
.EnableCustomToken(mBoundaryTokenWithDashes
, false);
794 mTokenizer
.EnableCustomToken(mBoundaryToken
, false);
797 // nsMultiMixedConv methods
798 nsMultiMixedConv::nsMultiMixedConv()
799 // XXX: This is a hack to bypass the raw pointer to refcounted object in
800 // lambda analysis. It should be removed and replaced when the
801 // IncrementalTokenizer API is improved to avoid the need for such
804 // This is safe because `mTokenizer` will not outlive `this`, meaning
805 // that this std::bind object will be destroyed before `this` dies.
806 : mTokenizer(std::bind(&nsMultiMixedConv::ConsumeToken
, this,
807 std::placeholders::_1
)) {}
809 nsresult
nsMultiMixedConv::SendStart() {
812 nsCOMPtr
<nsIStreamListener
> partListener(mFinalListener
);
813 if (mContentType
.IsEmpty()) {
814 mContentType
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
815 nsCOMPtr
<nsIStreamConverterService
> serv
=
816 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID
, &rv
);
817 if (NS_SUCCEEDED(rv
)) {
818 nsCOMPtr
<nsIStreamListener
> converter
;
819 rv
= serv
->AsyncConvertData(UNKNOWN_CONTENT_TYPE
, "*/*", mFinalListener
,
820 mContext
, getter_AddRefs(converter
));
821 if (NS_SUCCEEDED(rv
)) {
822 partListener
= converter
;
827 // if we already have an mPartChannel, that means we never sent a Stop()
828 // before starting up another "part." that would be bad.
829 MOZ_ASSERT(!mPartChannel
, "tisk tisk, shouldn't be overwriting a channel");
831 nsPartChannel
* newChannel
;
832 newChannel
= new nsPartChannel(mChannel
, mCurrentPartID
, mCurrentPartID
== 0,
837 if (mIsByteRangeRequest
) {
838 newChannel
->InitializeByteRange(mByteRangeStart
, mByteRangeEnd
);
843 // Set up the new part channel...
844 mPartChannel
= newChannel
;
846 rv
= mPartChannel
->SetContentType(mContentType
);
847 if (NS_FAILED(rv
)) return rv
;
849 rv
= mPartChannel
->SetContentLength(mContentLength
);
850 if (NS_FAILED(rv
)) return rv
;
852 mPartChannel
->SetContentDisposition(mContentDisposition
);
854 // Each part of a multipart/replace response can be used
855 // for the top level document. We must inform upper layers
856 // about this by setting the LOAD_REPLACE flag so that certain
857 // state assertions are evaluated as positive.
858 nsLoadFlags loadFlags
= 0;
859 mPartChannel
->GetLoadFlags(&loadFlags
);
860 loadFlags
|= nsIChannel::LOAD_REPLACE
;
861 mPartChannel
->SetLoadFlags(loadFlags
);
863 nsCOMPtr
<nsILoadGroup
> loadGroup
;
864 (void)mPartChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
866 // Add the new channel to the load group (if any)
868 rv
= loadGroup
->AddRequest(mPartChannel
, nullptr);
869 if (NS_FAILED(rv
)) return rv
;
872 // This prevents artificial call to OnStart/StopRequest when the root
873 // channel fails. Since now it's ensured to keep with the nsIStreamListener
874 // contract every time.
875 mRequestListenerNotified
= true;
877 // Let's start off the load. NOTE: we don't forward on the channel passed
878 // into our OnDataAvailable() as it's the root channel for the raw stream.
879 return mPartChannel
->SendOnStartRequest(mContext
);
882 nsresult
nsMultiMixedConv::SendStop(nsresult aStatus
) {
883 // Make sure we send out all accumulcated data prior call to OnStopRequest.
884 // If there is no data, this is a no-op.
885 nsresult rv
= SendData();
886 if (NS_SUCCEEDED(aStatus
)) {
890 rv
= mPartChannel
->SendOnStopRequest(mContext
, aStatus
);
891 // don't check for failure here, we need to remove the channel from
894 // Remove the channel from its load group (if any)
895 nsCOMPtr
<nsILoadGroup
> loadGroup
;
896 (void)mPartChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
898 (void)loadGroup
->RemoveRequest(mPartChannel
, mContext
, aStatus
);
902 mPartChannel
= nullptr;
906 void nsMultiMixedConv::AccumulateData(Token
const& aToken
) {
908 // This is the first read of raw data during this FeedInput loop
909 // of the incremental tokenizer. All 'raw' tokens are coming from
910 // the same linear buffer, hence begining of this loop raw data
911 // is begining of the first raw token. Length of this loop raw
912 // data is just sum of all 'raw' tokens we collect during this loop.
914 // It's ensured we flush (send to to the listener via OnDataAvailable)
915 // and nullify the collected raw data right after FeedInput call.
916 // Hence, the reference can't outlive the actual buffer.
917 mRawData
= aToken
.Fragment().BeginReading();
921 mRawDataLength
+= aToken
.Fragment().Length();
924 nsresult
nsMultiMixedConv::SendData() {
931 nsACString::const_char_iterator rawData
= mRawData
;
935 return NS_ERROR_FAILURE
; // something went wrong w/ processing
938 if (mContentLength
!= UINT64_MAX
) {
939 // make sure that we don't send more than the mContentLength
940 // XXX why? perhaps the Content-Length header was actually wrong!!
941 if ((uint64_t(mRawDataLength
) + mTotalSent
) > mContentLength
) {
942 mRawDataLength
= static_cast<uint32_t>(mContentLength
- mTotalSent
);
945 if (mRawDataLength
== 0) return NS_OK
;
948 uint64_t offset
= mTotalSent
;
949 mTotalSent
+= mRawDataLength
;
951 nsCOMPtr
<nsIStringInputStream
> ss(
952 do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv
));
953 if (NS_FAILED(rv
)) return rv
;
955 rv
= ss
->ShareData(rawData
, mRawDataLength
);
957 if (NS_FAILED(rv
)) return rv
;
959 return mPartChannel
->SendOnDataAvailable(mContext
, ss
, offset
,
963 void nsMultiMixedConv::HeadersToDefault() {
964 mContentLength
= UINT64_MAX
;
965 mContentType
.Truncate();
966 mContentDisposition
.Truncate();
967 mContentSecurityPolicy
.Truncate();
968 mIsByteRangeRequest
= false;
971 nsresult
nsMultiMixedConv::ProcessHeader() {
972 mozilla::Tokenizer
p(mResponseHeaderValue
);
974 switch (mResponseHeader
) {
975 case HEADER_CONTENT_TYPE
:
976 mContentType
= mResponseHeaderValue
;
977 mContentType
.CompressWhitespace();
979 case HEADER_CONTENT_LENGTH
:
981 if (!p
.ReadInteger(&mContentLength
)) {
982 return NS_ERROR_CORRUPTED_CONTENT
;
985 case HEADER_CONTENT_DISPOSITION
:
986 mContentDisposition
= mResponseHeaderValue
;
987 mContentDisposition
.CompressWhitespace();
989 case HEADER_SET_COOKIE
: {
990 nsCOMPtr
<nsIHttpChannelInternal
> httpInternal
=
991 do_QueryInterface(mChannel
);
992 mResponseHeaderValue
.CompressWhitespace();
993 if (!StaticPrefs::network_cookie_prevent_set_cookie_from_multipart() &&
995 DebugOnly
<nsresult
> rv
= httpInternal
->SetCookie(mResponseHeaderValue
);
996 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1001 case HEADER_CONTENT_RANGE
: {
1002 if (!p
.CheckWord("bytes") || !p
.CheckWhite()) {
1003 return NS_ERROR_CORRUPTED_CONTENT
;
1006 if (p
.CheckChar('*')) {
1007 mByteRangeStart
= mByteRangeEnd
= 0;
1008 } else if (!p
.ReadInteger(&mByteRangeStart
) || !p
.CheckChar('-') ||
1009 !p
.ReadInteger(&mByteRangeEnd
)) {
1010 return NS_ERROR_CORRUPTED_CONTENT
;
1012 mIsByteRangeRequest
= true;
1013 if (mContentLength
== UINT64_MAX
) {
1014 mContentLength
= uint64_t(mByteRangeEnd
- mByteRangeStart
+ 1);
1018 case HEADER_CONTENT_SECURITY_POLICY
: {
1019 mContentSecurityPolicy
= mResponseHeaderValue
;
1020 mContentSecurityPolicy
.CompressWhitespace();
1021 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
);
1023 nsCString resultCSP
= mRootContentSecurityPolicy
;
1024 if (!mContentSecurityPolicy
.IsEmpty()) {
1025 // We are updating the root channel CSP header respectively for
1026 // each part as: CSP-root + CSP-partN, where N is the part number.
1027 // Here we append current part's CSP to root CSP and reset CSP
1028 // header for each part.
1029 if (!resultCSP
.IsEmpty()) {
1030 resultCSP
.Append(";");
1032 resultCSP
.Append(mContentSecurityPolicy
);
1034 nsresult rv
= httpChannel
->SetResponseHeader(
1035 "Content-Security-Policy"_ns
, resultCSP
, false);
1036 if (NS_FAILED(rv
)) {
1037 return NS_ERROR_CORRUPTED_CONTENT
;
1042 case HEADER_UNKNOWN
:
1043 // We ignore anything else...
1050 nsresult
NS_NewMultiMixedConv(nsMultiMixedConv
** aMultiMixedConv
) {
1051 MOZ_ASSERT(aMultiMixedConv
!= nullptr, "null ptr");
1053 RefPtr
<nsMultiMixedConv
> conv
= new nsMultiMixedConv();
1054 conv
.forget(aMultiMixedConv
);