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