Line data Source code
1 : //* -*- Mode: C++; tab-width: 8; 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 : /**
7 : * Implementation of moz-anno: URLs for accessing favicons. The urls are sent
8 : * to the favicon service. If the favicon service doesn't have the
9 : * data, a stream containing the default favicon will be returned.
10 : *
11 : * The reference to annotations ("moz-anno") is a leftover from previous
12 : * iterations of this component. As of now the moz-anno protocol is independent
13 : * of annotations.
14 : */
15 :
16 : #include "nsAnnoProtocolHandler.h"
17 : #include "nsFaviconService.h"
18 : #include "nsIChannel.h"
19 : #include "nsIInputStreamChannel.h"
20 : #include "nsILoadGroup.h"
21 : #include "nsIStandardURL.h"
22 : #include "nsIStringStream.h"
23 : #include "nsIInputStream.h"
24 : #include "nsISupportsUtils.h"
25 : #include "nsIURI.h"
26 : #include "nsNetUtil.h"
27 : #include "nsIOutputStream.h"
28 : #include "nsInputStreamPump.h"
29 : #include "nsContentUtils.h"
30 : #include "nsServiceManagerUtils.h"
31 : #include "nsStringStream.h"
32 : #include "SimpleChannel.h"
33 : #include "mozilla/ScopeExit.h"
34 : #include "mozilla/storage.h"
35 : #include "Helpers.h"
36 : #include "FaviconHelpers.h"
37 :
38 : using namespace mozilla;
39 : using namespace mozilla::places;
40 :
41 : ////////////////////////////////////////////////////////////////////////////////
42 : //// Global Functions
43 :
44 : /**
45 : * Creates a channel to obtain the default favicon.
46 : */
47 : static
48 : nsresult
49 0 : GetDefaultIcon(nsIChannel *aOriginalChannel, nsIChannel **aChannel)
50 : {
51 0 : nsCOMPtr<nsIURI> defaultIconURI;
52 0 : nsresult rv = NS_NewURI(getter_AddRefs(defaultIconURI),
53 0 : NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
54 0 : NS_ENSURE_SUCCESS(rv, rv);
55 0 : nsCOMPtr<nsILoadInfo> loadInfo = aOriginalChannel->GetLoadInfo();
56 0 : rv = NS_NewChannelInternal(aChannel, defaultIconURI, loadInfo);
57 0 : NS_ENSURE_SUCCESS(rv, rv);
58 0 : Unused << (*aChannel)->SetContentType(NS_LITERAL_CSTRING(FAVICON_DEFAULT_MIMETYPE));
59 0 : Unused << aOriginalChannel->SetContentType(NS_LITERAL_CSTRING(FAVICON_DEFAULT_MIMETYPE));
60 0 : return NS_OK;
61 : }
62 :
63 : ////////////////////////////////////////////////////////////////////////////////
64 : //// faviconAsyncLoader
65 :
66 : namespace {
67 :
68 : /**
69 : * An instance of this class is passed to the favicon service as the callback
70 : * for getting favicon data from the database. We'll get this data back in
71 : * HandleResult, and on HandleCompletion, we'll close our output stream which
72 : * will close the original channel for the favicon request.
73 : *
74 : * However, if an error occurs at any point and we don't have mData, we will
75 : * just fallback to the default favicon. If anything happens at that point, the
76 : * world must be against us, so we can do nothing.
77 : */
78 : class faviconAsyncLoader : public AsyncStatementCallback
79 : {
80 : public:
81 0 : faviconAsyncLoader(nsIChannel *aChannel, nsIStreamListener *aListener,
82 : uint16_t aPreferredSize)
83 0 : : mChannel(aChannel)
84 : , mListener(aListener)
85 0 : , mPreferredSize(aPreferredSize)
86 : {
87 0 : MOZ_ASSERT(aChannel, "Not providing a channel will result in crashes!");
88 0 : MOZ_ASSERT(aListener, "Not providing a stream listener will result in crashes!");
89 0 : MOZ_ASSERT(aChannel, "Not providing a channel!");
90 0 : }
91 :
92 : //////////////////////////////////////////////////////////////////////////////
93 : //// mozIStorageStatementCallback
94 :
95 0 : NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet) override
96 : {
97 0 : nsCOMPtr<mozIStorageRow> row;
98 0 : while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
99 : int32_t width;
100 0 : nsresult rv = row->GetInt32(1, &width);
101 0 : NS_ENSURE_SUCCESS(rv, rv);
102 :
103 : // Check if we already found an image >= than the preferred size,
104 : // otherwise keep examining the next results.
105 0 : if (width < mPreferredSize && !mData.IsEmpty()) {
106 0 : return NS_OK;
107 : }
108 :
109 : // Eventually override the default mimeType for svg.
110 0 : if (width == UINT16_MAX) {
111 0 : rv = mChannel->SetContentType(NS_LITERAL_CSTRING(SVG_MIME_TYPE));
112 : } else {
113 0 : rv = mChannel->SetContentType(NS_LITERAL_CSTRING(PNG_MIME_TYPE));
114 : }
115 0 : NS_ENSURE_SUCCESS(rv, rv);
116 :
117 : // Obtain the binary blob that contains our favicon data.
118 : uint8_t *data;
119 : uint32_t dataLen;
120 0 : rv = row->GetBlob(0, &dataLen, &data);
121 0 : NS_ENSURE_SUCCESS(rv, rv);
122 0 : mData.Adopt(TO_CHARBUFFER(data), dataLen);
123 : }
124 :
125 0 : return NS_OK;
126 : }
127 :
128 0 : NS_IMETHOD HandleCompletion(uint16_t aReason) override
129 : {
130 0 : MOZ_DIAGNOSTIC_ASSERT(mListener);
131 0 : NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
132 :
133 : nsresult rv;
134 : // Ensure we'll break possible cycles with the listener.
135 0 : auto cleanup = MakeScopeExit([&] () {
136 0 : mListener = nullptr;
137 0 : });
138 :
139 0 : nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
140 : nsCOMPtr<nsIEventTarget> target =
141 0 : nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Other);
142 0 : if (!mData.IsEmpty()) {
143 0 : nsCOMPtr<nsIInputStream> stream;
144 0 : rv = NS_NewCStringInputStream(getter_AddRefs(stream), mData);
145 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
146 0 : if (NS_SUCCEEDED(rv)) {
147 0 : RefPtr<nsInputStreamPump> pump;
148 0 : rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream, -1, -1, 0, 0,
149 0 : true, target);
150 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
151 0 : if (NS_SUCCEEDED(rv)) {
152 0 : return pump->AsyncRead(mListener, nullptr);
153 : }
154 : }
155 : }
156 :
157 : // Fallback to the default favicon.
158 : // we should pass the loadInfo of the original channel along
159 : // to the new channel. Note that mChannel can not be null,
160 : // constructor checks that.
161 0 : nsCOMPtr<nsIChannel> newChannel;
162 0 : rv = GetDefaultIcon(mChannel, getter_AddRefs(newChannel));
163 0 : if (NS_FAILED(rv)) {
164 0 : mListener->OnStartRequest(mChannel, nullptr);
165 0 : mListener->OnStopRequest(mChannel, nullptr, rv);
166 0 : return rv;
167 : }
168 0 : return newChannel->AsyncOpen2(mListener);
169 : }
170 :
171 : protected:
172 0 : virtual ~faviconAsyncLoader() {}
173 :
174 : private:
175 : nsCOMPtr<nsIChannel> mChannel;
176 : nsCOMPtr<nsIStreamListener> mListener;
177 : nsCString mData;
178 : uint16_t mPreferredSize;
179 : };
180 :
181 : } // namespace
182 :
183 : ////////////////////////////////////////////////////////////////////////////////
184 : //// nsAnnoProtocolHandler
185 :
186 0 : NS_IMPL_ISUPPORTS(nsAnnoProtocolHandler, nsIProtocolHandler)
187 :
188 : // nsAnnoProtocolHandler::GetScheme
189 :
190 : NS_IMETHODIMP
191 0 : nsAnnoProtocolHandler::GetScheme(nsACString& aScheme)
192 : {
193 0 : aScheme.AssignLiteral("moz-anno");
194 0 : return NS_OK;
195 : }
196 :
197 :
198 : // nsAnnoProtocolHandler::GetDefaultPort
199 : //
200 : // There is no default port for annotation URLs
201 :
202 : NS_IMETHODIMP
203 0 : nsAnnoProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
204 : {
205 0 : *aDefaultPort = -1;
206 0 : return NS_OK;
207 : }
208 :
209 :
210 : // nsAnnoProtocolHandler::GetProtocolFlags
211 :
212 : NS_IMETHODIMP
213 0 : nsAnnoProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
214 : {
215 0 : *aProtocolFlags = (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
216 : URI_IS_LOCAL_RESOURCE);
217 0 : return NS_OK;
218 : }
219 :
220 :
221 : // nsAnnoProtocolHandler::NewURI
222 :
223 : NS_IMETHODIMP
224 0 : nsAnnoProtocolHandler::NewURI(const nsACString& aSpec,
225 : const char *aOriginCharset,
226 : nsIURI *aBaseURI, nsIURI **_retval)
227 : {
228 0 : nsCOMPtr <nsIURI> uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
229 0 : if (!uri)
230 0 : return NS_ERROR_OUT_OF_MEMORY;
231 0 : nsresult rv = uri->SetSpec(aSpec);
232 0 : NS_ENSURE_SUCCESS(rv, rv);
233 :
234 0 : *_retval = nullptr;
235 0 : uri.swap(*_retval);
236 0 : return NS_OK;
237 : }
238 :
239 :
240 : // nsAnnoProtocolHandler::NewChannel
241 : //
242 :
243 : NS_IMETHODIMP
244 0 : nsAnnoProtocolHandler::NewChannel2(nsIURI* aURI,
245 : nsILoadInfo* aLoadInfo,
246 : nsIChannel** _retval)
247 : {
248 0 : NS_ENSURE_ARG_POINTER(aURI);
249 :
250 : // annotation info
251 0 : nsCOMPtr<nsIURI> annoURI;
252 0 : nsAutoCString annoName;
253 0 : nsresult rv = ParseAnnoURI(aURI, getter_AddRefs(annoURI), annoName);
254 0 : NS_ENSURE_SUCCESS(rv, rv);
255 :
256 : // Only favicon annotation are supported.
257 0 : if (!annoName.EqualsLiteral(FAVICON_ANNOTATION_NAME))
258 0 : return NS_ERROR_INVALID_ARG;
259 :
260 0 : return NewFaviconChannel(aURI, annoURI, aLoadInfo, _retval);
261 : }
262 :
263 : NS_IMETHODIMP
264 0 : nsAnnoProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
265 : {
266 0 : return NewChannel2(aURI, nullptr, _retval);
267 : }
268 :
269 :
270 : // nsAnnoProtocolHandler::AllowPort
271 : //
272 : // Don't override any bans on bad ports.
273 :
274 : NS_IMETHODIMP
275 0 : nsAnnoProtocolHandler::AllowPort(int32_t port, const char *scheme,
276 : bool *_retval)
277 : {
278 0 : *_retval = false;
279 0 : return NS_OK;
280 : }
281 :
282 :
283 : // nsAnnoProtocolHandler::ParseAnnoURI
284 : //
285 : // Splits an annotation URL into its URI and name parts
286 :
287 : nsresult
288 0 : nsAnnoProtocolHandler::ParseAnnoURI(nsIURI* aURI,
289 : nsIURI** aResultURI, nsCString& aName)
290 : {
291 : nsresult rv;
292 0 : nsAutoCString path;
293 0 : rv = aURI->GetPath(path);
294 0 : NS_ENSURE_SUCCESS(rv, rv);
295 :
296 0 : int32_t firstColon = path.FindChar(':');
297 0 : if (firstColon <= 0)
298 0 : return NS_ERROR_MALFORMED_URI;
299 :
300 0 : rv = NS_NewURI(aResultURI, Substring(path, firstColon + 1));
301 0 : NS_ENSURE_SUCCESS(rv, rv);
302 :
303 0 : aName = Substring(path, 0, firstColon);
304 0 : return NS_OK;
305 : }
306 :
307 : nsresult
308 0 : nsAnnoProtocolHandler::NewFaviconChannel(nsIURI *aURI, nsIURI *aAnnotationURI,
309 : nsILoadInfo* aLoadInfo, nsIChannel **_channel)
310 : {
311 : // Create our channel. We'll call SetContentType with the right type when
312 : // we know what it actually is.
313 0 : nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
314 : aURI, aLoadInfo, aAnnotationURI,
315 0 : [] (nsIStreamListener* listener, nsIChannel* channel, nsIURI* annotationURI) {
316 0 : auto fallback = [&] () -> RequestOrReason {
317 0 : nsCOMPtr<nsIChannel> chan;
318 0 : nsresult rv = GetDefaultIcon(channel, getter_AddRefs(chan));
319 0 : NS_ENSURE_SUCCESS(rv, Err(rv));
320 :
321 0 : rv = chan->AsyncOpen2(listener);
322 0 : NS_ENSURE_SUCCESS(rv, Err(rv));
323 :
324 0 : return RequestOrReason(chan.forget());
325 0 : };
326 :
327 : // Now we go ahead and get our data asynchronously for the favicon.
328 : // Ignore the ref part of the URI before querying the database because
329 : // we may have added a size fragment for rendering purposes.
330 0 : nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
331 0 : nsAutoCString faviconSpec;
332 0 : nsresult rv = annotationURI->GetSpecIgnoringRef(faviconSpec);
333 : // Any failures fallback to the default icon channel.
334 0 : if (NS_FAILED(rv) || !faviconService)
335 0 : return fallback();
336 :
337 0 : uint16_t preferredSize = UINT16_MAX;
338 0 : MOZ_ALWAYS_SUCCEEDS(faviconService->PreferredSizeFromURI(annotationURI, &preferredSize));
339 : nsCOMPtr<mozIStorageStatementCallback> callback =
340 0 : new faviconAsyncLoader(channel, listener, preferredSize);
341 0 : if (!callback)
342 0 : return fallback();
343 :
344 0 : rv = faviconService->GetFaviconDataAsync(faviconSpec, callback);
345 0 : if (NS_FAILED(rv))
346 0 : return fallback();
347 :
348 0 : return RequestOrReason(nullptr);
349 0 : });
350 0 : NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY);
351 :
352 0 : channel.forget(_channel);
353 0 : return NS_OK;
354 : }
|