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/. */
6
7 #include "mozilla/css/StreamLoader.h"
8
9 #include "mozilla/IntegerTypeTraits.h"
10 #include "mozilla/Encoding.h"
11 #include "nsIChannel.h"
12 #include "nsIInputStream.h"
13
14 using namespace mozilla;
15
16 namespace mozilla {
17 namespace css {
18
StreamLoader(mozilla::css::SheetLoadData * aSheetLoadData)19 StreamLoader::StreamLoader(mozilla::css::SheetLoadData* aSheetLoadData)
20 : mSheetLoadData(aSheetLoadData), mStatus(NS_OK) {
21 MOZ_ASSERT(!aSheetLoadData->mSheet->IsGecko());
22 }
23
~StreamLoader()24 StreamLoader::~StreamLoader() {}
25
NS_IMPL_ISUPPORTS(StreamLoader,nsIStreamListener)26 NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener)
27
28 /* nsIRequestObserver implementation */
29 NS_IMETHODIMP
30 StreamLoader::OnStartRequest(nsIRequest* aRequest, nsISupports*) {
31 // It's kinda bad to let Web content send a number that results
32 // in a potentially large allocation directly, but efficiency of
33 // compression bombs is so great that it doesn't make much sense
34 // to require a site to send one before going ahead and allocating.
35 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
36 if (channel) {
37 int64_t length;
38 nsresult rv = channel->GetContentLength(&length);
39 if (NS_SUCCEEDED(rv) && length > 0) {
40 if (length > MaxValue<nsACString::size_type>::value) {
41 return (mStatus = NS_ERROR_OUT_OF_MEMORY);
42 }
43 if (!mBytes.SetCapacity(length, mozilla::fallible_t())) {
44 return (mStatus = NS_ERROR_OUT_OF_MEMORY);
45 }
46 }
47 }
48 return NS_OK;
49 }
50
51 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatus)52 StreamLoader::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
53 nsresult aStatus) {
54 // Decoded data
55 nsCString utf8String;
56 // How many bytes of decoded data to skip (3 when skipping UTF-8 BOM needed,
57 // 0 otherwise)
58 size_t skip = 0;
59
60 const Encoding* encoding;
61
62 nsresult rv = NS_OK;
63
64 {
65 // Hold the nsStringBuffer for the bytes from the stack to ensure release
66 // no matter which return branch is taken.
67 nsCString bytes(mBytes);
68 mBytes.Truncate();
69
70 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
71
72 if (NS_FAILED(mStatus)) {
73 mSheetLoadData->VerifySheetReadyToParse(mStatus, EmptyCString(), channel);
74 return mStatus;
75 }
76
77 nsresult rv =
78 mSheetLoadData->VerifySheetReadyToParse(aStatus, bytes, channel);
79 if (rv != NS_OK_PARSE_SHEET) {
80 return rv;
81 }
82
83 rv = NS_OK;
84
85 size_t bomLength;
86 Tie(encoding, bomLength) = Encoding::ForBOM(bytes);
87 if (!encoding) {
88 // No BOM
89 encoding = mSheetLoadData->DetermineNonBOMEncoding(bytes, channel);
90
91 rv = encoding->DecodeWithoutBOMHandling(bytes, utf8String);
92 } else if (encoding == UTF_8_ENCODING) {
93 // UTF-8 BOM; handling this manually because mozilla::Encoding
94 // can't handle this without copying with C++ types and uses
95 // infallible allocation with Rust types (which could avoid
96 // the copy).
97
98 // First, chop off the BOM.
99 auto tail = Span<const uint8_t>(bytes).From(bomLength);
100 size_t upTo = Encoding::UTF8ValidUpTo(tail);
101 if (upTo == tail.Length()) {
102 // No need to copy
103 skip = bomLength;
104 utf8String.Assign(bytes);
105 } else {
106 rv = encoding->DecodeWithoutBOMHandling(tail, utf8String, upTo);
107 }
108 } else {
109 // UTF-16LE or UTF-16BE
110 rv = encoding->DecodeWithBOMRemoval(bytes, utf8String);
111 }
112 } // run destructor for `bytes`
113
114 if (NS_FAILED(rv)) {
115 return rv;
116 }
117
118 // For reasons I don't understand, factoring the below lines into
119 // a method on SheetLoadData resulted in a linker error. Hence,
120 // accessing fields of mSheetLoadData from here.
121 mSheetLoadData->mEncoding = encoding;
122 bool dummy;
123 return mSheetLoadData->mLoader->ParseSheet(
124 EmptyString(), Span<const uint8_t>(utf8String).From(skip), mSheetLoadData,
125 /* aAllowAsync = */ true, dummy);
126 }
127
128 /* nsIStreamListener implementation */
129 NS_IMETHODIMP
OnDataAvailable(nsIRequest *,nsISupports *,nsIInputStream * aInputStream,uint64_t,uint32_t aCount)130 StreamLoader::OnDataAvailable(nsIRequest*, nsISupports*,
131 nsIInputStream* aInputStream, uint64_t,
132 uint32_t aCount) {
133 if (NS_FAILED(mStatus)) {
134 return mStatus;
135 }
136 uint32_t dummy;
137 return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
138 }
139
WriteSegmentFun(nsIInputStream *,void * aClosure,const char * aSegment,uint32_t,uint32_t aCount,uint32_t * aWriteCount)140 nsresult StreamLoader::WriteSegmentFun(nsIInputStream*, void* aClosure,
141 const char* aSegment, uint32_t,
142 uint32_t aCount, uint32_t* aWriteCount) {
143 StreamLoader* self = static_cast<StreamLoader*>(aClosure);
144 if (NS_FAILED(self->mStatus)) {
145 return self->mStatus;
146 }
147 if (!self->mBytes.Append(aSegment, aCount, mozilla::fallible_t())) {
148 self->mBytes.Truncate();
149 return (self->mStatus = NS_ERROR_OUT_OF_MEMORY);
150 }
151 *aWriteCount = aCount;
152 return NS_OK;
153 }
154
155 } // namespace css
156 } // namespace mozilla
157