no bug - Correct some typos in the comments. a=typo-fix
[gecko.git] / netwerk / streamconv / converters / nsMultiMixedConv.cpp
blobdf7294acf9ed75cb034c31c07e37e24ad69029db
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"
9 #include "nsNetCID.h"
10 #include "nsMimeTypes.h"
11 #include "nsIStringStream.h"
12 #include "nsCRT.h"
13 #include "nsIHttpChannelInternal.h"
14 #include "nsURLHelper.h"
15 #include "nsIStreamConverterService.h"
16 #include <algorithm>
17 #include "nsContentSecurityManager.h"
18 #include "nsHttp.h"
19 #include "nsNetUtil.h"
20 #include "nsIURI.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),
32 mListener(aListener),
33 mPartID(aPartID),
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;
45 mByteRangeEnd = aEnd;
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,
59 nsresult aStatus) {
60 // Drop the listener
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;
69 nsCOMPtr<nsIURI> uri;
70 GetURI(getter_AddRefs(uri));
71 NS_GetFilenameFromDisposition(mContentDispositionFilename,
72 mContentDispositionHeader);
73 mContentDisposition =
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)
90 NS_INTERFACE_MAP_END
93 // nsIRequest implementation...
96 NS_IMETHODIMP
97 nsPartChannel::GetName(nsACString& aResult) {
98 return mMultipartChannel->GetName(aResult);
101 NS_IMETHODIMP
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);
109 NS_IMETHODIMP
110 nsPartChannel::GetStatus(nsresult* aResult) {
111 nsresult rv = NS_OK;
113 if (NS_FAILED(mStatus)) {
114 *aResult = mStatus;
115 } else {
116 rv = mMultipartChannel->GetStatus(aResult);
119 return rv;
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);
135 NS_IMETHODIMP
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!
140 mStatus = aStatus;
141 return NS_OK;
144 NS_IMETHODIMP
145 nsPartChannel::GetCanceled(bool* aCanceled) {
146 *aCanceled = NS_FAILED(mStatus);
147 return NS_OK;
150 NS_IMETHODIMP
151 nsPartChannel::Suspend(void) {
152 // Suspending an individual part must not suspend the underlying
153 // multipart channel...
154 // XXX why not?
155 return NS_OK;
158 NS_IMETHODIMP
159 nsPartChannel::Resume(void) {
160 // Resuming an individual part must not resume the underlying
161 // multipart channel...
162 // XXX why not?
163 return NS_OK;
167 // nsIChannel implementation
170 NS_IMETHODIMP
171 nsPartChannel::GetOriginalURI(nsIURI** aURI) {
172 return mMultipartChannel->GetOriginalURI(aURI);
175 NS_IMETHODIMP
176 nsPartChannel::SetOriginalURI(nsIURI* aURI) {
177 return mMultipartChannel->SetOriginalURI(aURI);
180 NS_IMETHODIMP
181 nsPartChannel::GetURI(nsIURI** aURI) { return mMultipartChannel->GetURI(aURI); }
183 NS_IMETHODIMP
184 nsPartChannel::Open(nsIInputStream** aStream) {
185 nsCOMPtr<nsIStreamListener> listener;
186 nsresult rv =
187 nsContentSecurityManager::doContentSecurityCheck(this, listener);
188 NS_ENSURE_SUCCESS(rv, rv);
190 // This channel cannot be opened!
191 return NS_ERROR_FAILURE;
194 NS_IMETHODIMP
195 nsPartChannel::AsyncOpen(nsIStreamListener* aListener) {
196 nsCOMPtr<nsIStreamListener> listener = aListener;
197 nsresult rv =
198 nsContentSecurityManager::doContentSecurityCheck(this, listener);
199 NS_ENSURE_SUCCESS(rv, rv);
201 // This channel cannot be opened!
202 return NS_ERROR_FAILURE;
205 NS_IMETHODIMP
206 nsPartChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
207 *aLoadFlags = mLoadFlags;
208 return NS_OK;
211 NS_IMETHODIMP
212 nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
213 mLoadFlags = aLoadFlags;
214 return NS_OK;
217 NS_IMETHODIMP
218 nsPartChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
219 return GetTRRModeImpl(aTRRMode);
222 NS_IMETHODIMP
223 nsPartChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
224 return SetTRRModeImpl(aTRRMode);
227 NS_IMETHODIMP
228 nsPartChannel::GetIsDocument(bool* aIsDocument) {
229 return NS_GetIsDocumentChannel(this, aIsDocument);
232 NS_IMETHODIMP
233 nsPartChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
234 *aLoadGroup = do_AddRef(mLoadGroup).take();
235 return NS_OK;
238 NS_IMETHODIMP
239 nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
240 mLoadGroup = aLoadGroup;
242 return NS_OK;
245 NS_IMETHODIMP
246 nsPartChannel::GetOwner(nsISupports** aOwner) {
247 return mMultipartChannel->GetOwner(aOwner);
250 NS_IMETHODIMP
251 nsPartChannel::SetOwner(nsISupports* aOwner) {
252 return mMultipartChannel->SetOwner(aOwner);
255 NS_IMETHODIMP
256 nsPartChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
257 return mMultipartChannel->GetLoadInfo(aLoadInfo);
260 NS_IMETHODIMP
261 nsPartChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
262 MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
263 return mMultipartChannel->SetLoadInfo(aLoadInfo);
266 NS_IMETHODIMP
267 nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
268 return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
271 NS_IMETHODIMP
272 nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
273 return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
276 NS_IMETHODIMP
277 nsPartChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
278 return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
281 NS_IMETHODIMP
282 nsPartChannel::GetContentType(nsACString& aContentType) {
283 aContentType = mContentType;
284 return NS_OK;
287 NS_IMETHODIMP
288 nsPartChannel::SetContentType(const nsACString& aContentType) {
289 bool dummy;
290 net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
291 return NS_OK;
294 NS_IMETHODIMP
295 nsPartChannel::GetContentCharset(nsACString& aContentCharset) {
296 aContentCharset = mContentCharset;
297 return NS_OK;
300 NS_IMETHODIMP
301 nsPartChannel::SetContentCharset(const nsACString& aContentCharset) {
302 mContentCharset = aContentCharset;
303 return NS_OK;
306 NS_IMETHODIMP
307 nsPartChannel::GetContentLength(int64_t* aContentLength) {
308 *aContentLength = mContentLength;
309 return NS_OK;
312 NS_IMETHODIMP
313 nsPartChannel::SetContentLength(int64_t aContentLength) {
314 mContentLength = aContentLength;
315 return NS_OK;
318 NS_IMETHODIMP
319 nsPartChannel::GetContentDisposition(uint32_t* aContentDisposition) {
320 if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
322 *aContentDisposition = mContentDisposition;
323 return NS_OK;
326 NS_IMETHODIMP
327 nsPartChannel::SetContentDisposition(uint32_t aContentDisposition) {
328 return NS_ERROR_NOT_AVAILABLE;
331 NS_IMETHODIMP
332 nsPartChannel::GetContentDispositionFilename(
333 nsAString& aContentDispositionFilename) {
334 if (mContentDispositionFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
336 aContentDispositionFilename = mContentDispositionFilename;
337 return NS_OK;
340 NS_IMETHODIMP
341 nsPartChannel::SetContentDispositionFilename(
342 const nsAString& aContentDispositionFilename) {
343 return NS_ERROR_NOT_AVAILABLE;
346 NS_IMETHODIMP
347 nsPartChannel::GetContentDispositionHeader(
348 nsACString& aContentDispositionHeader) {
349 if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
351 aContentDispositionHeader = mContentDispositionHeader;
352 return NS_OK;
355 NS_IMETHODIMP
356 nsPartChannel::GetPartID(uint32_t* aPartID) {
357 *aPartID = mPartID;
358 return NS_OK;
361 NS_IMETHODIMP
362 nsPartChannel::GetIsFirstPart(bool* aIsFirstPart) {
363 *aIsFirstPart = mIsFirstPart;
364 return NS_OK;
367 NS_IMETHODIMP
368 nsPartChannel::GetIsLastPart(bool* aIsLastPart) {
369 *aIsLastPart = mIsLastPart;
370 return NS_OK;
374 // nsIByteRangeRequest implementation...
377 NS_IMETHODIMP
378 nsPartChannel::GetIsByteRangeRequest(bool* aIsByteRangeRequest) {
379 *aIsByteRangeRequest = mIsByteRangeRequest;
381 return NS_OK;
384 NS_IMETHODIMP
385 nsPartChannel::GetStartRange(int64_t* aStartRange) {
386 *aStartRange = mByteRangeStart;
388 return NS_OK;
391 NS_IMETHODIMP
392 nsPartChannel::GetEndRange(int64_t* aEndRange) {
393 *aEndRange = mByteRangeEnd;
394 return NS_OK;
397 NS_IMETHODIMP
398 nsPartChannel::GetBaseChannel(nsIChannel** aReturn) {
399 NS_ENSURE_ARG_POINTER(aReturn);
401 *aReturn = do_AddRef(mMultipartChannel).take();
402 return NS_OK;
405 // nsISupports implementation
406 NS_IMPL_ISUPPORTS(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener,
407 nsIThreadRetargetableStreamListener, nsIRequestObserver)
409 // nsIStreamConverter implementation
411 // No syncronous conversion at this time.
412 NS_IMETHODIMP
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
420 // (us).
421 NS_IMETHODIMP
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
429 // to throw at him.
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;
436 return NS_OK;
439 NS_IMETHODIMP
440 nsMultiMixedConv::GetConvertedType(const nsACString& aFromType,
441 nsIChannel* aChannel, nsACString& aToType) {
442 return NS_ERROR_NOT_IMPLEMENTED;
445 NS_IMETHODIMP
446 nsMultiMixedConv::MaybeRetarget(nsIRequest* request) {
447 return NS_ERROR_NOT_IMPLEMENTED;
450 // nsIRequestObserver implementation
451 NS_IMETHODIMP
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???");
456 nsresult rv;
458 mTotalSent = 0;
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);
468 if (NS_FAILED(rv)) {
469 return rv;
471 nsCString csp;
472 rv = httpChannel->GetResponseHeader("content-security-policy"_ns, csp);
473 if (NS_SUCCEEDED(rv)) {
474 mRootContentSecurityPolicy = csp;
476 } else {
477 // try asking the channel directly
478 rv = mChannel->GetContentType(contentType);
479 if (NS_FAILED(rv)) {
480 return NS_ERROR_FAILURE;
484 Tokenizer p(contentType);
485 p.SkipUntil(Token::Char(';'));
486 if (!p.CheckChar(';')) {
487 return NS_ERROR_CORRUPTED_CONTENT;
489 p.SkipWhites();
490 if (!p.CheckWord("boundary")) {
491 return NS_ERROR_CORRUPTED_CONTENT;
493 p.SkipWhites();
494 if (!p.CheckChar('=')) {
495 return NS_ERROR_CORRUPTED_CONTENT;
497 p.SkipWhites();
498 Unused << p.ReadUntil(Token::Char(';'), mBoundary);
499 mBoundary.Trim(
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);
521 mCRLFToken =
522 mTokenizer.AddCustomToken("\r\n", mTokenizer.CASE_SENSITIVE, false);
524 SwitchToControlParsing();
526 mBoundaryToken =
527 mTokenizer.AddCustomToken(mBoundary, mTokenizer.CASE_SENSITIVE);
528 mBoundaryTokenWithDashes =
529 mTokenizer.AddCustomToken("--"_ns + mBoundary, mTokenizer.CASE_SENSITIVE);
531 return NS_OK;
534 // nsIStreamListener implementation
535 NS_IMETHODIMP
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
557 // away.
558 nsresult rv_send = SendData();
560 return NS_FAILED(rv_send) ? rv_send : rv_feed;
563 NS_IMETHODIMP
564 nsMultiMixedConv::OnDataFinished(nsresult aStatus) { return NS_OK; }
566 NS_IMETHODIMP
567 nsMultiMixedConv::CheckListenerChain() { return NS_ERROR_NOT_IMPLEMENTED; }
569 NS_IMETHODIMP
570 nsMultiMixedConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
571 nsresult rv;
573 if (mPartChannel) {
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)) {
581 aStatus = rv;
583 rv = SendData();
584 if (NS_SUCCEEDED(aStatus)) {
585 aStatus = rv;
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);
600 if (multiListener) {
601 multiListener->OnAfterLastPart(aStatus);
604 return NS_OK;
607 nsresult nsMultiMixedConv::ConsumeToken(Token const& token) {
608 nsresult rv;
610 switch (mParserState) {
611 case PREAMBLE:
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;
617 break;
619 if (token.Equals(mBoundaryToken)) {
620 // And here the opposite from the just above block...
621 mTokenizer.RemoveCustomToken(mBoundaryTokenWithDashes);
622 mParserState = BOUNDARY_CRLF;
623 break;
626 // This is a preamble, just ignore it and wait for the boundary.
627 break;
629 case BOUNDARY_CRLF:
630 if (token.Equals(Token::NewLine())) {
631 mParserState = HEADER_NAME;
632 mResponseHeader = HEADER_UNKNOWN;
633 HeadersToDefault();
634 SetHeaderTokensEnabled(true);
635 break;
637 return NS_ERROR_CORRUPTED_CONTENT;
639 case HEADER_NAME:
640 SetHeaderTokensEnabled(false);
641 if (token.Equals(Token::NewLine())) {
642 mParserState = BODY_INIT;
643 SwitchToBodyParsing();
644 break;
646 for (uint32_t h = HEADER_CONTENT_TYPE; h < HEADER_UNKNOWN; ++h) {
647 if (token.Equals(mHeaderTokens[h])) {
648 mResponseHeader = static_cast<EHeader>(h);
649 break;
652 mParserState = HEADER_SEP;
653 break;
655 case HEADER_SEP:
656 if (token.Equals(Token::Char(':'))) {
657 mParserState = HEADER_VALUE;
658 mResponseHeaderValue.Truncate();
659 break;
661 if (mResponseHeader == HEADER_UNKNOWN) {
662 // If the header is not of any we understand, just pass everything till
663 // ':'
664 break;
666 if (token.Equals(Token::Whitespace())) {
667 // Accept only header-name traling whitespaces after known headers
668 break;
670 return NS_ERROR_CORRUPTED_CONTENT;
672 case HEADER_VALUE:
673 if (token.Equals(Token::Whitespace()) && mResponseHeaderValue.IsEmpty()) {
674 // Eat leading whitespaces
675 break;
677 if (token.Equals(Token::NewLine())) {
678 nsresult rv = ProcessHeader();
679 if (NS_FAILED(rv)) {
680 return rv;
682 mParserState = HEADER_NAME;
683 mResponseHeader = HEADER_UNKNOWN;
684 SetHeaderTokensEnabled(true);
685 } else {
686 mResponseHeaderValue.Append(token.Fragment());
688 break;
690 case BODY_INIT:
691 rv = SendStart();
692 if (NS_FAILED(rv)) {
693 return rv;
695 mParserState = BODY;
696 [[fallthrough]];
698 case BODY: {
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;
705 break;
707 AccumulateData(token);
708 break;
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
713 // data listener.
714 Token token2;
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();
720 break;
722 if (token2.Equals(mBoundaryTokenWithDashes) ||
723 token2.Equals(mBoundaryToken)) {
724 SwitchToControlParsing();
725 mParserState = TRAIL_DASH1;
726 break;
729 AccumulateData(token);
730 AccumulateData(token2);
731 break;
734 case TRAIL_DASH1:
735 if (token.Equals(Token::NewLine())) {
736 rv = SendStop(NS_OK);
737 if (NS_FAILED(rv)) {
738 return rv;
740 mParserState = BOUNDARY_CRLF;
741 mTokenizer.Rollback();
742 break;
744 if (token.Equals(Token::Char('-'))) {
745 mParserState = TRAIL_DASH2;
746 break;
748 return NS_ERROR_CORRUPTED_CONTENT;
750 case TRAIL_DASH2:
751 if (token.Equals(Token::Char('-'))) {
752 mPartChannel->SetIsLastPart();
753 // SendStop calls SendData first.
754 rv = SendStop(NS_OK);
755 if (NS_FAILED(rv)) {
756 return rv;
758 mParserState = EPILOGUE;
759 break;
761 return NS_ERROR_CORRUPTED_CONTENT;
763 case EPILOGUE:
764 // Just ignore
765 break;
767 default:
768 MOZ_ASSERT(false, "Missing parser state handling branch");
769 break;
770 } // switch
772 return NS_OK;
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
802 // workarounds.
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() {
810 nsresult rv = NS_OK;
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,
833 partListener);
835 ++mCurrentPartID;
837 if (mIsByteRangeRequest) {
838 newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
841 mTotalSent = 0;
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)
867 if (loadGroup) {
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)) {
887 aStatus = rv;
889 if (mPartChannel) {
890 rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
891 // don't check for failure here, we need to remove the channel from
892 // the loadgroup.
894 // Remove the channel from its load group (if any)
895 nsCOMPtr<nsILoadGroup> loadGroup;
896 (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
897 if (loadGroup) {
898 (void)loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
902 mPartChannel = nullptr;
903 return rv;
906 void nsMultiMixedConv::AccumulateData(Token const& aToken) {
907 if (!mRawData) {
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();
918 mRawDataLength = 0;
921 mRawDataLength += aToken.Fragment().Length();
924 nsresult nsMultiMixedConv::SendData() {
925 nsresult rv;
927 if (!mRawData) {
928 return NS_OK;
931 nsACString::const_char_iterator rawData = mRawData;
932 mRawData = nullptr;
934 if (!mPartChannel) {
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);
956 mRawData = nullptr;
957 if (NS_FAILED(rv)) return rv;
959 return mPartChannel->SendOnDataAvailable(mContext, ss, offset,
960 mRawDataLength);
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();
978 break;
979 case HEADER_CONTENT_LENGTH:
980 p.SkipWhites();
981 if (!p.ReadInteger(&mContentLength)) {
982 return NS_ERROR_CORRUPTED_CONTENT;
984 break;
985 case HEADER_CONTENT_DISPOSITION:
986 mContentDisposition = mResponseHeaderValue;
987 mContentDisposition.CompressWhitespace();
988 break;
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() &&
994 httpInternal) {
995 DebugOnly<nsresult> rv = httpInternal->SetCookie(mResponseHeaderValue);
996 MOZ_ASSERT(NS_SUCCEEDED(rv));
998 break;
1000 case HEADER_RANGE:
1001 case HEADER_CONTENT_RANGE: {
1002 if (!p.CheckWord("bytes") || !p.CheckWhite()) {
1003 return NS_ERROR_CORRUPTED_CONTENT;
1005 p.SkipWhites();
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);
1016 break;
1018 case HEADER_CONTENT_SECURITY_POLICY: {
1019 mContentSecurityPolicy = mResponseHeaderValue;
1020 mContentSecurityPolicy.CompressWhitespace();
1021 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1022 if (httpChannel) {
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;
1040 break;
1042 case HEADER_UNKNOWN:
1043 // We ignore anything else...
1044 break;
1047 return NS_OK;
1050 nsresult NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv) {
1051 MOZ_ASSERT(aMultiMixedConv != nullptr, "null ptr");
1053 RefPtr<nsMultiMixedConv> conv = new nsMultiMixedConv();
1054 conv.forget(aMultiMixedConv);
1055 return NS_OK;