|
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/. */ |
|
5 |
|
6 #include "mozilla/DebugOnly.h" |
|
7 |
|
8 #include "nsUnicharStreamLoader.h" |
|
9 #include "nsIInputStream.h" |
|
10 #include "nsICharsetConverterManager.h" |
|
11 #include "nsServiceManagerUtils.h" |
|
12 #include <algorithm> |
|
13 |
|
14 // 1024 bytes is specified in |
|
15 // http://www.whatwg.org/specs/web-apps/current-work/#charset for HTML; for |
|
16 // other resource types (e.g. CSS) typically fewer bytes are fine too, since |
|
17 // they only look at things right at the beginning of the data. |
|
18 #define SNIFFING_BUFFER_SIZE 1024 |
|
19 |
|
20 using namespace mozilla; |
|
21 |
|
22 NS_IMETHODIMP |
|
23 nsUnicharStreamLoader::Init(nsIUnicharStreamLoaderObserver *aObserver) |
|
24 { |
|
25 NS_ENSURE_ARG_POINTER(aObserver); |
|
26 |
|
27 mObserver = aObserver; |
|
28 |
|
29 if (!mRawData.SetCapacity(SNIFFING_BUFFER_SIZE, fallible_t())) |
|
30 return NS_ERROR_OUT_OF_MEMORY; |
|
31 |
|
32 return NS_OK; |
|
33 } |
|
34 |
|
35 nsresult |
|
36 nsUnicharStreamLoader::Create(nsISupports *aOuter, |
|
37 REFNSIID aIID, |
|
38 void **aResult) |
|
39 { |
|
40 if (aOuter) return NS_ERROR_NO_AGGREGATION; |
|
41 |
|
42 nsUnicharStreamLoader* it = new nsUnicharStreamLoader(); |
|
43 NS_ADDREF(it); |
|
44 nsresult rv = it->QueryInterface(aIID, aResult); |
|
45 NS_RELEASE(it); |
|
46 return rv; |
|
47 } |
|
48 |
|
49 NS_IMPL_ISUPPORTS(nsUnicharStreamLoader, nsIUnicharStreamLoader, |
|
50 nsIRequestObserver, nsIStreamListener) |
|
51 |
|
52 /* readonly attribute nsIChannel channel; */ |
|
53 NS_IMETHODIMP |
|
54 nsUnicharStreamLoader::GetChannel(nsIChannel **aChannel) |
|
55 { |
|
56 NS_IF_ADDREF(*aChannel = mChannel); |
|
57 return NS_OK; |
|
58 } |
|
59 |
|
60 /* readonly attribute nsACString charset */ |
|
61 NS_IMETHODIMP |
|
62 nsUnicharStreamLoader::GetCharset(nsACString& aCharset) |
|
63 { |
|
64 aCharset = mCharset; |
|
65 return NS_OK; |
|
66 } |
|
67 |
|
68 /* nsIRequestObserver implementation */ |
|
69 NS_IMETHODIMP |
|
70 nsUnicharStreamLoader::OnStartRequest(nsIRequest*, nsISupports*) |
|
71 { |
|
72 return NS_OK; |
|
73 } |
|
74 |
|
75 NS_IMETHODIMP |
|
76 nsUnicharStreamLoader::OnStopRequest(nsIRequest *aRequest, |
|
77 nsISupports *aContext, |
|
78 nsresult aStatus) |
|
79 { |
|
80 if (!mObserver) { |
|
81 NS_ERROR("nsUnicharStreamLoader::OnStopRequest called before ::Init"); |
|
82 return NS_ERROR_UNEXPECTED; |
|
83 } |
|
84 |
|
85 mContext = aContext; |
|
86 mChannel = do_QueryInterface(aRequest); |
|
87 |
|
88 nsresult rv = NS_OK; |
|
89 if (mRawData.Length() > 0 && NS_SUCCEEDED(aStatus)) { |
|
90 NS_ABORT_IF_FALSE(mBuffer.Length() == 0, |
|
91 "should not have both decoded and raw data"); |
|
92 rv = DetermineCharset(); |
|
93 } |
|
94 |
|
95 if (NS_FAILED(rv)) { |
|
96 // Call the observer but pass it no data. |
|
97 mObserver->OnStreamComplete(this, mContext, rv, EmptyString()); |
|
98 } else { |
|
99 mObserver->OnStreamComplete(this, mContext, aStatus, mBuffer); |
|
100 } |
|
101 |
|
102 mObserver = nullptr; |
|
103 mDecoder = nullptr; |
|
104 mContext = nullptr; |
|
105 mChannel = nullptr; |
|
106 mCharset.Truncate(); |
|
107 mBuffer.Truncate(); |
|
108 return rv; |
|
109 } |
|
110 |
|
111 /* nsIStreamListener implementation */ |
|
112 NS_IMETHODIMP |
|
113 nsUnicharStreamLoader::OnDataAvailable(nsIRequest *aRequest, |
|
114 nsISupports *aContext, |
|
115 nsIInputStream *aInputStream, |
|
116 uint64_t aSourceOffset, |
|
117 uint32_t aCount) |
|
118 { |
|
119 if (!mObserver) { |
|
120 NS_ERROR("nsUnicharStreamLoader::OnDataAvailable called before ::Init"); |
|
121 return NS_ERROR_UNEXPECTED; |
|
122 } |
|
123 |
|
124 mContext = aContext; |
|
125 mChannel = do_QueryInterface(aRequest); |
|
126 |
|
127 nsresult rv = NS_OK; |
|
128 if (mDecoder) { |
|
129 // process everything we've got |
|
130 uint32_t dummy; |
|
131 aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy); |
|
132 } else { |
|
133 // no decoder yet. Read up to SNIFFING_BUFFER_SIZE octets into |
|
134 // mRawData (this is the cutoff specified in |
|
135 // draft-abarth-mime-sniff-06). If we can get that much, then go |
|
136 // ahead and fire charset detection and read the rest. Otherwise |
|
137 // wait for more data. |
|
138 |
|
139 uint32_t haveRead = mRawData.Length(); |
|
140 uint32_t toRead = std::min(SNIFFING_BUFFER_SIZE - haveRead, aCount); |
|
141 uint32_t n; |
|
142 char *here = mRawData.BeginWriting() + haveRead; |
|
143 |
|
144 rv = aInputStream->Read(here, toRead, &n); |
|
145 if (NS_SUCCEEDED(rv)) { |
|
146 mRawData.SetLength(haveRead + n); |
|
147 if (mRawData.Length() == SNIFFING_BUFFER_SIZE) { |
|
148 rv = DetermineCharset(); |
|
149 if (NS_SUCCEEDED(rv)) { |
|
150 // process what's left |
|
151 uint32_t dummy; |
|
152 aInputStream->ReadSegments(WriteSegmentFun, this, aCount - n, &dummy); |
|
153 } |
|
154 } else { |
|
155 NS_ABORT_IF_FALSE(n == aCount, "didn't read as much as was available"); |
|
156 } |
|
157 } |
|
158 } |
|
159 |
|
160 mContext = nullptr; |
|
161 mChannel = nullptr; |
|
162 return rv; |
|
163 } |
|
164 |
|
165 /* internal */ |
|
166 static NS_DEFINE_CID(kCharsetConverterManagerCID, |
|
167 NS_ICHARSETCONVERTERMANAGER_CID); |
|
168 |
|
169 nsresult |
|
170 nsUnicharStreamLoader::DetermineCharset() |
|
171 { |
|
172 nsresult rv = mObserver->OnDetermineCharset(this, mContext, |
|
173 mRawData, mCharset); |
|
174 if (NS_FAILED(rv) || mCharset.IsEmpty()) { |
|
175 // The observer told us nothing useful |
|
176 mCharset.AssignLiteral("UTF-8"); |
|
177 } |
|
178 |
|
179 // Create the decoder for this character set |
|
180 nsCOMPtr<nsICharsetConverterManager> ccm = |
|
181 do_GetService(kCharsetConverterManagerCID, &rv); |
|
182 if (NS_FAILED(rv)) return rv; |
|
183 |
|
184 // Sadly, nsIUnicharStreamLoader is exposed to extensions, so we can't |
|
185 // assume mozilla::css::Loader to be the only caller. Since legacy |
|
186 // charset alias code doesn't know about the replacement encoding, |
|
187 // special-case it here, but let other stuff go through legacy alias |
|
188 // resolution for now. |
|
189 if (mCharset.EqualsLiteral("replacement")) { |
|
190 rv = ccm->GetUnicodeDecoderRaw(mCharset.get(), getter_AddRefs(mDecoder)); |
|
191 } else { |
|
192 rv = ccm->GetUnicodeDecoder(mCharset.get(), getter_AddRefs(mDecoder)); |
|
193 } |
|
194 if (NS_FAILED(rv)) return rv; |
|
195 |
|
196 // Process the data into mBuffer |
|
197 uint32_t dummy; |
|
198 rv = WriteSegmentFun(nullptr, this, |
|
199 mRawData.BeginReading(), |
|
200 0, mRawData.Length(), |
|
201 &dummy); |
|
202 mRawData.Truncate(); |
|
203 return rv; |
|
204 } |
|
205 |
|
206 NS_METHOD |
|
207 nsUnicharStreamLoader::WriteSegmentFun(nsIInputStream *, |
|
208 void *aClosure, |
|
209 const char *aSegment, |
|
210 uint32_t, |
|
211 uint32_t aCount, |
|
212 uint32_t *aWriteCount) |
|
213 { |
|
214 nsUnicharStreamLoader* self = static_cast<nsUnicharStreamLoader*>(aClosure); |
|
215 |
|
216 uint32_t haveRead = self->mBuffer.Length(); |
|
217 int32_t srcLen = aCount; |
|
218 int32_t dstLen; |
|
219 self->mDecoder->GetMaxLength(aSegment, srcLen, &dstLen); |
|
220 |
|
221 uint32_t capacity = haveRead + dstLen; |
|
222 if (!self->mBuffer.SetCapacity(capacity, fallible_t())) { |
|
223 return NS_ERROR_OUT_OF_MEMORY; |
|
224 } |
|
225 |
|
226 DebugOnly<nsresult> rv = |
|
227 self->mDecoder->Convert(aSegment, |
|
228 &srcLen, |
|
229 self->mBuffer.BeginWriting() + haveRead, |
|
230 &dstLen); |
|
231 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
232 MOZ_ASSERT(srcLen == static_cast<int32_t>(aCount)); |
|
233 haveRead += dstLen; |
|
234 |
|
235 self->mBuffer.SetLength(haveRead); |
|
236 *aWriteCount = aCount; |
|
237 return NS_OK; |
|
238 } |