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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ExtensionProtocolHandler.h"
8 
9 #include "nsIAddonPolicyService.h"
10 #include "nsServiceManagerUtils.h"
11 #include "nsIURL.h"
12 #include "nsIChannel.h"
13 #include "nsIStreamListener.h"
14 #include "nsIRequestObserver.h"
15 #include "nsIInputStreamChannel.h"
16 #include "nsIInputStream.h"
17 #include "nsIOutputStream.h"
18 #include "nsIStreamConverterService.h"
19 #include "nsIPipe.h"
20 #include "nsNetUtil.h"
21 #include "LoadInfo.h"
22 
23 namespace mozilla {
24 namespace net {
25 
NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler,nsISubstitutingProtocolHandler,nsIProtocolHandler,nsIProtocolHandlerWithDynamicFlags,nsISupportsWeakReference)26 NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler,
27                         nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags,
28                         nsISupportsWeakReference)
29 NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
30 NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler)
31 
32 nsresult
33 ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags)
34 {
35   // In general a moz-extension URI is only loadable by chrome, but a whitelisted
36   // subset are web-accessible (and cross-origin fetchable). Check that whitelist.
37   nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
38   bool loadableByAnyone = false;
39   if (aps) {
40     nsresult rv = aps->ExtensionURILoadableByAnyone(aURI, &loadableByAnyone);
41     NS_ENSURE_SUCCESS(rv, rv);
42   }
43 
44   *aFlags = URI_STD | URI_IS_LOCAL_RESOURCE | (loadableByAnyone ? (URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE) : URI_DANGEROUS_TO_LOAD);
45   return NS_OK;
46 }
47 
48 class PipeCloser : public nsIRequestObserver
49 {
50 public:
51   NS_DECL_ISUPPORTS
52 
PipeCloser(nsIOutputStream * aOutputStream)53   explicit PipeCloser(nsIOutputStream* aOutputStream) :
54     mOutputStream(aOutputStream)
55   {
56   }
57 
OnStartRequest(nsIRequest *,nsISupports *)58   NS_IMETHOD OnStartRequest(nsIRequest*, nsISupports*) override
59   {
60     return NS_OK;
61   }
62 
OnStopRequest(nsIRequest *,nsISupports *,nsresult aStatusCode)63   NS_IMETHOD OnStopRequest(nsIRequest*, nsISupports*, nsresult aStatusCode) override
64   {
65     NS_ENSURE_TRUE(mOutputStream, NS_ERROR_UNEXPECTED);
66 
67     nsresult rv = mOutputStream->Close();
68     mOutputStream = nullptr;
69     return rv;
70   }
71 
72 protected:
~PipeCloser()73   virtual ~PipeCloser() {}
74 
75 private:
76   nsCOMPtr<nsIOutputStream> mOutputStream;
77 };
78 
NS_IMPL_ISUPPORTS(PipeCloser,nsIRequestObserver)79 NS_IMPL_ISUPPORTS(PipeCloser, nsIRequestObserver)
80 
81 bool
82 ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
83                                               const nsACString& aPath,
84                                               const nsACString& aPathname,
85                                               nsACString& aResult)
86 {
87   // Create special moz-extension:-pages such as moz-extension://foo/_blank.html
88   // for all registered extensions. We can't just do this as a substitution
89   // because substitutions can only match on host.
90   if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) {
91     return false;
92   }
93   if (aPathname.EqualsLiteral("/_blank.html")) {
94     aResult.AssignLiteral("about:blank");
95     return true;
96   }
97   if (aPathname.EqualsLiteral("/_generated_background_page.html")) {
98     nsCOMPtr<nsIAddonPolicyService> aps =
99       do_GetService("@mozilla.org/addons/policy-service;1");
100     if (!aps) {
101       return false;
102     }
103     nsresult rv = aps->GetGeneratedBackgroundPageUrl(aHost, aResult);
104     NS_ENSURE_SUCCESS(rv, false);
105     if (!aResult.IsEmpty()) {
106       MOZ_RELEASE_ASSERT(Substring(aResult, 0, 5).Equals("data:"));
107       return true;
108     }
109   }
110 
111   return false;
112 }
113 
114 nsresult
SubstituteChannel(nsIURI * aURI,nsILoadInfo * aLoadInfo,nsIChannel ** result)115 ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI,
116                                             nsILoadInfo* aLoadInfo,
117                                             nsIChannel** result)
118 {
119   nsresult rv;
120   nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
121   NS_ENSURE_SUCCESS(rv, rv);
122 
123   nsAutoCString ext;
124   rv = url->GetFileExtension(ext);
125   NS_ENSURE_SUCCESS(rv, rv);
126 
127   if (!ext.LowerCaseEqualsLiteral("css")) {
128     return NS_OK;
129   }
130 
131   // Filter CSS files to replace locale message tokens with localized strings.
132 
133   nsCOMPtr<nsIStreamConverterService> convService =
134     do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
135   NS_ENSURE_SUCCESS(rv, rv);
136 
137   const char* kFromType = "application/vnd.mozilla.webext.unlocalized";
138   const char* kToType = "text/css";
139 
140   nsCOMPtr<nsIInputStream> inputStream;
141   if (aLoadInfo &&
142       aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
143     // If the channel needs to enforce CORS, we need to open the channel async.
144 
145     nsCOMPtr<nsIOutputStream> outputStream;
146     rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
147                     0, UINT32_MAX, true, false);
148     NS_ENSURE_SUCCESS(rv, rv);
149 
150     nsCOMPtr<nsIStreamListener> listener;
151     nsCOMPtr<nsIRequestObserver> observer = new PipeCloser(outputStream);
152     rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), outputStream, observer);
153     NS_ENSURE_SUCCESS(rv, rv);
154 
155     nsCOMPtr<nsIStreamListener> converter;
156     rv = convService->AsyncConvertData(kFromType, kToType, listener,
157                                        aURI, getter_AddRefs(converter));
158     NS_ENSURE_SUCCESS(rv, rv);
159 
160     nsCOMPtr<nsILoadInfo> loadInfo =
161       static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest();
162     (*result)->SetLoadInfo(loadInfo);
163 
164     rv = (*result)->AsyncOpen2(converter);
165   } else {
166     // Stylesheet loads for extension content scripts require a sync channel.
167 
168     nsCOMPtr<nsIInputStream> sourceStream;
169     if (aLoadInfo && aLoadInfo->GetEnforceSecurity()) {
170       rv = (*result)->Open2(getter_AddRefs(sourceStream));
171     } else {
172       rv = (*result)->Open(getter_AddRefs(sourceStream));
173     }
174     NS_ENSURE_SUCCESS(rv, rv);
175 
176     rv = convService->Convert(sourceStream, kFromType, kToType,
177                               aURI, getter_AddRefs(inputStream));
178   }
179   NS_ENSURE_SUCCESS(rv, rv);
180 
181   nsCOMPtr<nsIChannel> channel;
182   rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, inputStream,
183                                         NS_LITERAL_CSTRING("text/css"),
184                                         NS_LITERAL_CSTRING("utf-8"),
185                                         aLoadInfo);
186   NS_ENSURE_SUCCESS(rv, rv);
187 
188   channel.swap(*result);
189   return NS_OK;
190 }
191 
192 } // namespace net
193 } // namespace mozilla
194