1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/css/StreamLoader.h"
9 #include "mozilla/Encoding.h"
10 #include "mozilla/ScopeExit.h"
11 #include "nsIChannel.h"
12 #include "nsIInputStream.h"
13 #include "nsISupportsPriority.h"
17 using namespace mozilla
;
22 StreamLoader::StreamLoader(SheetLoadData
& aSheetLoadData
)
23 : mSheetLoadData(&aSheetLoadData
), mStatus(NS_OK
) {}
25 StreamLoader::~StreamLoader() {
27 MOZ_RELEASE_ASSERT(mOnStopRequestCalled
|| mChannelOpenFailed
);
31 NS_IMPL_ISUPPORTS(StreamLoader
, nsIStreamListener
)
34 void StreamLoader::PrioritizeAsPreload(nsIChannel
* aChannel
) {
35 if (nsCOMPtr
<nsISupportsPriority
> sp
= do_QueryInterface(aChannel
)) {
36 sp
->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST
);
40 void StreamLoader::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
42 /* nsIRequestObserver implementation */
44 StreamLoader::OnStartRequest(nsIRequest
* aRequest
) {
45 NotifyStart(aRequest
);
47 // It's kinda bad to let Web content send a number that results
48 // in a potentially large allocation directly, but efficiency of
49 // compression bombs is so great that it doesn't make much sense
50 // to require a site to send one before going ahead and allocating.
51 if (nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
)) {
53 nsresult rv
= channel
->GetContentLength(&length
);
54 if (NS_SUCCEEDED(rv
) && length
> 0) {
55 if (length
> std::numeric_limits
<nsACString::size_type
>::max()) {
56 return (mStatus
= NS_ERROR_OUT_OF_MEMORY
);
58 if (!mBytes
.SetCapacity(length
, fallible
)) {
59 return (mStatus
= NS_ERROR_OUT_OF_MEMORY
);
67 StreamLoader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatus
) {
69 MOZ_RELEASE_ASSERT(!mOnStopRequestCalled
);
70 mOnStopRequestCalled
= true;
73 nsresult rv
= mStatus
;
74 auto notifyStop
= MakeScopeExit([&] { NotifyStop(aRequest
, rv
); });
79 // Hold the nsStringBuffer for the bytes from the stack to ensure release
80 // no matter which return branch is taken.
81 nsCString
bytes(mBytes
);
84 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
86 if (NS_FAILED(mStatus
)) {
87 mSheetLoadData
->VerifySheetReadyToParse(mStatus
, EmptyCString(),
88 EmptyCString(), channel
);
92 rv
= mSheetLoadData
->VerifySheetReadyToParse(aStatus
, mBOMBytes
, bytes
,
94 if (rv
!= NS_OK_PARSE_SHEET
) {
95 // VerifySheetReadyToParse returns `NS_OK` when there was something wrong
96 // with the script. We need to override the result so that any <link
97 // preload> tags associted to this load will be notified the "error"
98 // event. It's fine because this error goes no where.
99 rv
= NS_ERROR_NOT_AVAILABLE
;
103 // BOM detection generally happens during the write callback, but that won't
104 // have happened if fewer than three bytes were received.
105 if (mEncodingFromBOM
.isNothing()) {
107 MOZ_ASSERT(mEncodingFromBOM
.isSome());
110 // The BOM handling has happened, but we still may not have an encoding if
111 // there was no BOM. Ensure we have one.
112 const Encoding
* encoding
= mEncodingFromBOM
.value();
115 encoding
= mSheetLoadData
->DetermineNonBOMEncoding(bytes
, channel
);
117 mSheetLoadData
->mEncoding
= encoding
;
119 size_t validated
= 0;
120 if (encoding
== UTF_8_ENCODING
) {
121 validated
= Encoding::UTF8ValidUpTo(bytes
);
124 if (validated
== bytes
.Length()) {
125 // Either this is UTF-8 and all valid, or it's not UTF-8 but is an
126 // empty string. This assumes that an empty string in any encoding
127 // decodes to empty string, which seems like a plausible assumption.
128 utf8String
.Assign(bytes
);
130 rv
= encoding
->DecodeWithoutBOMHandling(bytes
, utf8String
, validated
);
131 NS_ENSURE_SUCCESS(rv
, rv
);
133 } // run destructor for `bytes`
135 // For reasons I don't understand, factoring the below lines into
136 // a method on SheetLoadData resulted in a linker error. Hence,
137 // accessing fields of mSheetLoadData from here.
138 mSheetLoadData
->mLoader
->ParseSheet(utf8String
, *mSheetLoadData
,
139 Loader::AllowAsyncParse::Yes
);
144 /* nsIStreamListener implementation */
146 StreamLoader::OnDataAvailable(nsIRequest
*, nsIInputStream
* aInputStream
,
147 uint64_t, uint32_t aCount
) {
148 if (NS_FAILED(mStatus
)) {
152 return aInputStream
->ReadSegments(WriteSegmentFun
, this, aCount
, &dummy
);
155 void StreamLoader::HandleBOM() {
156 MOZ_ASSERT(mEncodingFromBOM
.isNothing());
157 MOZ_ASSERT(mBytes
.IsEmpty());
159 const Encoding
* encoding
;
161 Tie(encoding
, bomLength
) = Encoding::ForBOM(mBOMBytes
);
162 mEncodingFromBOM
.emplace(encoding
); // Null means no BOM.
164 // BOMs are three bytes at most, but may be fewer. Copy over anything
165 // that wasn't part of the BOM to mBytes. Note that we need to track
166 // any BOM bytes as well for SRI handling.
167 mBytes
.Append(Substring(mBOMBytes
, bomLength
));
168 mBOMBytes
.Truncate(bomLength
);
171 nsresult
StreamLoader::WriteSegmentFun(nsIInputStream
*, void* aClosure
,
172 const char* aSegment
, uint32_t,
173 uint32_t aCount
, uint32_t* aWriteCount
) {
175 StreamLoader
* self
= static_cast<StreamLoader
*>(aClosure
);
176 if (NS_FAILED(self
->mStatus
)) {
177 return self
->mStatus
;
180 // If we haven't done BOM detection yet, divert bytes into the special buffer.
181 if (self
->mEncodingFromBOM
.isNothing()) {
182 size_t bytesToCopy
= std::min(3 - self
->mBOMBytes
.Length(), aCount
);
183 self
->mBOMBytes
.Append(aSegment
, bytesToCopy
);
184 aSegment
+= bytesToCopy
;
185 *aWriteCount
+= bytesToCopy
;
186 aCount
-= bytesToCopy
;
188 if (self
->mBOMBytes
.Length() == 3) {
195 if (!self
->mBytes
.Append(aSegment
, aCount
, fallible
)) {
196 self
->mBytes
.Truncate();
197 return (self
->mStatus
= NS_ERROR_OUT_OF_MEMORY
);
200 *aWriteCount
+= aCount
;
205 } // namespace mozilla