Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsHostObjectProtocolHandler.h"
8 #include "nsHostObjectURI.h"
9 #include "nsError.h"
10 #include "nsClassHashtable.h"
11 #include "nsNetUtil.h"
12 #include "nsIPrincipal.h"
13 #include "nsIDOMFile.h"
14 #include "nsIDOMMediaStream.h"
15 #include "mozilla/dom/MediaSource.h"
16 #include "nsIMemoryReporter.h"
17 #include "mozilla/Preferences.h"
19 // -----------------------------------------------------------------------
20 // Hash table
21 struct DataInfo
22 {
23 // mObject is expected to be an nsIDOMBlob, nsIDOMMediaStream, or MediaSource
24 nsCOMPtr<nsISupports> mObject;
25 nsCOMPtr<nsIPrincipal> mPrincipal;
26 nsCString mStack;
27 };
29 static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable;
31 // Memory reporting for the hash table.
32 namespace mozilla {
34 class HostObjectURLsReporter MOZ_FINAL : public nsIMemoryReporter
35 {
36 public:
37 NS_DECL_ISUPPORTS
39 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
40 nsISupports* aData)
41 {
42 return MOZ_COLLECT_REPORT(
43 "host-object-urls", KIND_OTHER, UNITS_COUNT,
44 gDataTable ? gDataTable->Count() : 0,
45 "The number of host objects stored for access via URLs "
46 "(e.g. blobs passed to URL.createObjectURL).");
47 }
48 };
50 NS_IMPL_ISUPPORTS(HostObjectURLsReporter, nsIMemoryReporter)
52 class BlobURLsReporter MOZ_FINAL : public nsIMemoryReporter
53 {
54 public:
55 NS_DECL_ISUPPORTS
57 NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback,
58 nsISupports* aData)
59 {
60 EnumArg env;
61 env.mCallback = aCallback;
62 env.mData = aData;
64 if (gDataTable) {
65 gDataTable->EnumerateRead(CountCallback, &env);
66 gDataTable->EnumerateRead(ReportCallback, &env);
67 }
68 return NS_OK;
69 }
71 // Initialize info->mStack to record JS stack info, if enabled.
72 // The string generated here is used in ReportCallback, below.
73 static void GetJSStackForBlob(DataInfo* aInfo)
74 {
75 nsCString& stack = aInfo->mStack;
76 MOZ_ASSERT(stack.IsEmpty());
77 const uint32_t maxFrames = Preferences::GetUint("memory.blob_report.stack_frames");
79 if (maxFrames == 0) {
80 return;
81 }
83 nsresult rv;
84 nsIXPConnect* xpc = nsContentUtils::XPConnect();
85 nsCOMPtr<nsIStackFrame> frame;
86 rv = xpc->GetCurrentJSStack(getter_AddRefs(frame));
87 NS_ENSURE_SUCCESS_VOID(rv);
89 nsAutoCString origin;
90 nsCOMPtr<nsIURI> principalURI;
91 if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI)))
92 && principalURI) {
93 principalURI->GetPrePath(origin);
94 }
96 for (uint32_t i = 0; i < maxFrames && frame; ++i) {
97 nsString fileNameUTF16;
98 int32_t lineNumber = 0;
100 frame->GetFilename(fileNameUTF16);
101 frame->GetLineNumber(&lineNumber);
103 if (!fileNameUTF16.IsEmpty()) {
104 NS_ConvertUTF16toUTF8 fileName(fileNameUTF16);
105 stack += "js(";
106 if (!origin.IsEmpty()) {
107 // Make the file name root-relative for conciseness if possible.
108 const char* originData;
109 uint32_t originLen;
111 originLen = origin.GetData(&originData);
112 // If fileName starts with origin + "/", cut up to that "/".
113 if (fileName.Length() >= originLen + 1 &&
114 memcmp(fileName.get(), originData, originLen) == 0 &&
115 fileName[originLen] == '/') {
116 fileName.Cut(0, originLen);
117 }
118 }
119 fileName.ReplaceChar('/', '\\');
120 stack += fileName;
121 if (lineNumber > 0) {
122 stack += ", line=";
123 stack.AppendInt(lineNumber);
124 }
125 stack += ")/";
126 }
128 rv = frame->GetCaller(getter_AddRefs(frame));
129 NS_ENSURE_SUCCESS_VOID(rv);
130 }
131 }
133 private:
134 struct EnumArg {
135 nsIHandleReportCallback* mCallback;
136 nsISupports* mData;
137 nsDataHashtable<nsPtrHashKey<nsIDOMBlob>, uint32_t> mRefCounts;
138 };
140 // Determine number of URLs per blob, to handle the case where it's > 1.
141 static PLDHashOperator CountCallback(nsCStringHashKey::KeyType aKey,
142 DataInfo* aInfo,
143 void* aUserArg)
144 {
145 EnumArg* envp = static_cast<EnumArg*>(aUserArg);
146 nsCOMPtr<nsIDOMBlob> blob;
148 blob = do_QueryInterface(aInfo->mObject);
149 if (blob) {
150 envp->mRefCounts.Put(blob, envp->mRefCounts.Get(blob) + 1);
151 }
152 return PL_DHASH_NEXT;
153 }
155 static PLDHashOperator ReportCallback(nsCStringHashKey::KeyType aKey,
156 DataInfo* aInfo,
157 void* aUserArg)
158 {
159 EnumArg* envp = static_cast<EnumArg*>(aUserArg);
160 nsCOMPtr<nsIDOMBlob> blob;
162 blob = do_QueryInterface(aInfo->mObject);
163 if (blob) {
164 NS_NAMED_LITERAL_CSTRING
165 (desc, "A blob URL allocated with URL.createObjectURL; the referenced "
166 "blob cannot be freed until all URLs for it have been explicitly "
167 "invalidated with URL.revokeObjectURL.");
168 nsAutoCString path, url, owner, specialDesc;
169 nsCOMPtr<nsIURI> principalURI;
170 uint64_t size = 0;
171 uint32_t refCount = 1;
172 DebugOnly<bool> blobWasCounted;
174 blobWasCounted = envp->mRefCounts.Get(blob, &refCount);
175 MOZ_ASSERT(blobWasCounted);
176 MOZ_ASSERT(refCount > 0);
178 bool isMemoryFile = blob->IsMemoryFile();
180 if (isMemoryFile) {
181 if (NS_FAILED(blob->GetSize(&size))) {
182 size = 0;
183 }
184 }
186 path = isMemoryFile ? "memory-blob-urls/" : "file-blob-urls/";
187 if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) &&
188 principalURI != nullptr &&
189 NS_SUCCEEDED(principalURI->GetSpec(owner)) &&
190 !owner.IsEmpty()) {
191 owner.ReplaceChar('/', '\\');
192 path += "owner(";
193 path += owner;
194 path += ")";
195 } else {
196 path += "owner unknown";
197 }
198 path += "/";
199 path += aInfo->mStack;
200 url = aKey;
201 url.ReplaceChar('/', '\\');
202 path += url;
203 if (refCount > 1) {
204 nsAutoCString addrStr;
206 addrStr = "0x";
207 addrStr.AppendInt((uint64_t)(nsIDOMBlob*)blob, 16);
209 path += " ";
210 path.AppendInt(refCount);
211 path += "@";
212 path += addrStr;
214 specialDesc = desc;
215 specialDesc += "\n\nNOTE: This blob (address ";
216 specialDesc += addrStr;
217 specialDesc += ") has ";
218 specialDesc.AppendInt(refCount);
219 specialDesc += " URLs.";
220 if (isMemoryFile) {
221 specialDesc += " Its size is divided ";
222 specialDesc += refCount > 2 ? "among" : "between";
223 specialDesc += " them in this report.";
224 }
225 }
227 const nsACString& descString = specialDesc.IsEmpty()
228 ? static_cast<const nsACString&>(desc)
229 : static_cast<const nsACString&>(specialDesc);
230 if (isMemoryFile) {
231 envp->mCallback->Callback(EmptyCString(),
232 path,
233 KIND_OTHER,
234 UNITS_BYTES,
235 size / refCount,
236 descString,
237 envp->mData);
238 }
239 else {
240 envp->mCallback->Callback(EmptyCString(),
241 path,
242 KIND_OTHER,
243 UNITS_COUNT,
244 1,
245 descString,
246 envp->mData);
247 }
248 }
249 return PL_DHASH_NEXT;
250 }
251 };
253 NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter)
255 }
257 void
258 nsHostObjectProtocolHandler::Init(void)
259 {
260 static bool initialized = false;
262 if (!initialized) {
263 initialized = true;
264 RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter());
265 RegisterStrongMemoryReporter(new mozilla::BlobURLsReporter());
266 }
267 }
269 nsHostObjectProtocolHandler::nsHostObjectProtocolHandler()
270 {
271 Init();
272 }
274 nsresult
275 nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aScheme,
276 nsISupports* aObject,
277 nsIPrincipal* aPrincipal,
278 nsACString& aUri)
279 {
280 Init();
282 nsresult rv = GenerateURIString(aScheme, aUri);
283 NS_ENSURE_SUCCESS(rv, rv);
285 if (!gDataTable) {
286 gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>;
287 }
289 DataInfo* info = new DataInfo;
291 info->mObject = aObject;
292 info->mPrincipal = aPrincipal;
293 mozilla::BlobURLsReporter::GetJSStackForBlob(info);
295 gDataTable->Put(aUri, info);
296 return NS_OK;
297 }
299 void
300 nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri)
301 {
302 if (gDataTable) {
303 nsCString uriIgnoringRef;
304 int32_t hashPos = aUri.FindChar('#');
305 if (hashPos < 0) {
306 uriIgnoringRef = aUri;
307 }
308 else {
309 uriIgnoringRef = StringHead(aUri, hashPos);
310 }
311 gDataTable->Remove(uriIgnoringRef);
312 if (gDataTable->Count() == 0) {
313 delete gDataTable;
314 gDataTable = nullptr;
315 }
316 }
317 }
319 nsresult
320 nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme,
321 nsACString& aUri)
322 {
323 nsresult rv;
324 nsCOMPtr<nsIUUIDGenerator> uuidgen =
325 do_GetService("@mozilla.org/uuid-generator;1", &rv);
326 NS_ENSURE_SUCCESS(rv, rv);
328 nsID id;
329 rv = uuidgen->GenerateUUIDInPlace(&id);
330 NS_ENSURE_SUCCESS(rv, rv);
332 char chars[NSID_LENGTH];
333 id.ToProvidedString(chars);
335 aUri += aScheme;
336 aUri += NS_LITERAL_CSTRING(":");
337 aUri += Substring(chars + 1, chars + NSID_LENGTH - 2);
339 return NS_OK;
340 }
342 static DataInfo*
343 GetDataInfo(const nsACString& aUri)
344 {
345 if (!gDataTable) {
346 return nullptr;
347 }
349 DataInfo* res;
350 nsCString uriIgnoringRef;
351 int32_t hashPos = aUri.FindChar('#');
352 if (hashPos < 0) {
353 uriIgnoringRef = aUri;
354 }
355 else {
356 uriIgnoringRef = StringHead(aUri, hashPos);
357 }
358 gDataTable->Get(uriIgnoringRef, &res);
360 return res;
361 }
363 nsIPrincipal*
364 nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri)
365 {
366 if (!gDataTable) {
367 return nullptr;
368 }
370 DataInfo* res = GetDataInfo(aUri);
372 if (!res) {
373 return nullptr;
374 }
376 return res->mPrincipal;
377 }
379 void
380 nsHostObjectProtocolHandler::Traverse(const nsACString& aUri,
381 nsCycleCollectionTraversalCallback& aCallback)
382 {
383 if (!gDataTable) {
384 return;
385 }
387 DataInfo* res;
388 gDataTable->Get(aUri, &res);
389 if (!res) {
390 return;
391 }
393 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mObject");
394 aCallback.NoteXPCOMChild(res->mObject);
395 }
397 static nsISupports*
398 GetDataObject(nsIURI* aURI)
399 {
400 nsCString spec;
401 aURI->GetSpec(spec);
403 DataInfo* info = GetDataInfo(spec);
404 return info ? info->mObject : nullptr;
405 }
407 // -----------------------------------------------------------------------
408 // Protocol handler
410 NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler)
412 NS_IMETHODIMP
413 nsHostObjectProtocolHandler::GetDefaultPort(int32_t *result)
414 {
415 *result = -1;
416 return NS_OK;
417 }
419 NS_IMETHODIMP
420 nsHostObjectProtocolHandler::GetProtocolFlags(uint32_t *result)
421 {
422 *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_SUBSUMERS |
423 URI_IS_LOCAL_RESOURCE | URI_NON_PERSISTABLE;
424 return NS_OK;
425 }
427 NS_IMETHODIMP
428 nsHostObjectProtocolHandler::NewURI(const nsACString& aSpec,
429 const char *aCharset,
430 nsIURI *aBaseURI,
431 nsIURI **aResult)
432 {
433 *aResult = nullptr;
434 nsresult rv;
436 DataInfo* info = GetDataInfo(aSpec);
438 nsRefPtr<nsHostObjectURI> uri =
439 new nsHostObjectURI(info ? info->mPrincipal.get() : nullptr);
441 rv = uri->SetSpec(aSpec);
442 NS_ENSURE_SUCCESS(rv, rv);
444 NS_TryToSetImmutable(uri);
445 uri.forget(aResult);
447 return NS_OK;
448 }
450 NS_IMETHODIMP
451 nsHostObjectProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
452 {
453 *result = nullptr;
455 nsCString spec;
456 uri->GetSpec(spec);
458 DataInfo* info = GetDataInfo(spec);
460 if (!info) {
461 return NS_ERROR_DOM_BAD_URI;
462 }
463 nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(info->mObject);
464 if (!blob) {
465 return NS_ERROR_DOM_BAD_URI;
466 }
468 #ifdef DEBUG
469 {
470 nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(uri);
471 nsCOMPtr<nsIPrincipal> principal;
472 uriPrinc->GetPrincipal(getter_AddRefs(principal));
473 NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!");
474 }
475 #endif
477 nsCOMPtr<nsIInputStream> stream;
478 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream));
479 NS_ENSURE_SUCCESS(rv, rv);
481 nsCOMPtr<nsIChannel> channel;
482 rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
483 uri,
484 stream);
485 NS_ENSURE_SUCCESS(rv, rv);
487 nsCOMPtr<nsISupports> owner = do_QueryInterface(info->mPrincipal);
489 nsString type;
490 rv = blob->GetType(type);
491 NS_ENSURE_SUCCESS(rv, rv);
493 nsCOMPtr<nsIDOMFile> file = do_QueryInterface(info->mObject);
494 if (file) {
495 nsString filename;
496 rv = file->GetName(filename);
497 NS_ENSURE_SUCCESS(rv, rv);
498 channel->SetContentDispositionFilename(filename);
499 }
501 uint64_t size;
502 rv = blob->GetSize(&size);
503 NS_ENSURE_SUCCESS(rv, rv);
505 channel->SetOwner(owner);
506 channel->SetOriginalURI(uri);
507 channel->SetContentType(NS_ConvertUTF16toUTF8(type));
508 channel->SetContentLength(size);
510 channel.forget(result);
512 return NS_OK;
513 }
515 NS_IMETHODIMP
516 nsHostObjectProtocolHandler::AllowPort(int32_t port, const char *scheme,
517 bool *_retval)
518 {
519 // don't override anything.
520 *_retval = false;
521 return NS_OK;
522 }
524 NS_IMETHODIMP
525 nsBlobProtocolHandler::GetScheme(nsACString &result)
526 {
527 result.AssignLiteral(BLOBURI_SCHEME);
528 return NS_OK;
529 }
531 NS_IMETHODIMP
532 nsMediaStreamProtocolHandler::GetScheme(nsACString &result)
533 {
534 result.AssignLiteral(MEDIASTREAMURI_SCHEME);
535 return NS_OK;
536 }
538 NS_IMETHODIMP
539 nsMediaSourceProtocolHandler::GetScheme(nsACString &result)
540 {
541 result.AssignLiteral(MEDIASOURCEURI_SCHEME);
542 return NS_OK;
543 }
545 NS_IMETHODIMP
546 nsFontTableProtocolHandler::GetScheme(nsACString &result)
547 {
548 result.AssignLiteral(FONTTABLEURI_SCHEME);
549 return NS_OK;
550 }
552 nsresult
553 NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
554 {
555 NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs");
557 *aStream = nullptr;
559 nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(GetDataObject(aURI));
560 if (!blob) {
561 return NS_ERROR_DOM_BAD_URI;
562 }
564 return blob->GetInternalStream(aStream);
565 }
567 nsresult
568 NS_GetStreamForMediaStreamURI(nsIURI* aURI, nsIDOMMediaStream** aStream)
569 {
570 NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs");
572 *aStream = nullptr;
574 nsCOMPtr<nsIDOMMediaStream> stream = do_QueryInterface(GetDataObject(aURI));
575 if (!stream) {
576 return NS_ERROR_DOM_BAD_URI;
577 }
579 *aStream = stream;
580 NS_ADDREF(*aStream);
581 return NS_OK;
582 }
584 NS_IMETHODIMP
585 nsFontTableProtocolHandler::NewURI(const nsACString& aSpec,
586 const char *aCharset,
587 nsIURI *aBaseURI,
588 nsIURI **aResult)
589 {
590 nsRefPtr<nsIURI> uri;
592 // Either you got here via a ref or a fonttable: uri
593 if (aSpec.Length() && aSpec.CharAt(0) == '#') {
594 nsresult rv = aBaseURI->CloneIgnoringRef(getter_AddRefs(uri));
595 NS_ENSURE_SUCCESS(rv, rv);
597 uri->SetRef(aSpec);
598 } else {
599 // Relative URIs (other than #ref) are not meaningful within the
600 // fonttable: scheme.
601 // If aSpec is a relative URI -other- than a bare #ref,
602 // this will leave uri empty, and we'll return a failure code below.
603 uri = new nsSimpleURI();
604 uri->SetSpec(aSpec);
605 }
607 bool schemeIs;
608 if (NS_FAILED(uri->SchemeIs(FONTTABLEURI_SCHEME, &schemeIs)) || !schemeIs) {
609 NS_WARNING("Non-fonttable spec in nsFontTableProtocolHander");
610 return NS_ERROR_NOT_AVAILABLE;
611 }
613 uri.forget(aResult);
614 return NS_OK;
615 }
617 nsresult
618 NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource)
619 {
620 NS_ASSERTION(IsMediaSourceURI(aURI), "Only call this with mediasource URIs");
622 *aSource = nullptr;
624 nsCOMPtr<mozilla::dom::MediaSource> source = do_QueryInterface(GetDataObject(aURI));
625 if (!source) {
626 return NS_ERROR_DOM_BAD_URI;
627 }
629 source.forget(aSource);
630 return NS_OK;
631 }