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