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 #include "AddonPathService.h"
7 
8 #include "amIAddonManager.h"
9 #include "nsIURI.h"
10 #include "nsXULAppAPI.h"
11 #include "jsapi.h"
12 #include "nsServiceManagerUtils.h"
13 #include "nsLiteralString.h"
14 #include "nsThreadUtils.h"
15 #include "nsIIOService.h"
16 #include "nsNetUtil.h"
17 #include "nsIAddonPolicyService.h"
18 #include "nsIFileURL.h"
19 #include "nsIResProtocolHandler.h"
20 #include "nsIChromeRegistry.h"
21 #include "nsIJARURI.h"
22 #include "nsJSUtils.h"
23 #include "mozilla/dom/ScriptSettings.h"
24 #include "mozilla/dom/ToJSValue.h"
25 #include "mozilla/AddonPathService.h"
26 #include "mozilla/Omnijar.h"
27 
28 #include <algorithm>
29 
30 namespace mozilla {
31 
32 struct PathEntryComparator {
33   typedef AddonPathService::PathEntry PathEntry;
34 
Equalsmozilla::PathEntryComparator35   bool Equals(const PathEntry& entry1, const PathEntry& entry2) const {
36     return entry1.mPath == entry2.mPath;
37   }
38 
LessThanmozilla::PathEntryComparator39   bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const {
40     return entry1.mPath < entry2.mPath;
41   }
42 };
43 
AddonPathService()44 AddonPathService::AddonPathService() {}
45 
~AddonPathService()46 AddonPathService::~AddonPathService() {
47   MOZ_ASSERT(sInstance == this);
48   sInstance = nullptr;
49 }
50 
51 NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService)
52 
53 AddonPathService* AddonPathService::sInstance;
54 
55 /* static */ already_AddRefed<AddonPathService>
GetInstance()56 AddonPathService::GetInstance() {
57   if (!sInstance) {
58     sInstance = new AddonPathService();
59   }
60   return do_AddRef(sInstance);
61 }
62 
ConvertAddonId(const nsAString & addonIdString)63 static JSAddonId* ConvertAddonId(const nsAString& addonIdString) {
64   AutoSafeJSContext cx;
65   JS::RootedValue strv(cx);
66   if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) {
67     return nullptr;
68   }
69   JS::RootedString str(cx, strv.toString());
70   return JS::NewAddonId(cx, str);
71 }
72 
Find(const nsAString & path)73 JSAddonId* AddonPathService::Find(const nsAString& path) {
74   // Use binary search to find the nearest entry that is <= |path|.
75   PathEntryComparator comparator;
76   unsigned index =
77       mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator);
78   if (index == 0) {
79     return nullptr;
80   }
81   const PathEntry& entry = mPaths[index - 1];
82 
83   // Return the entry's addon if its path is a prefix of |path|.
84   if (StringBeginsWith(path, entry.mPath)) {
85     return entry.mAddonId;
86   }
87   return nullptr;
88 }
89 
90 NS_IMETHODIMP
FindAddonId(const nsAString & path,nsAString & addonIdString)91 AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString) {
92   if (JSAddonId* id = Find(path)) {
93     JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id));
94     AssignJSFlatString(addonIdString, flat);
95   }
96   return NS_OK;
97 }
98 
FindAddonId(const nsAString & path)99 /* static */ JSAddonId* AddonPathService::FindAddonId(const nsAString& path) {
100   // If no service has been created, then we're not going to find anything.
101   if (!sInstance) {
102     return nullptr;
103   }
104 
105   return sInstance->Find(path);
106 }
107 
108 NS_IMETHODIMP
InsertPath(const nsAString & path,const nsAString & addonIdString)109 AddonPathService::InsertPath(const nsAString& path,
110                              const nsAString& addonIdString) {
111   JSAddonId* addonId = ConvertAddonId(addonIdString);
112 
113   // Add the new path in sorted order.
114   PathEntryComparator comparator;
115   mPaths.InsertElementSorted(PathEntry(path, addonId), comparator);
116   return NS_OK;
117 }
118 
119 NS_IMETHODIMP
MapURIToAddonId(nsIURI * aURI,nsAString & addonIdString)120 AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) {
121   if (JSAddonId* id = MapURIToAddonID(aURI)) {
122     JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id));
123     AssignJSFlatString(addonIdString, flat);
124   }
125   return NS_OK;
126 }
127 
ResolveURI(nsIURI * aURI,nsAString & out)128 static nsresult ResolveURI(nsIURI* aURI, nsAString& out) {
129   bool equals;
130   nsresult rv;
131   nsCOMPtr<nsIURI> uri;
132   nsAutoCString spec;
133 
134   // Resolve resource:// URIs. At the end of this if/else block, we
135   // have both spec and uri variables identifying the same URI.
136   if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) {
137     nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
138     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
139 
140     nsCOMPtr<nsIProtocolHandler> ph;
141     rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
142     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
143 
144     nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
145     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
146 
147     rv = irph->ResolveURI(aURI, spec);
148     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
149 
150     rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
151     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
152   } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) {
153     // Going through the Chrome Registry may be prohibitively slow for many of
154     // the well-known chrome:// URI packages, so check for a few of them here
155     // first in order to fail early if we don't have a chrome:// URI which
156     // could have been provided by an add-on.
157     nsAutoCString package;
158     rv = aURI->GetHostPort(package);
159     if (NS_WARN_IF(NS_FAILED(rv)) || package.EqualsLiteral("branding") ||
160         package.EqualsLiteral("browser") || package.EqualsLiteral("branding") ||
161         package.EqualsLiteral("global") ||
162         package.EqualsLiteral("global-platform") ||
163         package.EqualsLiteral("mozapps") || package.EqualsLiteral("necko") ||
164         package.EqualsLiteral("passwordmgr") ||
165         package.EqualsLiteral("pippki") || package.EqualsLiteral("pipnss")) {
166       // Returning a failure code means the URI isn't associated with an add-on
167       // ID.
168       return NS_ERROR_FAILURE;
169     }
170 
171     nsCOMPtr<nsIChromeRegistry> chromeReg =
172         mozilla::services::GetChromeRegistryService();
173     if (NS_WARN_IF(!chromeReg)) return NS_ERROR_UNEXPECTED;
174 
175     rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri));
176     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
177   } else {
178     uri = aURI;
179   }
180 
181   if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) {
182     nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
183     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
184 
185     nsCOMPtr<nsIURI> jarFileURI;
186     rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
187     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
188 
189     return ResolveURI(jarFileURI, out);
190   }
191 
192   if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) {
193     nsCOMPtr<nsIFileURL> baseFileURL = do_QueryInterface(uri, &rv);
194     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
195 
196     nsCOMPtr<nsIFile> file;
197     rv = baseFileURL->GetFile(getter_AddRefs(file));
198     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
199 
200     return file->GetPath(out);
201   }
202   return NS_ERROR_FAILURE;
203 }
204 
MapURIToAddonID(nsIURI * aURI)205 JSAddonId* MapURIToAddonID(nsIURI* aURI) {
206   if (!NS_IsMainThread() || !XRE_IsParentProcess()) {
207     return nullptr;
208   }
209 
210   bool equals;
211   nsresult rv;
212   if (NS_SUCCEEDED(aURI->SchemeIs("moz-extension", &equals)) && equals) {
213     nsCOMPtr<nsIAddonPolicyService> service =
214         do_GetService("@mozilla.org/addons/policy-service;1");
215     if (service) {
216       nsString addonId;
217       rv = service->ExtensionURIToAddonId(aURI, addonId);
218       if (NS_FAILED(rv)) return nullptr;
219 
220       return ConvertAddonId(addonId);
221     }
222   }
223 
224   nsAutoString filePath;
225   rv = ResolveURI(aURI, filePath);
226   if (NS_FAILED(rv)) return nullptr;
227 
228   nsCOMPtr<nsIFile> greJar = Omnijar::GetPath(Omnijar::GRE);
229   nsCOMPtr<nsIFile> appJar = Omnijar::GetPath(Omnijar::APP);
230   if (greJar && appJar) {
231     nsAutoString greJarString, appJarString;
232     if (NS_FAILED(greJar->GetPath(greJarString)) ||
233         NS_FAILED(appJar->GetPath(appJarString)))
234       return nullptr;
235 
236     // If |aURI| is part of either Omnijar, then it can't be part of an
237     // add-on. This catches pretty much all URLs for Firefox content.
238     if (filePath.Equals(greJarString) || filePath.Equals(appJarString))
239       return nullptr;
240   }
241 
242   // If it's not part of Firefox, we resort to binary searching through the
243   // add-on paths.
244   return AddonPathService::FindAddonId(filePath);
245 }
246 
247 }  // namespace mozilla
248