Bug 1814798 - pt 2. Add a PHCManager component to control PHC r=glandium,emilio
[gecko.git] / netwerk / streamconv / converters / nsMultiMixedConv.cpp
blob54e57e41b46cfb9618cedcb5777525cfa7e74ee8
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 "nsNetCID.h"
9 #include "nsMimeTypes.h"
10 #include "nsIStringStream.h"
11 #include "nsCRT.h"
12 #include "nsIHttpChannelInternal.h"
13 #include "nsURLHelper.h"
14 #include "nsIStreamConverterService.h"
15 #include <algorithm>
16 #include "nsContentSecurityManager.h"
17 #include "nsHttp.h"
18 #include "nsNetUtil.h"
19 #include "nsIURI.h"
20 #include "nsHttpHeaderArray.h"
21 #include "mozilla/AutoRestore.h"
22 #include "mozilla/Tokenizer.h"
23 #include "nsComponentManagerUtils.h"
25 using namespace mozilla;
27 nsPartChannel::nsPartChannel(nsIChannel* aMultipartChannel, uint32_t aPartID,
28 bool aIsFirstPart, nsIStreamListener* aListener)
29 : mMultipartChannel(aMultipartChannel),
30 mListener(aListener),
31 mPartID(aPartID),
32 mIsFirstPart(aIsFirstPart) {
33 // Inherit the load flags from the original channel...
34 mMultipartChannel->GetLoadFlags(&mLoadFlags);
36 mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
39 void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd) {
40 mIsByteRangeRequest = true;
42 mByteRangeStart = aStart;
43 mByteRangeEnd = aEnd;
46 nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext) {
47 return mListener->OnStartRequest(this);
50 nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext,
51 nsIInputStream* aStream,
52 uint64_t aOffset, uint32_t aLen) {
53 return mListener->OnDataAvailable(this, aStream, aOffset, aLen);
56 nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext,
57 nsresult aStatus) {
58 // Drop the listener
59 nsCOMPtr<nsIStreamListener> listener;
60 listener.swap(mListener);
61 return listener->OnStopRequest(this, aStatus);
64 void nsPartChannel::SetContentDisposition(
65 const nsACString& aContentDispositionHeader) {
66 mContentDispositionHeader = aContentDispositionHeader;
67 nsCOMPtr<nsIURI> uri;
68 GetURI(getter_AddRefs(uri));
69 NS_GetFilenameFromDisposition(mContentDispositionFilename,
70 mContentDispositionHeader);
71 mContentDisposition =
72 NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
76 // nsISupports implementation...
79 NS_IMPL_ADDREF(nsPartChannel)
80 NS_IMPL_RELEASE(nsPartChannel)
82 NS_INTERFACE_MAP_BEGIN(nsPartChannel)
83 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
84 NS_INTERFACE_MAP_ENTRY(nsIRequest)
85 NS_INTERFACE_MAP_ENTRY(nsIChannel)
86 NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest)
87 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel)
88 NS_INTERFACE_MAP_END
91 // nsIRequest implementation...
94 NS_IMETHODIMP
95 nsPartChannel::GetName(nsACString& aResult) {
96 return mMultipartChannel->GetName(aResult);
99 NS_IMETHODIMP
100 nsPartChannel::IsPending(bool* aResult) {
101 // For now, consider the active lifetime of each part the same as
102 // the underlying multipart channel... This is not exactly right,
103 // but it is good enough :-)
104 return mMultipartChannel->IsPending(aResult);
107 NS_IMETHODIMP
108 nsPartChannel::GetStatus(nsresult* aResult) {
109 nsresult rv = NS_OK;
111 if (NS_FAILED(mStatus)) {
112 *aResult = mStatus;
113 } else {
114 rv = mMultipartChannel->GetStatus(aResult);
117 return rv;
120 NS_IMETHODIMP nsPartChannel::SetCanceledReason(const nsACString& aReason) {
121 return SetCanceledReasonImpl(aReason);
124 NS_IMETHODIMP nsPartChannel::GetCanceledReason(nsACString& aReason) {
125 return GetCanceledReasonImpl(aReason);
128 NS_IMETHODIMP nsPartChannel::CancelWithReason(nsresult aStatus,
129 const nsACString& aReason) {
130 return CancelWithReasonImpl(aStatus, aReason);
133 NS_IMETHODIMP
134 nsPartChannel::Cancel(nsresult aStatus) {
135 // Cancelling an individual part must not cancel the underlying
136 // multipart channel...
137 // XXX but we should stop sending data for _this_ part channel!
138 mStatus = aStatus;
139 return NS_OK;
142 NS_IMETHODIMP
143 nsPartChannel::GetCanceled(bool* aCanceled) {
144 *aCanceled = NS_FAILED(mStatus);
145 return NS_OK;
148 NS_IMETHODIMP
149 nsPartChannel::Suspend(void) {
150 // Suspending an individual part must not suspend the underlying
151 // multipart channel...
152 // XXX why not?
153 return NS_OK;
156 NS_IMETHODIMP
157 nsPartChannel::Resume(void) {
158 // Resuming an individual part must not resume the underlying
159 // multipart channel...
160 // XXX why not?
161 return NS_OK;
165 // nsIChannel implementation
168 NS_IMETHODIMP
169 nsPartChannel::GetOriginalURI(nsIURI** aURI) {
170 return mMultipartChannel->GetOriginalURI(aURI);
173 NS_IMETHODIMP
174 nsPartChannel::SetOriginalURI(nsIURI* aURI) {
175 return mMultipartChannel->SetOriginalURI(aURI);
178 NS_IMETHODIMP
179 nsPartChannel::GetURI(nsIURI** aURI) { return mMultipartChannel->GetURI(aURI); }
181 NS_IMETHODIMP
182 nsPartChannel::Open(nsIInputStream** aStream) {
183 nsCOMPtr<nsIStreamListener> listener;
184 nsresult rv =
185 nsContentSecurityManager::doContentSecurityCheck(this, listener);
186 NS_ENSURE_SUCCESS(rv, rv);
188 // This channel cannot be opened!
189 return NS_ERROR_FAILURE;
192 NS_IMETHODIMP
193 nsPartChannel::AsyncOpen(nsIStreamListener* aListener) {
194 nsCOMPtr<nsIStreamListener> listener = aListener;
195 nsresult rv =
196 nsContentSecurityManager::doContentSecurityCheck(this, listener);
197 NS_ENSURE_SUCCESS(rv, rv);
199 // This channel cannot be opened!
200 return NS_ERROR_FAILURE;
203 NS_IMETHODIMP
204 nsPartChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
205 *aLoadFlags = mLoadFlags;
206 return NS_OK;
209 NS_IMETHODIMP
210 nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
211 mLoadFlags = aLoadFlags;
212 return NS_OK;
215 NS_IMETHODIMP
216 nsPartChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
217 return GetTRRModeImpl(aTRRMode);
220 NS_IMETHODIMP
221 nsPartChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
222 return SetTRRModeImpl(aTRRMode);
225 NS_IMETHODIMP
226 nsPartChannel::GetIsDocument(bool* aIsDocument) {
227 return NS_GetIsDocumentChannel(this, aIsDocument);
230 NS_IMETHODIMP
231 nsPartChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
232 *aLoadGroup = do_AddRef(mLoadGroup).take();
233 return NS_OK;
236 NS_IMETHODIMP
237 nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
238 mLoadGroup = aLoadGroup;
240 return NS_OK;
243 NS_IMETHODIMP
244 nsPartChannel::GetOwner(nsISupports** aOwner) {
245 return mMultipartChannel->GetOwner(aOwner);
248 NS_IMETHODIMP
249 nsPartChannel::SetOwner(nsISupports* aOwner) {
250 return mMultipartChannel->SetOwner(aOwner);
253 NS_IMETHODIMP
254 nsPartChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
255 return mMultipartChannel->GetLoadInfo(aLoadInfo);
258 NS_IMETHODIMP
259 nsPartChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
260 MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
261 return mMultipartChannel->SetLoadInfo(aLoadInfo);
264 NS_IMETHODIMP
265 nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
266 return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
269 NS_IMETHODIMP
270 nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
271 return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
274 NS_IMETHODIMP
275 nsPartChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) {
276 return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
279 NS_IMETHODIMP
280 nsPartChannel::GetContentType(nsACString& aContentType) {
281 aContentType = mContentType;
282 return NS_OK;
285 NS_IMETHODIMP
286 nsPartChannel::SetContentType(const nsACString& aContentType) {
287 bool dummy;
288 net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
289 return NS_OK;
292 NS_IMETHODIMP
293 nsPartChannel::GetContentCharset(nsACString& aContentCharset) {
294 aContentCharset = mContentCharset;
295 return NS_OK;
298 NS_IMETHODIMP
299 nsPartChannel::SetContentCharset(const nsACString& aContentCharset) {
300 mContentCharset = aContentCharset;
301 return NS_OK;
304 NS_IMETHODIMP
305 nsPartChannel::GetContentLength(int64_t* aContentLength) {
306 *aContentLength = mContentLength;
307 return NS_OK;
310 NS_IMETHODIMP
311 nsPartChannel::SetContentLength(int64_t aContentLength) {
312 mContentLength = aContentLength;
313 return NS_OK;
316 NS_IMETHODIMP
317 nsPartChannel::GetContentDisposition(uint32_t* aContentDisposition) {
318 if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
320 *aContentDisposition = mContentDisposition;
321 return NS_OK;
324 NS_IMETHODIMP
325 nsPartChannel::SetContentDisposition(uint32_t aContentDisposition) {
326 return NS_ERROR_NOT_AVAILABLE;
329 NS_IMETHODIMP
330 nsPartChannel::GetContentDispositionFilename(
331 nsAString& aContentDispositionFilename) {
332 if (mContentDispositionFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
334 aContentDispositionFilename = mContentDispositionFilename;
335 return NS_OK;
338 NS_IMETHODIMP
339 nsPartChannel::SetContentDispositionFilename(
340 const nsAString& aContentDispositionFilename) {
341 return NS_ERROR_NOT_AVAILABLE;
344 NS_IMETHODIMP
345 nsPartChannel::GetContentDispositionHeader(
346 nsACString& aContentDispositionHeader) {
347 if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
349 aContentDispositionHeader = mContentDispositionHeader;
350 return NS_OK;
353 NS_IMETHODIMP
354 nsPartChannel::GetPartID(uint32_t* aPartID) {
355 *aPartID = mPartID;
356 return NS_OK;
359 NS_IMETHODIMP
360 nsPartChannel::GetIsFirstPart(bool* aIsFirstPart) {
361 *aIsFirstPart = mIsFirstPart;
362 return NS_OK;
365 NS_IMETHODIMP
366 nsPartChannel::GetIsLastPart(bool* aIsLastPart) {
367 *aIsLastPart = mIsLastPart;
368 return NS_OK;
372 // nsIByteRangeRequest implementation...
375 NS_IMETHODIMP
376 nsPartChannel::GetIsByteRangeRequest(bool* aIsByteRangeRequest) {
377 *aIsByteRangeRequest = mIsByteRangeRequest;
379 return NS_OK;
382 NS_IMETHODIMP
383 nsPartChannel::GetStartRange(int64_t* aStartRange) {
384 *aStartRange = mByteRangeStart;
386 return NS_OK;
389 NS_IMETHODIMP
390 nsPartChannel::GetEndRange(int64_t* aEndRange) {
391 *aEndRange = mByteRangeEnd;
392 return NS_OK;
395 NS_IMETHODIMP
396 nsPartChannel::GetBaseChannel(nsIChannel** aReturn) {
397 NS_ENSURE_ARG_POINTER(aReturn);
399 *aReturn = do_AddRef(mMultipartChannel).take();
400 return NS_OK;
403 // nsISupports implementation
404 NS_IMPL_ISUPPORTS(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener,
405 nsIRequestObserver)
407 // nsIStreamConverter implementation
409 // No syncronous conversion at this time.
410 NS_IMETHODIMP
411 nsMultiMixedConv::Convert(nsIInputStream* aFromStream, const char* aFromType,
412 const char* aToType, nsISupports* aCtxt,
413 nsIInputStream** _retval) {
414 return NS_ERROR_NOT_IMPLEMENTED;
417 // Stream converter service calls this to initialize the actual stream converter
418 // (us).
419 NS_IMETHODIMP
420 nsMultiMixedConv::AsyncConvertData(const char* aFromType, const char* aToType,
421 nsIStreamListener* aListener,
422 nsISupports* aCtxt) {
423 NS_ASSERTION(aListener && aFromType && aToType,
424 "null pointer passed into multi mixed converter");
426 // hook up our final listener. this guy gets the various On*() calls we want
427 // to throw at him.
429 // WARNING: this listener must be able to handle multiple OnStartRequest,
430 // OnDataAvail() and OnStopRequest() call combinations. We call of series
431 // of these for each sub-part in the raw stream.
432 mFinalListener = aListener;
434 return NS_OK;
437 NS_IMETHODIMP
438 nsMultiMixedConv::GetConvertedType(const nsACString& aFromType,
439 nsIChannel* aChannel, nsACString& aToType) {
440 return NS_ERROR_NOT_IMPLEMENTED;
443 // nsIRequestObserver implementation
444 NS_IMETHODIMP
445 nsMultiMixedConv::OnStartRequest(nsIRequest* request) {
446 // we're assuming the content-type is available at this stage
447 NS_ASSERTION(mBoundary.IsEmpty(), "a second on start???");
449 nsresult rv;
451 mTotalSent = 0;
452 mChannel = do_QueryInterface(request, &rv);
453 if (NS_FAILED(rv)) return rv;
455 nsAutoCString contentType;
457 // ask the HTTP channel for the content-type and extract the boundary from it.
458 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
459 if (NS_SUCCEEDED(rv)) {
460 rv = httpChannel->GetResponseHeader("content-type"_ns, contentType);
461 if (NS_FAILED(rv)) {
462 return rv;
464 nsCString csp;
465 rv = httpChannel->GetResponseHeader("content-security-policy"_ns, csp);
466 if (NS_SUCCEEDED(rv)) {
467 mRootContentSecurityPolicy = csp;
469 } else {
470 // try asking the channel directly
471 rv = mChannel->GetContentType(contentType);
472 if (NS_FAILED(rv)) {
473 return NS_ERROR_FAILURE;
477 Tokenizer p(contentType);
478 p.SkipUntil(Token::Char(';'));
479 if (!p.CheckChar(';')) {
480 return NS_ERROR_CORRUPTED_CONTENT;
482 p.SkipWhites();
483 if (!p.CheckWord("boundary")) {
484 return NS_ERROR_CORRUPTED_CONTENT;
486 p.SkipWhites();
487 if (!p.CheckChar('=')) {
488 return NS_ERROR_CORRUPTED_CONTENT;
490 p.SkipWhites();
491 Unused << p.ReadUntil(Token::Char(';'), mBoundary);
492 mBoundary.Trim(
493 " \""); // ignoring potential quoted string formatting violations
494 if (mBoundary.IsEmpty()) {
495 return NS_ERROR_CORRUPTED_CONTENT;
498 mHeaderTokens[HEADER_CONTENT_TYPE] = mTokenizer.AddCustomToken(
499 "content-type", mTokenizer.CASE_INSENSITIVE, false);
500 mHeaderTokens[HEADER_CONTENT_LENGTH] = mTokenizer.AddCustomToken(
501 "content-length", mTokenizer.CASE_INSENSITIVE, false);
502 mHeaderTokens[HEADER_CONTENT_DISPOSITION] = mTokenizer.AddCustomToken(
503 "content-disposition", mTokenizer.CASE_INSENSITIVE, false);
504 mHeaderTokens[HEADER_SET_COOKIE] = mTokenizer.AddCustomToken(
505 "set-cookie", mTokenizer.CASE_INSENSITIVE, false);
506 mHeaderTokens[HEADER_CONTENT_RANGE] = mTokenizer.AddCustomToken(
507 "content-range", mTokenizer.CASE_INSENSITIVE, false);
508 mHeaderTokens[HEADER_RANGE] =
509 mTokenizer.AddCustomToken("range", mTokenizer.CASE_INSENSITIVE, false);
510 mHeaderTokens[HEADER_CONTENT_SECURITY_POLICY] = mTokenizer.AddCustomToken(
511 "content-security-policy", mTokenizer.CASE_INSENSITIVE, false);
513 mLFToken = mTokenizer.AddCustomToken("\n", mTokenizer.CASE_SENSITIVE, false);
514 mCRLFToken =
515 mTokenizer.AddCustomToken("\r\n", mTokenizer.CASE_SENSITIVE, false);
517 SwitchToControlParsing();
519 mBoundaryToken =
520 mTokenizer.AddCustomToken(mBoundary, mTokenizer.CASE_SENSITIVE);
521 mBoundaryTokenWithDashes =
522 mTokenizer.AddCustomToken("--"_ns + mBoundary, mTokenizer.CASE_SENSITIVE);
524 return NS_OK;
527 // nsIStreamListener implementation
528 NS_IMETHODIMP
529 nsMultiMixedConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
530 uint64_t sourceOffset, uint32_t count) {
531 // Failing these assertions may indicate that some of the target listeners of
532 // this converter is looping the thead queue, which is harmful to how we
533 // collect the raw (content) data.
534 MOZ_DIAGNOSTIC_ASSERT(!mInOnDataAvailable,
535 "nsMultiMixedConv::OnDataAvailable reentered!");
536 MOZ_DIAGNOSTIC_ASSERT(
537 !mRawData, "There are unsent data from the previous tokenizer feed!");
539 if (mInOnDataAvailable) {
540 // The multipart logic is incapable of being reentered.
541 return NS_ERROR_UNEXPECTED;
544 mozilla::AutoRestore<bool> restore(mInOnDataAvailable);
545 mInOnDataAvailable = true;
547 nsresult rv_feed = mTokenizer.FeedInput(inStr, count);
548 // We must do this every time. Regardless if something has failed during the
549 // parsing process. Otherwise the raw data reference would not be thrown
550 // away.
551 nsresult rv_send = SendData();
553 return NS_FAILED(rv_send) ? rv_send : rv_feed;
556 NS_IMETHODIMP
557 nsMultiMixedConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
558 nsresult rv;
560 if (mPartChannel) {
561 mPartChannel->SetIsLastPart();
563 MOZ_DIAGNOSTIC_ASSERT(
564 !mRawData, "There are unsent data from the previous tokenizer feed!");
566 rv = mTokenizer.FinishInput();
567 if (NS_SUCCEEDED(aStatus)) {
568 aStatus = rv;
570 rv = SendData();
571 if (NS_SUCCEEDED(aStatus)) {
572 aStatus = rv;
575 (void)SendStop(aStatus);
576 } else if (NS_FAILED(aStatus) && !mRequestListenerNotified) {
577 // underlying data production problem. we should not be in
578 // the middle of sending data. if we were, mPartChannel,
579 // above, would have been non-null.
581 (void)mFinalListener->OnStartRequest(request);
582 (void)mFinalListener->OnStopRequest(request, aStatus);
585 nsCOMPtr<nsIMultiPartChannelListener> multiListener =
586 do_QueryInterface(mFinalListener);
587 if (multiListener) {
588 multiListener->OnAfterLastPart(aStatus);
591 return NS_OK;
594 nsresult nsMultiMixedConv::ConsumeToken(Token const& token) {
595 nsresult rv;
597 switch (mParserState) {
598 case PREAMBLE:
599 if (token.Equals(mBoundaryTokenWithDashes)) {
600 // The server first used boundary '--boundary'. Hence, we no longer
601 // accept plain 'boundary' token as a delimiter.
602 mTokenizer.RemoveCustomToken(mBoundaryToken);
603 mParserState = BOUNDARY_CRLF;
604 break;
606 if (token.Equals(mBoundaryToken)) {
607 // And here the opposite from the just above block...
608 mTokenizer.RemoveCustomToken(mBoundaryTokenWithDashes);
609 mParserState = BOUNDARY_CRLF;
610 break;
613 // This is a preamble, just ignore it and wait for the boundary.
614 break;
616 case BOUNDARY_CRLF:
617 if (token.Equals(Token::NewLine())) {
618 mParserState = HEADER_NAME;
619 mResponseHeader = HEADER_UNKNOWN;
620 HeadersToDefault();
621 SetHeaderTokensEnabled(true);
622 break;
624 return NS_ERROR_CORRUPTED_CONTENT;
626 case HEADER_NAME:
627 SetHeaderTokensEnabled(false);
628 if (token.Equals(Token::NewLine())) {
629 mParserState = BODY_INIT;
630 SwitchToBodyParsing();
631 break;
633 for (uint32_t h = HEADER_CONTENT_TYPE; h < HEADER_UNKNOWN; ++h) {
634 if (token.Equals(mHeaderTokens[h])) {
635 mResponseHeader = static_cast<EHeader>(h);
636 break;
639 mParserState = HEADER_SEP;
640 break;
642 case HEADER_SEP:
643 if (token.Equals(Token::Char(':'))) {
644 mParserState = HEADER_VALUE;
645 mResponseHeaderValue.Truncate();
646 break;
648 if (mResponseHeader == HEADER_UNKNOWN) {
649 // If the header is not of any we understand, just pass everything till
650 // ':'
651 break;
653 if (token.Equals(Token::Whitespace())) {
654 // Accept only header-name traling whitespaces after known headers
655 break;
657 return NS_ERROR_CORRUPTED_CONTENT;
659 case HEADER_VALUE:
660 if (token.Equals(Token::Whitespace()) && mResponseHeaderValue.IsEmpty()) {
661 // Eat leading whitespaces
662 break;
664 if (token.Equals(Token::NewLine())) {
665 nsresult rv = ProcessHeader();
666 if (NS_FAILED(rv)) {
667 return rv;
669 mParserState = HEADER_NAME;
670 mResponseHeader = HEADER_UNKNOWN;
671 SetHeaderTokensEnabled(true);
672 } else {
673 mResponseHeaderValue.Append(token.Fragment());
675 break;
677 case BODY_INIT:
678 rv = SendStart();
679 if (NS_FAILED(rv)) {
680 return rv;
682 mParserState = BODY;
683 [[fallthrough]];
685 case BODY: {
686 if (!token.Equals(mLFToken) && !token.Equals(mCRLFToken)) {
687 if (token.Equals(mBoundaryTokenWithDashes) ||
688 token.Equals(mBoundaryToken)) {
689 // Allow CRLF to NOT be part of the boundary as well
690 SwitchToControlParsing();
691 mParserState = TRAIL_DASH1;
692 break;
694 AccumulateData(token);
695 break;
698 // After CRLF we must explicitly check for boundary. If found,
699 // that CRLF is part of the boundary and must not be send to the
700 // data listener.
701 Token token2;
702 if (!mTokenizer.Next(token2)) {
703 // Note: this will give us the CRLF token again when more data
704 // or OnStopRequest arrive. I.e. we will enter BODY case in
705 // the very same state as we are now and start this block over.
706 mTokenizer.NeedMoreInput();
707 break;
709 if (token2.Equals(mBoundaryTokenWithDashes) ||
710 token2.Equals(mBoundaryToken)) {
711 SwitchToControlParsing();
712 mParserState = TRAIL_DASH1;
713 break;
716 AccumulateData(token);
717 AccumulateData(token2);
718 break;
721 case TRAIL_DASH1:
722 if (token.Equals(Token::NewLine())) {
723 rv = SendStop(NS_OK);
724 if (NS_FAILED(rv)) {
725 return rv;
727 mParserState = BOUNDARY_CRLF;
728 mTokenizer.Rollback();
729 break;
731 if (token.Equals(Token::Char('-'))) {
732 mParserState = TRAIL_DASH2;
733 break;
735 return NS_ERROR_CORRUPTED_CONTENT;
737 case TRAIL_DASH2:
738 if (token.Equals(Token::Char('-'))) {
739 mPartChannel->SetIsLastPart();
740 // SendStop calls SendData first.
741 rv = SendStop(NS_OK);
742 if (NS_FAILED(rv)) {
743 return rv;
745 mParserState = EPILOGUE;
746 break;
748 return NS_ERROR_CORRUPTED_CONTENT;
750 case EPILOGUE:
751 // Just ignore
752 break;
754 default:
755 MOZ_ASSERT(false, "Missing parser state handling branch");
756 break;
757 } // switch
759 return NS_OK;
762 void nsMultiMixedConv::SetHeaderTokensEnabled(bool aEnable) {
763 for (uint32_t h = HEADER_FIRST; h < HEADER_UNKNOWN; ++h) {
764 mTokenizer.EnableCustomToken(mHeaderTokens[h], aEnable);
768 void nsMultiMixedConv::SwitchToBodyParsing() {
769 mTokenizer.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
770 mTokenizer.EnableCustomToken(mLFToken, true);
771 mTokenizer.EnableCustomToken(mCRLFToken, true);
772 mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, true);
773 mTokenizer.EnableCustomToken(mBoundaryToken, true);
776 void nsMultiMixedConv::SwitchToControlParsing() {
777 mTokenizer.SetTokenizingMode(Tokenizer::Mode::FULL);
778 mTokenizer.EnableCustomToken(mLFToken, false);
779 mTokenizer.EnableCustomToken(mCRLFToken, false);
780 mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, false);
781 mTokenizer.EnableCustomToken(mBoundaryToken, false);
784 // nsMultiMixedConv methods
785 nsMultiMixedConv::nsMultiMixedConv()
786 // XXX: This is a hack to bypass the raw pointer to refcounted object in
787 // lambda analysis. It should be removed and replaced when the
788 // IncrementalTokenizer API is improved to avoid the need for such
789 // workarounds.
791 // This is safe because `mTokenizer` will not outlive `this`, meaning
792 // that this std::bind object will be destroyed before `this` dies.
793 : mTokenizer(std::bind(&nsMultiMixedConv::ConsumeToken, this,
794 std::placeholders::_1)) {}
796 nsresult nsMultiMixedConv::SendStart() {
797 nsresult rv = NS_OK;
799 nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
800 if (mContentType.IsEmpty()) {
801 mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
802 nsCOMPtr<nsIStreamConverterService> serv =
803 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
804 if (NS_SUCCEEDED(rv)) {
805 nsCOMPtr<nsIStreamListener> converter;
806 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mFinalListener,
807 mContext, getter_AddRefs(converter));
808 if (NS_SUCCEEDED(rv)) {
809 partListener = converter;
814 // if we already have an mPartChannel, that means we never sent a Stop()
815 // before starting up another "part." that would be bad.
816 MOZ_ASSERT(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");
818 nsPartChannel* newChannel;
819 newChannel = new nsPartChannel(mChannel, mCurrentPartID, mCurrentPartID == 0,
820 partListener);
822 ++mCurrentPartID;
824 if (mIsByteRangeRequest) {
825 newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
828 mTotalSent = 0;
830 // Set up the new part channel...
831 mPartChannel = newChannel;
833 rv = mPartChannel->SetContentType(mContentType);
834 if (NS_FAILED(rv)) return rv;
836 rv = mPartChannel->SetContentLength(mContentLength);
837 if (NS_FAILED(rv)) return rv;
839 mPartChannel->SetContentDisposition(mContentDisposition);
841 // Each part of a multipart/replace response can be used
842 // for the top level document. We must inform upper layers
843 // about this by setting the LOAD_REPLACE flag so that certain
844 // state assertions are evaluated as positive.
845 nsLoadFlags loadFlags = 0;
846 mPartChannel->GetLoadFlags(&loadFlags);
847 loadFlags |= nsIChannel::LOAD_REPLACE;
848 mPartChannel->SetLoadFlags(loadFlags);
850 nsCOMPtr<nsILoadGroup> loadGroup;
851 (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
853 // Add the new channel to the load group (if any)
854 if (loadGroup) {
855 rv = loadGroup->AddRequest(mPartChannel, nullptr);
856 if (NS_FAILED(rv)) return rv;
859 // This prevents artificial call to OnStart/StopRequest when the root
860 // channel fails. Since now it's ensured to keep with the nsIStreamListener
861 // contract every time.
862 mRequestListenerNotified = true;
864 // Let's start off the load. NOTE: we don't forward on the channel passed
865 // into our OnDataAvailable() as it's the root channel for the raw stream.
866 return mPartChannel->SendOnStartRequest(mContext);
869 nsresult nsMultiMixedConv::SendStop(nsresult aStatus) {
870 // Make sure we send out all accumulcated data prior call to OnStopRequest.
871 // If there is no data, this is a no-op.
872 nsresult rv = SendData();
873 if (NS_SUCCEEDED(aStatus)) {
874 aStatus = rv;
876 if (mPartChannel) {
877 rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
878 // don't check for failure here, we need to remove the channel from
879 // the loadgroup.
881 // Remove the channel from its load group (if any)
882 nsCOMPtr<nsILoadGroup> loadGroup;
883 (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
884 if (loadGroup) {
885 (void)loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
889 mPartChannel = nullptr;
890 return rv;
893 void nsMultiMixedConv::AccumulateData(Token const& aToken) {
894 if (!mRawData) {
895 // This is the first read of raw data during this FeedInput loop
896 // of the incremental tokenizer. All 'raw' tokens are coming from
897 // the same linear buffer, hence begining of this loop raw data
898 // is begining of the first raw token. Length of this loop raw
899 // data is just sum of all 'raw' tokens we collect during this loop.
901 // It's ensured we flush (send to to the listener via OnDataAvailable)
902 // and nullify the collected raw data right after FeedInput call.
903 // Hence, the reference can't outlive the actual buffer.
904 mRawData = aToken.Fragment().BeginReading();
905 mRawDataLength = 0;
908 mRawDataLength += aToken.Fragment().Length();
911 nsresult nsMultiMixedConv::SendData() {
912 nsresult rv;
914 if (!mRawData) {
915 return NS_OK;
918 nsACString::const_char_iterator rawData = mRawData;
919 mRawData = nullptr;
921 if (!mPartChannel) {
922 return NS_ERROR_FAILURE; // something went wrong w/ processing
925 if (mContentLength != UINT64_MAX) {
926 // make sure that we don't send more than the mContentLength
927 // XXX why? perhaps the Content-Length header was actually wrong!!
928 if ((uint64_t(mRawDataLength) + mTotalSent) > mContentLength) {
929 mRawDataLength = static_cast<uint32_t>(mContentLength - mTotalSent);
932 if (mRawDataLength == 0) return NS_OK;
935 uint64_t offset = mTotalSent;
936 mTotalSent += mRawDataLength;
938 nsCOMPtr<nsIStringInputStream> ss(
939 do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
940 if (NS_FAILED(rv)) return rv;
942 rv = ss->ShareData(rawData, mRawDataLength);
943 mRawData = nullptr;
944 if (NS_FAILED(rv)) return rv;
946 return mPartChannel->SendOnDataAvailable(mContext, ss, offset,
947 mRawDataLength);
950 void nsMultiMixedConv::HeadersToDefault() {
951 mContentLength = UINT64_MAX;
952 mContentType.Truncate();
953 mContentDisposition.Truncate();
954 mContentSecurityPolicy.Truncate();
955 mIsByteRangeRequest = false;
958 nsresult nsMultiMixedConv::ProcessHeader() {
959 mozilla::Tokenizer p(mResponseHeaderValue);
961 switch (mResponseHeader) {
962 case HEADER_CONTENT_TYPE:
963 mContentType = mResponseHeaderValue;
964 mContentType.CompressWhitespace();
965 break;
966 case HEADER_CONTENT_LENGTH:
967 p.SkipWhites();
968 if (!p.ReadInteger(&mContentLength)) {
969 return NS_ERROR_CORRUPTED_CONTENT;
971 break;
972 case HEADER_CONTENT_DISPOSITION:
973 mContentDisposition = mResponseHeaderValue;
974 mContentDisposition.CompressWhitespace();
975 break;
976 case HEADER_SET_COOKIE: {
977 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
978 do_QueryInterface(mChannel);
979 mResponseHeaderValue.CompressWhitespace();
980 if (httpInternal) {
981 DebugOnly<nsresult> rv = httpInternal->SetCookie(mResponseHeaderValue);
982 MOZ_ASSERT(NS_SUCCEEDED(rv));
984 break;
986 case HEADER_RANGE:
987 case HEADER_CONTENT_RANGE: {
988 if (!p.CheckWord("bytes") || !p.CheckWhite()) {
989 return NS_ERROR_CORRUPTED_CONTENT;
991 p.SkipWhites();
992 if (p.CheckChar('*')) {
993 mByteRangeStart = mByteRangeEnd = 0;
994 } else if (!p.ReadInteger(&mByteRangeStart) || !p.CheckChar('-') ||
995 !p.ReadInteger(&mByteRangeEnd)) {
996 return NS_ERROR_CORRUPTED_CONTENT;
998 mIsByteRangeRequest = true;
999 if (mContentLength == UINT64_MAX) {
1000 mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
1002 break;
1004 case HEADER_CONTENT_SECURITY_POLICY: {
1005 mContentSecurityPolicy = mResponseHeaderValue;
1006 mContentSecurityPolicy.CompressWhitespace();
1007 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1008 if (httpChannel) {
1009 nsCString resultCSP = mRootContentSecurityPolicy;
1010 if (!mContentSecurityPolicy.IsEmpty()) {
1011 // We are updating the root channel CSP header respectively for
1012 // each part as: CSP-root + CSP-partN, where N is the part number.
1013 // Here we append current part's CSP to root CSP and reset CSP
1014 // header for each part.
1015 if (!resultCSP.IsEmpty()) {
1016 resultCSP.Append(";");
1018 resultCSP.Append(mContentSecurityPolicy);
1020 nsresult rv = httpChannel->SetResponseHeader(
1021 "Content-Security-Policy"_ns, resultCSP, false);
1022 if (NS_FAILED(rv)) {
1023 return NS_ERROR_CORRUPTED_CONTENT;
1026 break;
1028 case HEADER_UNKNOWN:
1029 // We ignore anything else...
1030 break;
1033 return NS_OK;
1036 nsresult NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv) {
1037 MOZ_ASSERT(aMultiMixedConv != nullptr, "null ptr");
1039 RefPtr<nsMultiMixedConv> conv = new nsMultiMixedConv();
1040 conv.forget(aMultiMixedConv);
1041 return NS_OK;