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