|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
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 "mozilla/chrome/RegistryMessageUtils.h" |
|
7 |
|
8 #include "nsResProtocolHandler.h" |
|
9 #include "nsIIOService.h" |
|
10 #include "nsIFile.h" |
|
11 #include "nsNetUtil.h" |
|
12 #include "nsURLHelper.h" |
|
13 #include "nsEscape.h" |
|
14 |
|
15 #include "mozilla/Omnijar.h" |
|
16 |
|
17 static NS_DEFINE_CID(kResURLCID, NS_RESURL_CID); |
|
18 |
|
19 static nsResProtocolHandler *gResHandler = nullptr; |
|
20 |
|
21 #if defined(PR_LOGGING) |
|
22 // |
|
23 // Log module for Resource Protocol logging... |
|
24 // |
|
25 // To enable logging (see prlog.h for full details): |
|
26 // |
|
27 // set NSPR_LOG_MODULES=nsResProtocol:5 |
|
28 // set NSPR_LOG_FILE=log.txt |
|
29 // |
|
30 // this enables PR_LOG_ALWAYS level information and places all output in |
|
31 // the file log.txt |
|
32 // |
|
33 static PRLogModuleInfo *gResLog; |
|
34 #endif |
|
35 |
|
36 #define kAPP NS_LITERAL_CSTRING("app") |
|
37 #define kGRE NS_LITERAL_CSTRING("gre") |
|
38 |
|
39 //---------------------------------------------------------------------------- |
|
40 // nsResURL : overrides nsStandardURL::GetFile to provide nsIFile resolution |
|
41 //---------------------------------------------------------------------------- |
|
42 |
|
43 nsresult |
|
44 nsResURL::EnsureFile() |
|
45 { |
|
46 nsresult rv; |
|
47 |
|
48 NS_ENSURE_TRUE(gResHandler, NS_ERROR_NOT_AVAILABLE); |
|
49 |
|
50 nsAutoCString spec; |
|
51 rv = gResHandler->ResolveURI(this, spec); |
|
52 if (NS_FAILED(rv)) |
|
53 return rv; |
|
54 |
|
55 nsAutoCString scheme; |
|
56 rv = net_ExtractURLScheme(spec, nullptr, nullptr, &scheme); |
|
57 if (NS_FAILED(rv)) |
|
58 return rv; |
|
59 |
|
60 // Bug 585869: |
|
61 // In most cases, the scheme is jar if it's not file. |
|
62 // Regardless, net_GetFileFromURLSpec should be avoided |
|
63 // when the scheme isn't file. |
|
64 if (!scheme.Equals(NS_LITERAL_CSTRING("file"))) |
|
65 return NS_ERROR_NO_INTERFACE; |
|
66 |
|
67 rv = net_GetFileFromURLSpec(spec, getter_AddRefs(mFile)); |
|
68 #ifdef DEBUG_bsmedberg |
|
69 if (NS_SUCCEEDED(rv)) { |
|
70 bool exists = true; |
|
71 mFile->Exists(&exists); |
|
72 if (!exists) { |
|
73 printf("resource %s doesn't exist!\n", spec.get()); |
|
74 } |
|
75 } |
|
76 #endif |
|
77 |
|
78 return rv; |
|
79 } |
|
80 |
|
81 /* virtual */ nsStandardURL* |
|
82 nsResURL::StartClone() |
|
83 { |
|
84 nsResURL *clone = new nsResURL(); |
|
85 return clone; |
|
86 } |
|
87 |
|
88 NS_IMETHODIMP |
|
89 nsResURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) |
|
90 { |
|
91 *aClassIDNoAlloc = kResURLCID; |
|
92 return NS_OK; |
|
93 } |
|
94 |
|
95 //---------------------------------------------------------------------------- |
|
96 // nsResProtocolHandler <public> |
|
97 //---------------------------------------------------------------------------- |
|
98 |
|
99 nsResProtocolHandler::nsResProtocolHandler() |
|
100 : mSubstitutions(32) |
|
101 { |
|
102 #if defined(PR_LOGGING) |
|
103 gResLog = PR_NewLogModule("nsResProtocol"); |
|
104 #endif |
|
105 |
|
106 NS_ASSERTION(!gResHandler, "res handler already created!"); |
|
107 gResHandler = this; |
|
108 } |
|
109 |
|
110 nsResProtocolHandler::~nsResProtocolHandler() |
|
111 { |
|
112 gResHandler = nullptr; |
|
113 } |
|
114 |
|
115 nsresult |
|
116 nsResProtocolHandler::Init() |
|
117 { |
|
118 nsresult rv; |
|
119 |
|
120 mIOService = do_GetIOService(&rv); |
|
121 NS_ENSURE_SUCCESS(rv, rv); |
|
122 |
|
123 nsAutoCString appURI, greURI; |
|
124 rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appURI); |
|
125 NS_ENSURE_SUCCESS(rv, rv); |
|
126 rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greURI); |
|
127 NS_ENSURE_SUCCESS(rv, rv); |
|
128 |
|
129 // |
|
130 // make resource:/// point to the application directory or omnijar |
|
131 // |
|
132 nsCOMPtr<nsIURI> uri; |
|
133 rv = NS_NewURI(getter_AddRefs(uri), appURI.Length() ? appURI : greURI); |
|
134 NS_ENSURE_SUCCESS(rv, rv); |
|
135 |
|
136 rv = SetSubstitution(EmptyCString(), uri); |
|
137 NS_ENSURE_SUCCESS(rv, rv); |
|
138 |
|
139 // |
|
140 // make resource://app/ point to the application directory or omnijar |
|
141 // |
|
142 rv = SetSubstitution(kAPP, uri); |
|
143 NS_ENSURE_SUCCESS(rv, rv); |
|
144 |
|
145 // |
|
146 // make resource://gre/ point to the GRE directory |
|
147 // |
|
148 if (appURI.Length()) { // We already have greURI in uri if appURI.Length() is 0. |
|
149 rv = NS_NewURI(getter_AddRefs(uri), greURI); |
|
150 NS_ENSURE_SUCCESS(rv, rv); |
|
151 } |
|
152 |
|
153 rv = SetSubstitution(kGRE, uri); |
|
154 NS_ENSURE_SUCCESS(rv, rv); |
|
155 |
|
156 //XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir... |
|
157 // but once I finish multiple chrome registration I'm not sure that it is needed |
|
158 |
|
159 // XXX dveditz: resource://pchrome/ defeats profile directory salting |
|
160 // if web content can load it. Tread carefully. |
|
161 |
|
162 return rv; |
|
163 } |
|
164 |
|
165 static PLDHashOperator |
|
166 EnumerateSubstitution(const nsACString& aKey, |
|
167 nsIURI* aURI, |
|
168 void* aArg) |
|
169 { |
|
170 nsTArray<ResourceMapping>* resources = |
|
171 static_cast<nsTArray<ResourceMapping>*>(aArg); |
|
172 SerializedURI uri; |
|
173 if (aURI) { |
|
174 aURI->GetSpec(uri.spec); |
|
175 aURI->GetOriginCharset(uri.charset); |
|
176 } |
|
177 |
|
178 ResourceMapping resource = { |
|
179 nsCString(aKey), uri |
|
180 }; |
|
181 resources->AppendElement(resource); |
|
182 return (PLDHashOperator)PL_DHASH_NEXT; |
|
183 } |
|
184 |
|
185 void |
|
186 nsResProtocolHandler::CollectSubstitutions(InfallibleTArray<ResourceMapping>& aResources) |
|
187 { |
|
188 mSubstitutions.EnumerateRead(&EnumerateSubstitution, &aResources); |
|
189 } |
|
190 |
|
191 //---------------------------------------------------------------------------- |
|
192 // nsResProtocolHandler::nsISupports |
|
193 //---------------------------------------------------------------------------- |
|
194 |
|
195 NS_IMPL_ISUPPORTS(nsResProtocolHandler, |
|
196 nsIResProtocolHandler, |
|
197 nsIProtocolHandler, |
|
198 nsISupportsWeakReference) |
|
199 |
|
200 //---------------------------------------------------------------------------- |
|
201 // nsResProtocolHandler::nsIProtocolHandler |
|
202 //---------------------------------------------------------------------------- |
|
203 |
|
204 NS_IMETHODIMP |
|
205 nsResProtocolHandler::GetScheme(nsACString &result) |
|
206 { |
|
207 result.AssignLiteral("resource"); |
|
208 return NS_OK; |
|
209 } |
|
210 |
|
211 NS_IMETHODIMP |
|
212 nsResProtocolHandler::GetDefaultPort(int32_t *result) |
|
213 { |
|
214 *result = -1; // no port for res: URLs |
|
215 return NS_OK; |
|
216 } |
|
217 |
|
218 NS_IMETHODIMP |
|
219 nsResProtocolHandler::GetProtocolFlags(uint32_t *result) |
|
220 { |
|
221 // XXXbz Is this really true for all resource: URIs? Could we |
|
222 // somehow give different flags to some of them? |
|
223 *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE; |
|
224 return NS_OK; |
|
225 } |
|
226 |
|
227 NS_IMETHODIMP |
|
228 nsResProtocolHandler::NewURI(const nsACString &aSpec, |
|
229 const char *aCharset, |
|
230 nsIURI *aBaseURI, |
|
231 nsIURI **result) |
|
232 { |
|
233 nsresult rv; |
|
234 |
|
235 nsResURL *resURL = new nsResURL(); |
|
236 if (!resURL) |
|
237 return NS_ERROR_OUT_OF_MEMORY; |
|
238 NS_ADDREF(resURL); |
|
239 |
|
240 // unescape any %2f and %2e to make sure nsStandardURL coalesces them. |
|
241 // Later net_GetFileFromURLSpec() will do a full unescape and we want to |
|
242 // treat them the same way the file system will. (bugs 380994, 394075) |
|
243 nsAutoCString spec; |
|
244 const char *src = aSpec.BeginReading(); |
|
245 const char *end = aSpec.EndReading(); |
|
246 const char *last = src; |
|
247 |
|
248 spec.SetCapacity(aSpec.Length()+1); |
|
249 for ( ; src < end; ++src) { |
|
250 if (*src == '%' && (src < end-2) && *(src+1) == '2') { |
|
251 char ch = '\0'; |
|
252 if (*(src+2) == 'f' || *(src+2) == 'F') |
|
253 ch = '/'; |
|
254 else if (*(src+2) == 'e' || *(src+2) == 'E') |
|
255 ch = '.'; |
|
256 |
|
257 if (ch) { |
|
258 if (last < src) |
|
259 spec.Append(last, src-last); |
|
260 spec.Append(ch); |
|
261 src += 2; |
|
262 last = src+1; // src will be incremented by the loop |
|
263 } |
|
264 } |
|
265 } |
|
266 if (last < src) |
|
267 spec.Append(last, src-last); |
|
268 |
|
269 rv = resURL->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI); |
|
270 if (NS_SUCCEEDED(rv)) |
|
271 rv = CallQueryInterface(resURL, result); |
|
272 NS_RELEASE(resURL); |
|
273 return rv; |
|
274 } |
|
275 |
|
276 NS_IMETHODIMP |
|
277 nsResProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result) |
|
278 { |
|
279 NS_ENSURE_ARG_POINTER(uri); |
|
280 nsresult rv; |
|
281 nsAutoCString spec; |
|
282 |
|
283 rv = ResolveURI(uri, spec); |
|
284 if (NS_FAILED(rv)) return rv; |
|
285 |
|
286 rv = mIOService->NewChannel(spec, nullptr, nullptr, result); |
|
287 if (NS_FAILED(rv)) return rv; |
|
288 |
|
289 nsLoadFlags loadFlags = 0; |
|
290 (*result)->GetLoadFlags(&loadFlags); |
|
291 (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE); |
|
292 return (*result)->SetOriginalURI(uri); |
|
293 } |
|
294 |
|
295 NS_IMETHODIMP |
|
296 nsResProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) |
|
297 { |
|
298 // don't override anything. |
|
299 *_retval = false; |
|
300 return NS_OK; |
|
301 } |
|
302 |
|
303 //---------------------------------------------------------------------------- |
|
304 // nsResProtocolHandler::nsIResProtocolHandler |
|
305 //---------------------------------------------------------------------------- |
|
306 |
|
307 NS_IMETHODIMP |
|
308 nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI) |
|
309 { |
|
310 if (!baseURI) { |
|
311 mSubstitutions.Remove(root); |
|
312 return NS_OK; |
|
313 } |
|
314 |
|
315 // If baseURI isn't a resource URI, we can set the substitution immediately. |
|
316 nsAutoCString scheme; |
|
317 nsresult rv = baseURI->GetScheme(scheme); |
|
318 NS_ENSURE_SUCCESS(rv, rv); |
|
319 if (!scheme.Equals(NS_LITERAL_CSTRING("resource"))) { |
|
320 mSubstitutions.Put(root, baseURI); |
|
321 return NS_OK; |
|
322 } |
|
323 |
|
324 // baseURI is a resource URI, let's resolve it first. |
|
325 nsAutoCString newBase; |
|
326 rv = ResolveURI(baseURI, newBase); |
|
327 NS_ENSURE_SUCCESS(rv, rv); |
|
328 |
|
329 nsCOMPtr<nsIURI> newBaseURI; |
|
330 rv = mIOService->NewURI(newBase, nullptr, nullptr, |
|
331 getter_AddRefs(newBaseURI)); |
|
332 NS_ENSURE_SUCCESS(rv, rv); |
|
333 |
|
334 mSubstitutions.Put(root, newBaseURI); |
|
335 return NS_OK; |
|
336 } |
|
337 |
|
338 NS_IMETHODIMP |
|
339 nsResProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result) |
|
340 { |
|
341 NS_ENSURE_ARG_POINTER(result); |
|
342 |
|
343 if (mSubstitutions.Get(root, result)) |
|
344 return NS_OK; |
|
345 |
|
346 // try invoking the directory service for "resource:root" |
|
347 |
|
348 nsAutoCString key; |
|
349 key.AssignLiteral("resource:"); |
|
350 key.Append(root); |
|
351 |
|
352 nsCOMPtr<nsIFile> file; |
|
353 nsresult rv = NS_GetSpecialDirectory(key.get(), getter_AddRefs(file)); |
|
354 if (NS_FAILED(rv)) |
|
355 return NS_ERROR_NOT_AVAILABLE; |
|
356 |
|
357 rv = mIOService->NewFileURI(file, result); |
|
358 if (NS_FAILED(rv)) |
|
359 return NS_ERROR_NOT_AVAILABLE; |
|
360 |
|
361 return NS_OK; |
|
362 } |
|
363 |
|
364 NS_IMETHODIMP |
|
365 nsResProtocolHandler::HasSubstitution(const nsACString& root, bool *result) |
|
366 { |
|
367 NS_ENSURE_ARG_POINTER(result); |
|
368 |
|
369 *result = mSubstitutions.Get(root, nullptr); |
|
370 return NS_OK; |
|
371 } |
|
372 |
|
373 NS_IMETHODIMP |
|
374 nsResProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result) |
|
375 { |
|
376 nsresult rv; |
|
377 |
|
378 nsAutoCString host; |
|
379 nsAutoCString path; |
|
380 |
|
381 rv = uri->GetAsciiHost(host); |
|
382 if (NS_FAILED(rv)) return rv; |
|
383 |
|
384 rv = uri->GetPath(path); |
|
385 if (NS_FAILED(rv)) return rv; |
|
386 |
|
387 // Unescape the path so we can perform some checks on it. |
|
388 nsAutoCString unescapedPath(path); |
|
389 NS_UnescapeURL(unescapedPath); |
|
390 |
|
391 // Don't misinterpret the filepath as an absolute URI. |
|
392 if (unescapedPath.FindChar(':') != -1) |
|
393 return NS_ERROR_MALFORMED_URI; |
|
394 |
|
395 if (unescapedPath.FindChar('\\') != -1) |
|
396 return NS_ERROR_MALFORMED_URI; |
|
397 |
|
398 const char *p = path.get() + 1; // path always starts with a slash |
|
399 NS_ASSERTION(*(p-1) == '/', "Path did not begin with a slash!"); |
|
400 |
|
401 if (*p == '/') |
|
402 return NS_ERROR_MALFORMED_URI; |
|
403 |
|
404 nsCOMPtr<nsIURI> baseURI; |
|
405 rv = GetSubstitution(host, getter_AddRefs(baseURI)); |
|
406 if (NS_FAILED(rv)) return rv; |
|
407 |
|
408 rv = baseURI->Resolve(nsDependentCString(p, path.Length()-1), result); |
|
409 |
|
410 #if defined(PR_LOGGING) |
|
411 if (PR_LOG_TEST(gResLog, PR_LOG_DEBUG)) { |
|
412 nsAutoCString spec; |
|
413 uri->GetAsciiSpec(spec); |
|
414 PR_LOG(gResLog, PR_LOG_DEBUG, |
|
415 ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get())); |
|
416 } |
|
417 #endif |
|
418 return rv; |
|
419 } |