1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et 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/. */
8 #include "nsJARChannel.h"
9 #include "nsJARProtocolHandler.h"
10 #include "nsMimeTypes.h"
11 #include "nsNetUtil.h"
13 #include "nsContentUtils.h"
14 #include "nsProxyRelease.h"
15 #include "nsContentSecurityManager.h"
16 #include "nsComponentManagerUtils.h"
18 #include "nsIFileURL.h"
19 #include "nsIURIMutator.h"
21 #include "mozilla/BasePrincipal.h"
22 #include "mozilla/ErrorNames.h"
23 #include "mozilla/IntegerPrintfMacros.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/StaticPrefs_network.h"
27 #include "mozilla/Telemetry.h"
28 #include "mozilla/TelemetryComms.h"
29 #include "private/pprio.h"
30 #include "nsInputStreamPump.h"
31 #include "nsThreadUtils.h"
32 #include "nsJARProtocolHandler.h"
34 using namespace mozilla
;
35 using namespace mozilla::net
;
37 static NS_DEFINE_CID(kZipReaderCID
, NS_ZIPREADER_CID
);
39 // the entry for a directory will either be empty (in the case of the
40 // top-level directory) or will end with a slash
41 #define ENTRY_IS_DIRECTORY(_entry) \
42 ((_entry).IsEmpty() || '/' == (_entry).Last())
44 //-----------------------------------------------------------------------------
47 // set MOZ_LOG=nsJarProtocol:5
49 static LazyLogModule
gJarProtocolLog("nsJarProtocol");
51 // Ignore any LOG macro that we inherit from arbitrary headers. (We define our
52 // own LOG macro below.)
60 #define LOG(args) MOZ_LOG(gJarProtocolLog, mozilla::LogLevel::Debug, args)
61 #define LOG_ENABLED() MOZ_LOG_TEST(gJarProtocolLog, mozilla::LogLevel::Debug)
63 //-----------------------------------------------------------------------------
66 // this class allows us to do some extra work on the stream transport thread.
67 //-----------------------------------------------------------------------------
69 class nsJARInputThunk
: public nsIInputStream
{
71 NS_DECL_THREADSAFE_ISUPPORTS
72 NS_DECL_NSIINPUTSTREAM
74 nsJARInputThunk(nsIZipReader
* zipReader
, const nsACString
& jarEntry
,
76 : mUsingJarCache(usingJarCache
),
77 mJarReader(zipReader
),
80 MOZ_DIAGNOSTIC_ASSERT(zipReader
, "zipReader must not be null");
83 int64_t GetContentLength() { return mContentLength
; }
88 virtual ~nsJARInputThunk() { Close(); }
91 nsCOMPtr
<nsIZipReader
> mJarReader
;
92 nsCOMPtr
<nsIInputStream
> mJarStream
;
94 int64_t mContentLength
;
97 NS_IMPL_ISUPPORTS(nsJARInputThunk
, nsIInputStream
)
99 nsresult
nsJARInputThunk::Init() {
101 return NS_ERROR_INVALID_ARG
;
104 mJarReader
->GetInputStream(mJarEntry
, getter_AddRefs(mJarStream
));
109 // ask the JarStream for the content length
111 rv
= mJarStream
->Available((uint64_t*)&avail
);
112 if (NS_FAILED(rv
)) return rv
;
114 mContentLength
= avail
< INT64_MAX
? (int64_t)avail
: -1;
120 nsJARInputThunk::Close() {
123 if (mJarStream
) rv
= mJarStream
->Close();
125 if (!mUsingJarCache
&& mJarReader
) mJarReader
->Close();
127 mJarReader
= nullptr;
133 nsJARInputThunk::Available(uint64_t* avail
) {
134 return mJarStream
->Available(avail
);
138 nsJARInputThunk::StreamStatus() { return mJarStream
->StreamStatus(); }
141 nsJARInputThunk::Read(char* buf
, uint32_t count
, uint32_t* countRead
) {
142 return mJarStream
->Read(buf
, count
, countRead
);
146 nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer
, void* closure
,
147 uint32_t count
, uint32_t* countRead
) {
148 // stream transport does only calls Read()
149 return NS_ERROR_NOT_IMPLEMENTED
;
153 nsJARInputThunk::IsNonBlocking(bool* nonBlocking
) {
154 *nonBlocking
= false;
158 //-----------------------------------------------------------------------------
160 //-----------------------------------------------------------------------------
162 nsJARChannel::nsJARChannel()
166 mLoadFlags(LOAD_NORMAL
),
171 LOG(("nsJARChannel::nsJARChannel [this=%p]\n", this));
172 // hold an owning reference to the jar handler
173 mJarHandler
= gJarHandler
;
176 nsJARChannel::~nsJARChannel() {
177 LOG(("nsJARChannel::~nsJARChannel [this=%p]\n", this));
178 if (NS_IsMainThread()) {
182 // Proxy release the following members to main thread.
183 NS_ReleaseOnMainThread("nsJARChannel::mLoadInfo", mLoadInfo
.forget());
184 NS_ReleaseOnMainThread("nsJARChannel::mCallbacks", mCallbacks
.forget());
185 NS_ReleaseOnMainThread("nsJARChannel::mProgressSink", mProgressSink
.forget());
186 NS_ReleaseOnMainThread("nsJARChannel::mLoadGroup", mLoadGroup
.forget());
187 NS_ReleaseOnMainThread("nsJARChannel::mListener", mListener
.forget());
190 NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel
, nsHashPropertyBag
, nsIRequest
,
191 nsIChannel
, nsIStreamListener
, nsIRequestObserver
,
192 nsIThreadRetargetableRequest
,
193 nsIThreadRetargetableStreamListener
, nsIJARChannel
)
195 nsresult
nsJARChannel::Init(nsIURI
* uri
) {
196 LOG(("nsJARChannel::Init [this=%p]\n", this));
199 mWorker
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &rv
);
204 mJarURI
= do_QueryInterface(uri
, &rv
);
205 if (NS_FAILED(rv
)) return rv
;
207 mOriginalURI
= mJarURI
;
209 // Prevent loading jar:javascript URIs (see bug 290982).
210 nsCOMPtr
<nsIURI
> innerURI
;
211 rv
= mJarURI
->GetJARFile(getter_AddRefs(innerURI
));
216 if (innerURI
->SchemeIs("javascript")) {
217 NS_WARNING("blocking jar:javascript:");
218 return NS_ERROR_INVALID_ARG
;
221 mJarURI
->GetSpec(mSpec
);
225 nsresult
nsJARChannel::CreateJarInput(nsIZipReaderCache
* jarCache
,
226 nsJARInputThunk
** resultInput
) {
227 LOG(("nsJARChannel::CreateJarInput [this=%p]\n", this));
228 MOZ_ASSERT(resultInput
);
229 MOZ_ASSERT(mJarFile
);
231 // important to pass a clone of the file since the nsIFile impl is not
232 // necessarily MT-safe
233 nsCOMPtr
<nsIFile
> clonedFile
;
236 rv
= mJarFile
->Clone(getter_AddRefs(clonedFile
));
237 if (NS_FAILED(rv
)) return rv
;
240 nsCOMPtr
<nsIZipReader
> reader
;
241 if (mPreCachedJarReader
) {
242 reader
= mPreCachedJarReader
;
243 } else if (jarCache
) {
244 if (mInnerJarEntry
.IsEmpty())
245 rv
= jarCache
->GetZip(clonedFile
, getter_AddRefs(reader
));
247 rv
= jarCache
->GetInnerZip(clonedFile
, mInnerJarEntry
,
248 getter_AddRefs(reader
));
250 // create an uncached jar reader
251 nsCOMPtr
<nsIZipReader
> outerReader
= do_CreateInstance(kZipReaderCID
, &rv
);
252 if (NS_FAILED(rv
)) return rv
;
254 rv
= outerReader
->Open(clonedFile
);
255 if (NS_FAILED(rv
)) return rv
;
257 if (mInnerJarEntry
.IsEmpty())
258 reader
= outerReader
;
260 reader
= do_CreateInstance(kZipReaderCID
, &rv
);
261 if (NS_FAILED(rv
)) return rv
;
263 rv
= reader
->OpenInner(outerReader
, mInnerJarEntry
);
266 if (NS_FAILED(rv
)) return rv
;
268 RefPtr
<nsJARInputThunk
> input
=
269 new nsJARInputThunk(reader
, mJarEntry
, jarCache
!= nullptr);
275 // Make GetContentLength meaningful
276 mContentLength
= input
->GetContentLength();
278 input
.forget(resultInput
);
282 nsresult
nsJARChannel::LookupFile() {
283 LOG(("nsJARChannel::LookupFile [this=%p %s]\n", this, mSpec
.get()));
285 if (mJarFile
) return NS_OK
;
289 rv
= mJarURI
->GetJARFile(getter_AddRefs(mJarBaseURI
));
290 if (NS_FAILED(rv
)) return rv
;
292 rv
= mJarURI
->GetJAREntry(mJarEntry
);
293 if (NS_FAILED(rv
)) return rv
;
295 // The name of the JAR entry must not contain URL-escaped characters:
296 // we're moving from URL domain to a filename domain here. nsStandardURL
297 // does basic escaping by default, which breaks reading zipped files which
298 // have e.g. spaces in their filenames.
299 NS_UnescapeURL(mJarEntry
);
301 if (mJarFileOverride
) {
302 mJarFile
= mJarFileOverride
;
303 LOG(("nsJARChannel::LookupFile [this=%p] Overriding mJarFile\n", this));
307 // try to get a nsIFile directly from the url, which will often succeed.
309 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(mJarBaseURI
);
310 if (fileURL
) fileURL
->GetFile(getter_AddRefs(mJarFile
));
313 // try to handle a nested jar
315 nsCOMPtr
<nsIJARURI
> jarURI
= do_QueryInterface(mJarBaseURI
);
317 nsCOMPtr
<nsIFileURL
> fileURL
;
318 nsCOMPtr
<nsIURI
> innerJarURI
;
319 rv
= jarURI
->GetJARFile(getter_AddRefs(innerJarURI
));
320 if (NS_SUCCEEDED(rv
)) fileURL
= do_QueryInterface(innerJarURI
);
322 fileURL
->GetFile(getter_AddRefs(mJarFile
));
323 jarURI
->GetJAREntry(mInnerJarEntry
);
331 nsresult
CreateLocalJarInput(nsIZipReaderCache
* aJarCache
, nsIFile
* aFile
,
332 const nsACString
& aInnerJarEntry
,
333 const nsACString
& aJarEntry
,
334 nsJARInputThunk
** aResultInput
) {
335 LOG(("nsJARChannel::CreateLocalJarInput [aJarCache=%p, %s, %s]\n", aJarCache
,
336 PromiseFlatCString(aInnerJarEntry
).get(),
337 PromiseFlatCString(aJarEntry
).get()));
339 MOZ_ASSERT(!NS_IsMainThread());
340 MOZ_ASSERT(aJarCache
);
341 MOZ_ASSERT(aResultInput
);
345 nsCOMPtr
<nsIZipReader
> reader
;
346 if (aInnerJarEntry
.IsEmpty()) {
347 rv
= aJarCache
->GetZip(aFile
, getter_AddRefs(reader
));
349 rv
= aJarCache
->GetInnerZip(aFile
, aInnerJarEntry
, getter_AddRefs(reader
));
351 if (NS_WARN_IF(NS_FAILED(rv
))) {
355 RefPtr
<nsJARInputThunk
> input
=
356 new nsJARInputThunk(reader
, aJarEntry
, aJarCache
!= nullptr);
362 input
.forget(aResultInput
);
366 nsresult
nsJARChannel::OpenLocalFile() {
367 LOG(("nsJARChannel::OpenLocalFile [this=%p]\n", this));
369 MOZ_ASSERT(NS_IsMainThread());
372 MOZ_ASSERT(mIsPending
);
373 MOZ_ASSERT(mJarFile
);
377 // Set mLoadGroup and mOpened before AsyncOpen return, and set back if
378 // if failed when callback.
380 mLoadGroup
->AddRequest(this, nullptr);
384 if (mPreCachedJarReader
|| !mEnableOMT
) {
385 RefPtr
<nsJARInputThunk
> input
;
386 rv
= CreateJarInput(gJarHandler
->JarCache(), getter_AddRefs(input
));
387 if (NS_WARN_IF(NS_FAILED(rv
))) {
388 return OnOpenLocalFileComplete(rv
, true);
390 return ContinueOpenLocalFile(input
, true);
393 nsCOMPtr
<nsIZipReaderCache
> jarCache
= gJarHandler
->JarCache();
394 if (NS_WARN_IF(!jarCache
)) {
395 return NS_ERROR_UNEXPECTED
;
398 nsCOMPtr
<nsIFile
> clonedFile
;
399 rv
= mJarFile
->Clone(getter_AddRefs(clonedFile
));
400 if (NS_WARN_IF(NS_FAILED(rv
))) {
404 nsAutoCString
jarEntry(mJarEntry
);
405 nsAutoCString
innerJarEntry(mInnerJarEntry
);
407 RefPtr
<nsJARChannel
> self
= this;
408 return mWorker
->Dispatch(NS_NewRunnableFunction(
409 "nsJARChannel::OpenLocalFile",
410 [self
, jarCache
, clonedFile
, jarEntry
, innerJarEntry
]() mutable {
411 RefPtr
<nsJARInputThunk
> input
;
412 nsresult rv
= CreateLocalJarInput(jarCache
, clonedFile
, innerJarEntry
,
413 jarEntry
, getter_AddRefs(input
));
415 nsCOMPtr
<nsIRunnable
> target
;
416 if (NS_SUCCEEDED(rv
)) {
417 target
= NewRunnableMethod
<RefPtr
<nsJARInputThunk
>, bool>(
418 "nsJARChannel::ContinueOpenLocalFile", self
,
419 &nsJARChannel::ContinueOpenLocalFile
, input
, false);
421 target
= NewRunnableMethod
<nsresult
, bool>(
422 "nsJARChannel::OnOpenLocalFileComplete", self
,
423 &nsJARChannel::OnOpenLocalFileComplete
, rv
, false);
426 // nsJARChannel must be release on main thread, and sometimes
427 // this still hold nsJARChannel after dispatched.
430 NS_DispatchToMainThread(target
.forget());
434 nsresult
nsJARChannel::ContinueOpenLocalFile(nsJARInputThunk
* aInput
,
436 LOG(("nsJARChannel::ContinueOpenLocalFile [this=%p %p]\n", this, aInput
));
438 MOZ_ASSERT(NS_IsMainThread());
439 MOZ_ASSERT(mIsPending
);
441 // Make GetContentLength meaningful
442 mContentLength
= aInput
->GetContentLength();
445 RefPtr
<nsJARInputThunk
> input
= aInput
;
446 // Create input stream pump and call AsyncRead as a block.
447 rv
= NS_NewInputStreamPump(getter_AddRefs(mPump
), input
.forget());
448 if (NS_SUCCEEDED(rv
)) {
449 rv
= mPump
->AsyncRead(this);
452 if (NS_SUCCEEDED(rv
)) {
453 rv
= CheckPendingEvents();
456 return OnOpenLocalFileComplete(rv
, aIsSyncCall
);
459 nsresult
nsJARChannel::OnOpenLocalFileComplete(nsresult aResult
,
461 LOG(("nsJARChannel::OnOpenLocalFileComplete [this=%p %08x]\n", this,
462 static_cast<uint32_t>(aResult
)));
464 MOZ_ASSERT(NS_IsMainThread());
465 MOZ_ASSERT(mIsPending
);
467 if (NS_FAILED(aResult
)) {
468 if (aResult
== NS_ERROR_FILE_NOT_FOUND
) {
469 CheckForBrokenChromeURL(mLoadInfo
, mOriginalURI
);
472 NotifyError(aResult
);
476 mLoadGroup
->RemoveRequest(this, nullptr, aResult
);
482 mCallbacks
= nullptr;
483 mProgressSink
= nullptr;
491 nsresult
nsJARChannel::CheckPendingEvents() {
492 MOZ_ASSERT(NS_IsMainThread());
493 MOZ_ASSERT(mIsPending
);
498 uint32_t suspendCount
= mPendingEvent
.suspendCount
;
499 while (suspendCount
--) {
500 if (NS_WARN_IF(NS_FAILED(rv
= mPump
->Suspend()))) {
505 if (mPendingEvent
.isCanceled
) {
506 if (NS_WARN_IF(NS_FAILED(rv
= mPump
->Cancel(mStatus
)))) {
509 mPendingEvent
.isCanceled
= false;
515 void nsJARChannel::NotifyError(nsresult aError
) {
516 MOZ_ASSERT(NS_FAILED(aError
));
520 OnStartRequest(nullptr);
521 OnStopRequest(nullptr, aError
);
524 void nsJARChannel::FireOnProgress(uint64_t aProgress
) {
525 MOZ_ASSERT(NS_IsMainThread());
526 MOZ_ASSERT(mProgressSink
);
528 mProgressSink
->OnProgress(this, aProgress
, mContentLength
);
531 //-----------------------------------------------------------------------------
533 //-----------------------------------------------------------------------------
536 nsJARChannel::GetName(nsACString
& result
) { return mJarURI
->GetSpec(result
); }
539 nsJARChannel::IsPending(bool* result
) {
540 *result
= mIsPending
;
545 nsJARChannel::GetStatus(nsresult
* status
) {
546 if (mPump
&& NS_SUCCEEDED(mStatus
))
547 mPump
->GetStatus(status
);
553 NS_IMETHODIMP
nsJARChannel::SetCanceledReason(const nsACString
& aReason
) {
554 return SetCanceledReasonImpl(aReason
);
557 NS_IMETHODIMP
nsJARChannel::GetCanceledReason(nsACString
& aReason
) {
558 return GetCanceledReasonImpl(aReason
);
561 NS_IMETHODIMP
nsJARChannel::CancelWithReason(nsresult aStatus
,
562 const nsACString
& aReason
) {
563 return CancelWithReasonImpl(aStatus
, aReason
);
567 nsJARChannel::Cancel(nsresult status
) {
571 return mPump
->Cancel(status
);
575 mPendingEvent
.isCanceled
= true;
582 nsJARChannel::GetCanceled(bool* aCanceled
) {
583 *aCanceled
= mCanceled
;
588 nsJARChannel::Suspend() {
589 ++mPendingEvent
.suspendCount
;
592 return mPump
->Suspend();
599 nsJARChannel::Resume() {
600 if (NS_WARN_IF(mPendingEvent
.suspendCount
== 0)) {
601 return NS_ERROR_UNEXPECTED
;
603 --mPendingEvent
.suspendCount
;
606 return mPump
->Resume();
613 nsJARChannel::GetLoadFlags(nsLoadFlags
* aLoadFlags
) {
614 *aLoadFlags
= mLoadFlags
;
619 nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags
) {
620 mLoadFlags
= aLoadFlags
;
625 nsJARChannel::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
626 return GetTRRModeImpl(aTRRMode
);
630 nsJARChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
631 return SetTRRModeImpl(aTRRMode
);
635 nsJARChannel::GetIsDocument(bool* aIsDocument
) {
636 return NS_GetIsDocumentChannel(this, aIsDocument
);
640 nsJARChannel::GetLoadGroup(nsILoadGroup
** aLoadGroup
) {
641 NS_IF_ADDREF(*aLoadGroup
= mLoadGroup
);
646 nsJARChannel::SetLoadGroup(nsILoadGroup
* aLoadGroup
) {
647 mLoadGroup
= aLoadGroup
;
651 //-----------------------------------------------------------------------------
653 //-----------------------------------------------------------------------------
656 nsJARChannel::GetOriginalURI(nsIURI
** aURI
) {
657 *aURI
= mOriginalURI
;
663 nsJARChannel::SetOriginalURI(nsIURI
* aURI
) {
664 NS_ENSURE_ARG_POINTER(aURI
);
670 nsJARChannel::GetURI(nsIURI
** aURI
) {
671 NS_IF_ADDREF(*aURI
= mJarURI
);
677 nsJARChannel::GetOwner(nsISupports
** aOwner
) {
678 // JAR signatures are not processed to avoid main-thread network I/O (bug
681 NS_IF_ADDREF(*aOwner
);
686 nsJARChannel::SetOwner(nsISupports
* aOwner
) {
692 nsJARChannel::GetLoadInfo(nsILoadInfo
** aLoadInfo
) {
693 NS_IF_ADDREF(*aLoadInfo
= mLoadInfo
);
698 nsJARChannel::SetLoadInfo(nsILoadInfo
* aLoadInfo
) {
699 MOZ_RELEASE_ASSERT(aLoadInfo
, "loadinfo can't be null");
700 mLoadInfo
= aLoadInfo
;
705 nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor
** aCallbacks
) {
706 NS_IF_ADDREF(*aCallbacks
= mCallbacks
);
711 nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor
* aCallbacks
) {
712 mCallbacks
= aCallbacks
;
717 nsJARChannel::GetSecurityInfo(nsITransportSecurityInfo
** aSecurityInfo
) {
718 MOZ_ASSERT(aSecurityInfo
, "Null out param");
719 *aSecurityInfo
= nullptr;
723 bool nsJARChannel::GetContentTypeGuess(nsACString
& aResult
) const {
724 const char *ext
= nullptr, *fileName
= mJarEntry
.get();
725 int32_t len
= mJarEntry
.Length();
727 // check if we're displaying a directory
728 // mJarEntry will be empty if we're trying to display
729 // the topmost directory in a zip, e.g. jar:foo.zip!/
730 if (ENTRY_IS_DIRECTORY(mJarEntry
)) {
731 aResult
.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT
);
735 // Not a directory, take a guess by its extension
736 for (int32_t i
= len
- 1; i
>= 0; i
--) {
737 if (fileName
[i
] == '.') {
738 ext
= &fileName
[i
+ 1];
745 nsIMIMEService
* mimeServ
= gJarHandler
->MimeService();
749 mimeServ
->GetTypeFromExtension(nsDependentCString(ext
), aResult
);
750 return !aResult
.IsEmpty();
754 nsJARChannel::GetContentType(nsACString
& aResult
) {
755 // If the Jar file has not been open yet,
756 // We return application/x-unknown-content-type
758 aResult
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
762 aResult
= mContentType
;
767 nsJARChannel::SetContentType(const nsACString
& aContentType
) {
768 // We behave like HTTP channels (treat this as a hint if called before open,
769 // and override the charset if called after open).
770 // mContentCharset is unchanged if not parsed
771 NS_ParseResponseContentType(aContentType
, mContentType
, mContentCharset
);
776 nsJARChannel::GetContentCharset(nsACString
& aContentCharset
) {
777 // If someone gives us a charset hint we should just use that charset.
778 // So we don't care when this is being called.
779 aContentCharset
= mContentCharset
;
784 nsJARChannel::SetContentCharset(const nsACString
& aContentCharset
) {
785 mContentCharset
= aContentCharset
;
790 nsJARChannel::GetContentDisposition(uint32_t* aContentDisposition
) {
791 return NS_ERROR_NOT_AVAILABLE
;
795 nsJARChannel::SetContentDisposition(uint32_t aContentDisposition
) {
796 return NS_ERROR_NOT_AVAILABLE
;
800 nsJARChannel::GetContentDispositionFilename(
801 nsAString
& aContentDispositionFilename
) {
802 return NS_ERROR_NOT_AVAILABLE
;
806 nsJARChannel::SetContentDispositionFilename(
807 const nsAString
& aContentDispositionFilename
) {
808 return NS_ERROR_NOT_AVAILABLE
;
812 nsJARChannel::GetContentDispositionHeader(
813 nsACString
& aContentDispositionHeader
) {
814 return NS_ERROR_NOT_AVAILABLE
;
818 nsJARChannel::GetContentLength(int64_t* result
) {
819 *result
= mContentLength
;
824 nsJARChannel::SetContentLength(int64_t aContentLength
) {
825 // XXX does this really make any sense at all?
826 mContentLength
= aContentLength
;
830 static void RecordZeroLengthEvent(bool aIsSync
, const nsCString
& aSpec
,
831 nsresult aStatus
, bool aCanceled
,
832 nsILoadInfo
* aLoadInfo
) {
833 if (!StaticPrefs::network_jar_record_failure_reason()) {
838 bool shouldSkipCheckForBrokenURLOrZeroSized
;
839 MOZ_ALWAYS_SUCCEEDS(aLoadInfo
->GetShouldSkipCheckForBrokenURLOrZeroSized(
840 &shouldSkipCheckForBrokenURLOrZeroSized
));
841 if (shouldSkipCheckForBrokenURLOrZeroSized
) {
846 // The event can only hold 80 characters.
847 // We only save the file name and path inside the jar.
848 auto findFilenameStart
= [](const nsCString
& aSpec
) -> uint32_t {
849 int32_t pos
= aSpec
.Find("!/");
850 if (pos
== kNotFound
) {
851 MOZ_ASSERT(false, "This should not happen");
854 int32_t from
= aSpec
.RFindChar('/', pos
);
855 if (from
== kNotFound
) {
856 MOZ_ASSERT(false, "This should not happen");
859 // Skip over the slash
864 // If for some reason we are unable to extract the filename we report the
865 // entire string, or 80 characters of it, to make sure we don't miss any
867 uint32_t from
= findFilenameStart(aSpec
);
868 const auto fileName
= Substring(aSpec
, from
);
870 nsAutoCString errorCString
;
871 mozilla::GetErrorName(aStatus
, errorCString
);
873 // To test this telemetry we use a zip file and we want to make
874 // sure don't filter it out.
875 bool isTest
= fileName
.Find("test_empty_file.zip!") != -1;
876 bool isOmniJa
= StringBeginsWith(fileName
, "omni.ja!"_ns
);
878 Telemetry::SetEventRecordingEnabled("zero_byte_load"_ns
, true);
879 Telemetry::EventID eventType
= Telemetry::EventID::Zero_byte_load_Load_Others
;
880 if (StringEndsWith(fileName
, ".ftl"_ns
)) {
881 eventType
= Telemetry::EventID::Zero_byte_load_Load_Ftl
;
882 } else if (StringEndsWith(fileName
, ".dtd"_ns
)) {
883 // We're going to skip reporting telemetry on res DTDs.
884 // See Bug 1693711 for investigation into those empty loads.
885 if (!isTest
&& StringBeginsWith(fileName
, "omni.ja!/res/dtd"_ns
)) {
889 eventType
= Telemetry::EventID::Zero_byte_load_Load_Dtd
;
890 } else if (StringEndsWith(fileName
, ".properties"_ns
)) {
891 eventType
= Telemetry::EventID::Zero_byte_load_Load_Properties
;
892 } else if (StringEndsWith(fileName
, ".js"_ns
) ||
893 StringEndsWith(fileName
, ".jsm"_ns
) ||
894 StringEndsWith(fileName
, ".mjs"_ns
)) {
895 // We're going to skip reporting telemetry on JS loads
896 // coming not from omni.ja.
897 // See Bug 1693711 for investigation into those empty loads.
898 if (!isTest
&& !isOmniJa
) {
901 eventType
= Telemetry::EventID::Zero_byte_load_Load_Js
;
902 } else if (StringEndsWith(fileName
, ".xml"_ns
)) {
903 eventType
= Telemetry::EventID::Zero_byte_load_Load_Xml
;
904 } else if (StringEndsWith(fileName
, ".xhtml"_ns
)) {
905 // This error seems to be very common and is not strongly
906 // correlated to YSOD.
907 if (aStatus
== NS_ERROR_PARSED_DATA_CACHED
) {
911 // We're not investigating YSODs from extensions for now.
916 eventType
= Telemetry::EventID::Zero_byte_load_Load_Xhtml
;
917 } else if (StringEndsWith(fileName
, ".css"_ns
)) {
918 // Bug 1702937: Filter out svg+'css'+'png'/NS_BINDING_ABORTED combo.
919 if (aStatus
== NS_BINDING_ABORTED
) {
923 // Bug 1702937: Filter css/NS_ERROR_CORRUPTED_CONTENT that is coming from
924 // outside of omni.ja.
925 if (!isOmniJa
&& aStatus
== NS_ERROR_CORRUPTED_CONTENT
) {
928 eventType
= Telemetry::EventID::Zero_byte_load_Load_Css
;
929 } else if (StringEndsWith(fileName
, ".json"_ns
)) {
930 eventType
= Telemetry::EventID::Zero_byte_load_Load_Json
;
931 } else if (StringEndsWith(fileName
, ".html"_ns
)) {
932 eventType
= Telemetry::EventID::Zero_byte_load_Load_Html
;
933 // See bug 1695560. Filter out non-omni.ja HTML.
938 // See bug 1695560. "activity-stream-noscripts.html" with NS_ERROR_FAILURE
940 if (fileName
.EqualsLiteral("omni.ja!/chrome/browser/res/activity-stream/"
941 "prerendered/activity-stream-noscripts.html") &&
942 aStatus
== NS_ERROR_FAILURE
) {
945 } else if (StringEndsWith(fileName
, ".png"_ns
)) {
946 eventType
= Telemetry::EventID::Zero_byte_load_Load_Png
;
948 // Bug 1702937: Filter out svg+'css'+'png'/NS_BINDING_ABORTED combo.
949 if (!isOmniJa
|| aStatus
== NS_BINDING_ABORTED
) {
952 } else if (StringEndsWith(fileName
, ".svg"_ns
)) {
953 eventType
= Telemetry::EventID::Zero_byte_load_Load_Svg
;
955 // Bug 1702937: Filter out svg+'css'+'png'/NS_BINDING_ABORTED combo.
956 if (!isOmniJa
|| aStatus
== NS_BINDING_ABORTED
) {
961 // We're going to, for now, filter out `other` category.
962 // See Bug 1693711 for investigation into those empty loads.
963 // Bug 1702937: Filter other/*.ico/NS_BINDING_ABORTED.
964 if (!isTest
&& eventType
== Telemetry::EventID::Zero_byte_load_Load_Others
&&
965 (!isOmniJa
|| (aStatus
== NS_BINDING_ABORTED
&&
966 StringEndsWith(fileName
, ".ico"_ns
)))) {
970 // FTL uses I/O to test for file presence, so we get
971 // a high volume of events from it, but it is not erronous.
972 // Also, Fluent is resilient to empty loads, so even if any
973 // of the errors are real errors, they don't cause YSOD.
974 // We can investigate them separately.
976 (eventType
== Telemetry::EventID::Zero_byte_load_Load_Ftl
||
977 eventType
== Telemetry::EventID::Zero_byte_load_Load_Json
) &&
978 aStatus
== NS_ERROR_FILE_NOT_FOUND
) {
982 // See bug 1695560. "search-extensions/google/favicon.ico" with
983 // NS_BINDING_ABORTED is filtered out.
984 if (fileName
.EqualsLiteral(
985 "omni.ja!/chrome/browser/search-extensions/google/favicon.ico") &&
986 aStatus
== NS_BINDING_ABORTED
) {
990 // See bug 1695560. "update.locale" with
991 // NS_ERROR_FILE_NOT_FOUND is filtered out.
992 if (fileName
.EqualsLiteral("omni.ja!/update.locale") &&
993 aStatus
== NS_ERROR_FILE_NOT_FOUND
) {
997 auto res
= CopyableTArray
<Telemetry::EventExtraEntry
>{};
1000 Telemetry::EventExtraEntry
{"sync"_ns
, aIsSync
? "true"_ns
: "false"_ns
});
1002 Telemetry::EventExtraEntry
{"file_name"_ns
, nsCString(fileName
)});
1003 res
.AppendElement(Telemetry::EventExtraEntry
{"status"_ns
, errorCString
});
1004 res
.AppendElement(Telemetry::EventExtraEntry
{
1005 "cancelled"_ns
, aCanceled
? "true"_ns
: "false"_ns
});
1006 Telemetry::RecordEvent(eventType
, Nothing
{}, Some(res
));
1010 nsJARChannel::Open(nsIInputStream
** aStream
) {
1011 LOG(("nsJARChannel::Open [this=%p]\n", this));
1012 nsCOMPtr
<nsIStreamListener
> listener
;
1014 nsContentSecurityManager::doContentSecurityCheck(this, listener
);
1015 NS_ENSURE_SUCCESS(rv
, rv
);
1017 auto recordEvent
= MakeScopeExit([&] {
1018 if (mContentLength
<= 0 || NS_FAILED(rv
)) {
1019 RecordZeroLengthEvent(true, mSpec
, rv
, mCanceled
, mLoadInfo
);
1023 LOG(("nsJARChannel::Open [this=%p]\n", this));
1025 NS_ENSURE_TRUE(!mOpened
, NS_ERROR_IN_PROGRESS
);
1026 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
1031 if (NS_FAILED(rv
)) return rv
;
1033 // If mJarFile was not set by LookupFile, we can't open a channel.
1035 MOZ_ASSERT_UNREACHABLE("only file-backed jars are supported");
1036 return NS_ERROR_NOT_IMPLEMENTED
;
1039 RefPtr
<nsJARInputThunk
> input
;
1040 rv
= CreateJarInput(gJarHandler
->JarCache(), getter_AddRefs(input
));
1041 if (NS_FAILED(rv
)) return rv
;
1043 input
.forget(aStream
);
1049 void nsJARChannel::SetOpened() {
1050 MOZ_ASSERT(!mOpened
, "Opening channel twice?");
1052 // Compute the content type now.
1053 if (!GetContentTypeGuess(mContentType
)) {
1054 mContentType
.Assign(UNKNOWN_CONTENT_TYPE
);
1059 nsJARChannel::AsyncOpen(nsIStreamListener
* aListener
) {
1060 LOG(("nsJARChannel::AsyncOpen [this=%p]\n", this));
1061 nsCOMPtr
<nsIStreamListener
> listener
= aListener
;
1063 nsContentSecurityManager::doContentSecurityCheck(this, listener
);
1064 if (NS_FAILED(rv
)) {
1066 mListener
= nullptr;
1067 mCallbacks
= nullptr;
1068 mProgressSink
= nullptr;
1072 LOG(("nsJARChannel::AsyncOpen [this=%p]\n", this));
1074 mLoadInfo
->GetSecurityMode() == 0 ||
1075 mLoadInfo
->GetInitialSecurityCheckDone() ||
1076 (mLoadInfo
->GetSecurityMode() ==
1077 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
&&
1078 mLoadInfo
->GetLoadingPrincipal() &&
1079 mLoadInfo
->GetLoadingPrincipal()->IsSystemPrincipal()),
1080 "security flags in loadInfo but doContentSecurityCheck() not called");
1082 NS_ENSURE_ARG_POINTER(listener
);
1083 NS_ENSURE_TRUE(!mOpened
, NS_ERROR_IN_PROGRESS
);
1084 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
1088 // Initialize mProgressSink
1089 NS_QueryNotificationCallbacks(mCallbacks
, mLoadGroup
, mProgressSink
);
1091 mListener
= listener
;
1095 if (NS_FAILED(rv
) || !mJarFile
) {
1096 // Not a local file...
1098 mListener
= nullptr;
1099 mCallbacks
= nullptr;
1100 mProgressSink
= nullptr;
1101 return mJarFile
? rv
: NS_ERROR_UNSAFE_CONTENT_TYPE
;
1104 rv
= OpenLocalFile();
1105 if (NS_FAILED(rv
)) {
1107 mListener
= nullptr;
1108 mCallbacks
= nullptr;
1109 mProgressSink
= nullptr;
1116 //-----------------------------------------------------------------------------
1118 //-----------------------------------------------------------------------------
1120 nsJARChannel::GetJarFile(nsIFile
** aFile
) {
1121 NS_IF_ADDREF(*aFile
= mJarFile
);
1126 nsJARChannel::SetJarFile(nsIFile
* aFile
) {
1128 return NS_ERROR_IN_PROGRESS
;
1130 mJarFileOverride
= aFile
;
1135 nsJARChannel::EnsureCached(bool* aIsCached
) {
1140 return NS_ERROR_ALREADY_OPENED
;
1143 if (mPreCachedJarReader
) {
1144 // We've already been called and found the JAR is cached
1149 nsCOMPtr
<nsIURI
> innerFileURI
;
1150 rv
= mJarURI
->GetJARFile(getter_AddRefs(innerFileURI
));
1151 NS_ENSURE_SUCCESS(rv
, rv
);
1153 nsCOMPtr
<nsIFileURL
> innerFileURL
= do_QueryInterface(innerFileURI
, &rv
);
1154 NS_ENSURE_SUCCESS(rv
, rv
);
1156 nsCOMPtr
<nsIFile
> jarFile
;
1157 rv
= innerFileURL
->GetFile(getter_AddRefs(jarFile
));
1158 NS_ENSURE_SUCCESS(rv
, rv
);
1160 nsCOMPtr
<nsIIOService
> ioService
= do_GetIOService(&rv
);
1161 NS_ENSURE_SUCCESS(rv
, rv
);
1163 nsCOMPtr
<nsIProtocolHandler
> handler
;
1164 rv
= ioService
->GetProtocolHandler("jar", getter_AddRefs(handler
));
1165 NS_ENSURE_SUCCESS(rv
, rv
);
1167 auto jarHandler
= static_cast<nsJARProtocolHandler
*>(handler
.get());
1168 MOZ_ASSERT(jarHandler
);
1170 nsIZipReaderCache
* jarCache
= jarHandler
->JarCache();
1172 rv
= jarCache
->GetZipIfCached(jarFile
, getter_AddRefs(mPreCachedJarReader
));
1173 if (rv
== NS_ERROR_CACHE_KEY_NOT_FOUND
) {
1176 NS_ENSURE_SUCCESS(rv
, rv
);
1183 nsJARChannel::GetZipEntry(nsIZipEntry
** aZipEntry
) {
1184 nsresult rv
= LookupFile();
1185 if (NS_FAILED(rv
)) return rv
;
1187 if (!mJarFile
) return NS_ERROR_NOT_AVAILABLE
;
1189 nsCOMPtr
<nsIZipReader
> reader
;
1190 rv
= gJarHandler
->JarCache()->GetZip(mJarFile
, getter_AddRefs(reader
));
1191 if (NS_FAILED(rv
)) return rv
;
1193 return reader
->GetEntry(mJarEntry
, aZipEntry
);
1196 //-----------------------------------------------------------------------------
1197 // nsIStreamListener
1198 //-----------------------------------------------------------------------------
1201 nsJARChannel::OnStartRequest(nsIRequest
* req
) {
1202 LOG(("nsJARChannel::OnStartRequest [this=%p %s]\n", this, mSpec
.get()));
1205 nsresult rv
= mListener
->OnStartRequest(this);
1206 if (NS_FAILED(rv
)) {
1210 // Restrict loadable content types.
1211 nsAutoCString contentType
;
1212 GetContentType(contentType
);
1213 auto contentPolicyType
= mLoadInfo
->GetExternalContentPolicyType();
1214 if (contentType
.Equals(APPLICATION_HTTP_INDEX_FORMAT
) &&
1215 contentPolicyType
!= ExtContentPolicy::TYPE_DOCUMENT
&&
1216 contentPolicyType
!= ExtContentPolicy::TYPE_FETCH
) {
1217 return NS_ERROR_CORRUPTED_CONTENT
;
1219 if (contentPolicyType
== ExtContentPolicy::TYPE_STYLESHEET
&&
1220 !contentType
.EqualsLiteral(TEXT_CSS
)) {
1221 return NS_ERROR_CORRUPTED_CONTENT
;
1223 if (contentPolicyType
== ExtContentPolicy::TYPE_SCRIPT
&&
1224 !nsContentUtils::IsJavascriptMIMEType(
1225 NS_ConvertUTF8toUTF16(contentType
))) {
1226 return NS_ERROR_CORRUPTED_CONTENT
;
1233 nsJARChannel::OnStopRequest(nsIRequest
* req
, nsresult status
) {
1234 LOG(("nsJARChannel::OnStopRequest [this=%p %s status=%" PRIx32
"]\n", this,
1235 mSpec
.get(), static_cast<uint32_t>(status
)));
1237 if (NS_SUCCEEDED(mStatus
)) mStatus
= status
;
1240 if (!mOnDataCalled
|| NS_FAILED(status
)) {
1241 RecordZeroLengthEvent(false, mSpec
, status
, mCanceled
, mLoadInfo
);
1244 mListener
->OnStopRequest(this, status
);
1245 mListener
= nullptr;
1248 if (mLoadGroup
) mLoadGroup
->RemoveRequest(this, nullptr, status
);
1254 // Drop notification callbacks to prevent cycles.
1255 mCallbacks
= nullptr;
1256 mProgressSink
= nullptr;
1258 #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
1260 // To deallocate file descriptor by RemoteOpenFileChild destructor.
1268 nsJARChannel::OnDataAvailable(nsIRequest
* req
, nsIInputStream
* stream
,
1269 uint64_t offset
, uint32_t count
) {
1270 LOG(("nsJARChannel::OnDataAvailable [this=%p %s]\n", this, mSpec
.get()));
1274 // don't send out OnDataAvailable notifications if we've been canceled.
1279 mOnDataCalled
= true;
1280 rv
= mListener
->OnDataAvailable(this, stream
, offset
, count
);
1282 // simply report progress here instead of hooking ourselves up as a
1283 // nsITransportEventSink implementation.
1284 // XXX do the 64-bit stuff for real
1285 if (mProgressSink
&& NS_SUCCEEDED(rv
)) {
1286 if (NS_IsMainThread()) {
1287 FireOnProgress(offset
+ count
);
1289 NS_DispatchToMainThread(NewRunnableMethod
<uint64_t>(
1290 "nsJARChannel::FireOnProgress", this, &nsJARChannel::FireOnProgress
,
1295 return rv
; // let the pump cancel on failure
1299 nsJARChannel::RetargetDeliveryTo(nsISerialEventTarget
* aEventTarget
) {
1300 MOZ_ASSERT(NS_IsMainThread());
1302 nsCOMPtr
<nsIThreadRetargetableRequest
> request
= do_QueryInterface(mRequest
);
1304 return NS_ERROR_NO_INTERFACE
;
1307 return request
->RetargetDeliveryTo(aEventTarget
);
1311 nsJARChannel::GetDeliveryTarget(nsISerialEventTarget
** aEventTarget
) {
1312 MOZ_ASSERT(NS_IsMainThread());
1314 nsCOMPtr
<nsIThreadRetargetableRequest
> request
= do_QueryInterface(mRequest
);
1316 return NS_ERROR_NO_INTERFACE
;
1319 return request
->GetDeliveryTarget(aEventTarget
);
1323 nsJARChannel::CheckListenerChain() {
1324 MOZ_ASSERT(NS_IsMainThread());
1326 nsCOMPtr
<nsIThreadRetargetableStreamListener
> listener
=
1327 do_QueryInterface(mListener
);
1329 return NS_ERROR_NO_INTERFACE
;
1332 return listener
->CheckListenerChain();
1336 nsJARChannel::OnDataFinished(nsresult aStatus
) {
1337 nsCOMPtr
<nsIThreadRetargetableStreamListener
> listener
=
1338 do_QueryInterface(mListener
);
1340 return listener
->OnDataFinished(aStatus
);