Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set sw=4 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/. */
7 #include "nsJAR.h"
8 #include "nsJARChannel.h"
9 #include "nsJARProtocolHandler.h"
10 #include "nsMimeTypes.h"
11 #include "nsNetUtil.h"
12 #include "nsEscape.h"
13 #include "nsIPrefService.h"
14 #include "nsIPrefBranch.h"
15 #include "nsIViewSourceChannel.h"
16 #include "nsChannelProperties.h"
18 #include "nsIScriptSecurityManager.h"
19 #include "nsIPrincipal.h"
20 #include "nsIFileURL.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/net/RemoteOpenFileChild.h"
24 #include "nsITabChild.h"
26 using namespace mozilla;
27 using namespace mozilla::net;
29 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
31 // the entry for a directory will either be empty (in the case of the
32 // top-level directory) or will end with a slash
33 #define ENTRY_IS_DIRECTORY(_entry) \
34 ((_entry).IsEmpty() || '/' == (_entry).Last())
36 //-----------------------------------------------------------------------------
38 // Ignore any LOG macro that we inherit from arbitrary headers. (We define our
39 // own LOG macro below.)
40 #ifdef LOG
41 #undef LOG
42 #endif
44 #if defined(PR_LOGGING)
45 //
46 // set NSPR_LOG_MODULES=nsJarProtocol:5
47 //
48 static PRLogModuleInfo *gJarProtocolLog = nullptr;
49 #endif
51 // If you ever want to define PR_FORCE_LOGGING in this file, see bug 545995
52 #define LOG(args) PR_LOG(gJarProtocolLog, PR_LOG_DEBUG, args)
53 #define LOG_ENABLED() PR_LOG_TEST(gJarProtocolLog, 4)
55 //-----------------------------------------------------------------------------
56 // nsJARInputThunk
57 //
58 // this class allows us to do some extra work on the stream transport thread.
59 //-----------------------------------------------------------------------------
61 class nsJARInputThunk : public nsIInputStream
62 {
63 public:
64 NS_DECL_THREADSAFE_ISUPPORTS
65 NS_DECL_NSIINPUTSTREAM
67 nsJARInputThunk(nsIZipReader *zipReader,
68 nsIURI* fullJarURI,
69 const nsACString &jarEntry,
70 bool usingJarCache)
71 : mUsingJarCache(usingJarCache)
72 , mJarReader(zipReader)
73 , mJarEntry(jarEntry)
74 , mContentLength(-1)
75 {
76 if (fullJarURI) {
77 #ifdef DEBUG
78 nsresult rv =
79 #endif
80 fullJarURI->GetAsciiSpec(mJarDirSpec);
81 NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail");
82 }
83 }
85 virtual ~nsJARInputThunk()
86 {
87 Close();
88 }
90 int64_t GetContentLength()
91 {
92 return mContentLength;
93 }
95 nsresult Init();
97 private:
99 bool mUsingJarCache;
100 nsCOMPtr<nsIZipReader> mJarReader;
101 nsCString mJarDirSpec;
102 nsCOMPtr<nsIInputStream> mJarStream;
103 nsCString mJarEntry;
104 int64_t mContentLength;
105 };
107 NS_IMPL_ISUPPORTS(nsJARInputThunk, nsIInputStream)
109 nsresult
110 nsJARInputThunk::Init()
111 {
112 nsresult rv;
113 if (ENTRY_IS_DIRECTORY(mJarEntry)) {
114 // A directory stream also needs the Spec of the FullJarURI
115 // because is included in the stream data itself.
117 NS_ENSURE_STATE(!mJarDirSpec.IsEmpty());
119 rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec,
120 mJarEntry,
121 getter_AddRefs(mJarStream));
122 }
123 else {
124 rv = mJarReader->GetInputStream(mJarEntry,
125 getter_AddRefs(mJarStream));
126 }
127 if (NS_FAILED(rv)) {
128 // convert to the proper result if the entry wasn't found
129 // so that error pages work
130 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
131 rv = NS_ERROR_FILE_NOT_FOUND;
132 return rv;
133 }
135 // ask the JarStream for the content length
136 uint64_t avail;
137 rv = mJarStream->Available((uint64_t *) &avail);
138 if (NS_FAILED(rv)) return rv;
140 mContentLength = avail < INT64_MAX ? (int64_t) avail : -1;
142 return NS_OK;
143 }
145 NS_IMETHODIMP
146 nsJARInputThunk::Close()
147 {
148 nsresult rv = NS_OK;
150 if (mJarStream)
151 rv = mJarStream->Close();
153 if (!mUsingJarCache && mJarReader)
154 mJarReader->Close();
156 mJarReader = nullptr;
158 return rv;
159 }
161 NS_IMETHODIMP
162 nsJARInputThunk::Available(uint64_t *avail)
163 {
164 return mJarStream->Available(avail);
165 }
167 NS_IMETHODIMP
168 nsJARInputThunk::Read(char *buf, uint32_t count, uint32_t *countRead)
169 {
170 return mJarStream->Read(buf, count, countRead);
171 }
173 NS_IMETHODIMP
174 nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer, void *closure,
175 uint32_t count, uint32_t *countRead)
176 {
177 // stream transport does only calls Read()
178 return NS_ERROR_NOT_IMPLEMENTED;
179 }
181 NS_IMETHODIMP
182 nsJARInputThunk::IsNonBlocking(bool *nonBlocking)
183 {
184 *nonBlocking = false;
185 return NS_OK;
186 }
188 //-----------------------------------------------------------------------------
189 // nsJARChannel
190 //-----------------------------------------------------------------------------
193 nsJARChannel::nsJARChannel()
194 : mOpened(false)
195 , mAppURI(nullptr)
196 , mContentLength(-1)
197 , mLoadFlags(LOAD_NORMAL)
198 , mStatus(NS_OK)
199 , mIsPending(false)
200 , mIsUnsafe(true)
201 , mOpeningRemote(false)
202 {
203 #if defined(PR_LOGGING)
204 if (!gJarProtocolLog)
205 gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
206 #endif
208 // hold an owning reference to the jar handler
209 NS_ADDREF(gJarHandler);
210 }
212 nsJARChannel::~nsJARChannel()
213 {
214 // release owning reference to the jar handler
215 nsJARProtocolHandler *handler = gJarHandler;
216 NS_RELEASE(handler); // nullptr parameter
217 }
219 NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel,
220 nsHashPropertyBag,
221 nsIRequest,
222 nsIChannel,
223 nsIStreamListener,
224 nsIRequestObserver,
225 nsIDownloadObserver,
226 nsIRemoteOpenFileListener,
227 nsIJARChannel)
229 nsresult
230 nsJARChannel::Init(nsIURI *uri)
231 {
232 nsresult rv;
233 mJarURI = do_QueryInterface(uri, &rv);
234 if (NS_FAILED(rv))
235 return rv;
237 mOriginalURI = mJarURI;
239 // Prevent loading jar:javascript URIs (see bug 290982).
240 nsCOMPtr<nsIURI> innerURI;
241 rv = mJarURI->GetJARFile(getter_AddRefs(innerURI));
242 if (NS_FAILED(rv))
243 return rv;
244 bool isJS;
245 rv = innerURI->SchemeIs("javascript", &isJS);
246 if (NS_FAILED(rv))
247 return rv;
248 if (isJS) {
249 NS_WARNING("blocking jar:javascript:");
250 return NS_ERROR_INVALID_ARG;
251 }
253 #if defined(PR_LOGGING)
254 mJarURI->GetSpec(mSpec);
255 #endif
256 return rv;
257 }
259 nsresult
260 nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput)
261 {
262 MOZ_ASSERT(resultInput);
264 // important to pass a clone of the file since the nsIFile impl is not
265 // necessarily MT-safe
266 nsCOMPtr<nsIFile> clonedFile;
267 nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile));
268 if (NS_FAILED(rv))
269 return rv;
271 nsCOMPtr<nsIZipReader> reader;
272 if (jarCache) {
273 if (mInnerJarEntry.IsEmpty())
274 rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
275 else
276 rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
277 getter_AddRefs(reader));
278 } else {
279 // create an uncached jar reader
280 nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
281 if (NS_FAILED(rv))
282 return rv;
284 rv = outerReader->Open(clonedFile);
285 if (NS_FAILED(rv))
286 return rv;
288 if (mInnerJarEntry.IsEmpty())
289 reader = outerReader;
290 else {
291 reader = do_CreateInstance(kZipReaderCID, &rv);
292 if (NS_FAILED(rv))
293 return rv;
295 rv = reader->OpenInner(outerReader, mInnerJarEntry);
296 }
297 }
298 if (NS_FAILED(rv))
299 return rv;
301 nsRefPtr<nsJARInputThunk> input = new nsJARInputThunk(reader,
302 mJarURI,
303 mJarEntry,
304 jarCache != nullptr
305 );
306 rv = input->Init();
307 if (NS_FAILED(rv))
308 return rv;
310 // Make GetContentLength meaningful
311 mContentLength = input->GetContentLength();
313 input.forget(resultInput);
314 return NS_OK;
315 }
317 nsresult
318 nsJARChannel::LookupFile()
319 {
320 LOG(("nsJARChannel::LookupFile [this=%x %s]\n", this, mSpec.get()));
322 nsresult rv;
323 nsCOMPtr<nsIURI> uri;
325 rv = mJarURI->GetJARFile(getter_AddRefs(mJarBaseURI));
326 if (NS_FAILED(rv))
327 return rv;
329 rv = mJarURI->GetJAREntry(mJarEntry);
330 if (NS_FAILED(rv))
331 return rv;
333 // The name of the JAR entry must not contain URL-escaped characters:
334 // we're moving from URL domain to a filename domain here. nsStandardURL
335 // does basic escaping by default, which breaks reading zipped files which
336 // have e.g. spaces in their filenames.
337 NS_UnescapeURL(mJarEntry);
339 // try to get a nsIFile directly from the url, which will often succeed.
340 {
341 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
342 if (fileURL)
343 fileURL->GetFile(getter_AddRefs(mJarFile));
344 }
345 // if we're in child process and have special "remoteopenfile:://" scheme,
346 // create special nsIFile that gets file handle from parent when opened.
347 if (!mJarFile && !gJarHandler->IsMainProcess()) {
348 nsAutoCString scheme;
349 rv = mJarBaseURI->GetScheme(scheme);
350 if (NS_SUCCEEDED(rv) && scheme.EqualsLiteral("remoteopenfile")) {
351 nsRefPtr<RemoteOpenFileChild> remoteFile = new RemoteOpenFileChild();
352 rv = remoteFile->Init(mJarBaseURI, mAppURI);
353 NS_ENSURE_SUCCESS(rv, rv);
354 mJarFile = remoteFile;
356 nsIZipReaderCache *jarCache = gJarHandler->JarCache();
357 if (jarCache) {
358 bool cached = false;
359 rv = jarCache->IsCached(mJarFile, &cached);
360 if (NS_SUCCEEDED(rv) && cached) {
361 // zipcache already has file mmapped: don't open on parent,
362 // just return and proceed to cache hit in CreateJarInput()
363 return NS_OK;
364 }
365 }
367 mOpeningRemote = true;
369 if (gJarHandler->RemoteOpenFileInProgress(remoteFile, this)) {
370 // JarHandler will trigger OnRemoteFileOpen() after the first
371 // request for this file completes and we'll get a JAR cache
372 // hit.
373 return NS_OK;
374 }
376 // Open file on parent: OnRemoteFileOpenComplete called when done
377 nsCOMPtr<nsITabChild> tabChild;
378 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, tabChild);
379 rv = remoteFile->AsyncRemoteFileOpen(PR_RDONLY, this, tabChild.get());
380 NS_ENSURE_SUCCESS(rv, rv);
381 }
382 }
383 // try to handle a nested jar
384 if (!mJarFile) {
385 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
386 if (jarURI) {
387 nsCOMPtr<nsIFileURL> fileURL;
388 nsCOMPtr<nsIURI> innerJarURI;
389 rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
390 if (NS_SUCCEEDED(rv))
391 fileURL = do_QueryInterface(innerJarURI);
392 if (fileURL) {
393 fileURL->GetFile(getter_AddRefs(mJarFile));
394 jarURI->GetJAREntry(mInnerJarEntry);
395 }
396 }
397 }
399 return rv;
400 }
402 nsresult
403 nsJARChannel::OpenLocalFile()
404 {
405 MOZ_ASSERT(mIsPending);
407 // Local files are always considered safe.
408 mIsUnsafe = false;
410 nsRefPtr<nsJARInputThunk> input;
411 nsresult rv = CreateJarInput(gJarHandler->JarCache(),
412 getter_AddRefs(input));
413 if (NS_SUCCEEDED(rv)) {
414 // Create input stream pump and call AsyncRead as a block.
415 rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
416 if (NS_SUCCEEDED(rv))
417 rv = mPump->AsyncRead(this, nullptr);
418 }
420 return rv;
421 }
423 void
424 nsJARChannel::NotifyError(nsresult aError)
425 {
426 MOZ_ASSERT(NS_FAILED(aError));
428 mStatus = aError;
430 OnStartRequest(nullptr, nullptr);
431 OnStopRequest(nullptr, nullptr, aError);
432 }
434 //-----------------------------------------------------------------------------
435 // nsIRequest
436 //-----------------------------------------------------------------------------
438 NS_IMETHODIMP
439 nsJARChannel::GetName(nsACString &result)
440 {
441 return mJarURI->GetSpec(result);
442 }
444 NS_IMETHODIMP
445 nsJARChannel::IsPending(bool *result)
446 {
447 *result = mIsPending;
448 return NS_OK;
449 }
451 NS_IMETHODIMP
452 nsJARChannel::GetStatus(nsresult *status)
453 {
454 if (mPump && NS_SUCCEEDED(mStatus))
455 mPump->GetStatus(status);
456 else
457 *status = mStatus;
458 return NS_OK;
459 }
461 NS_IMETHODIMP
462 nsJARChannel::Cancel(nsresult status)
463 {
464 mStatus = status;
465 if (mPump)
466 return mPump->Cancel(status);
468 NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
469 return NS_OK;
470 }
472 NS_IMETHODIMP
473 nsJARChannel::Suspend()
474 {
475 if (mPump)
476 return mPump->Suspend();
478 NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
479 return NS_OK;
480 }
482 NS_IMETHODIMP
483 nsJARChannel::Resume()
484 {
485 if (mPump)
486 return mPump->Resume();
488 NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
489 return NS_OK;
490 }
492 NS_IMETHODIMP
493 nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
494 {
495 *aLoadFlags = mLoadFlags;
496 return NS_OK;
497 }
499 NS_IMETHODIMP
500 nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
501 {
502 mLoadFlags = aLoadFlags;
503 return NS_OK;
504 }
506 NS_IMETHODIMP
507 nsJARChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
508 {
509 NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
510 return NS_OK;
511 }
513 NS_IMETHODIMP
514 nsJARChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
515 {
516 mLoadGroup = aLoadGroup;
517 return NS_OK;
518 }
520 //-----------------------------------------------------------------------------
521 // nsIChannel
522 //-----------------------------------------------------------------------------
524 NS_IMETHODIMP
525 nsJARChannel::GetOriginalURI(nsIURI **aURI)
526 {
527 *aURI = mOriginalURI;
528 NS_ADDREF(*aURI);
529 return NS_OK;
530 }
532 NS_IMETHODIMP
533 nsJARChannel::SetOriginalURI(nsIURI *aURI)
534 {
535 NS_ENSURE_ARG_POINTER(aURI);
536 mOriginalURI = aURI;
537 return NS_OK;
538 }
540 NS_IMETHODIMP
541 nsJARChannel::GetURI(nsIURI **aURI)
542 {
543 if (mAppURI) {
544 NS_IF_ADDREF(*aURI = mAppURI);
545 } else {
546 NS_IF_ADDREF(*aURI = mJarURI);
547 }
549 return NS_OK;
550 }
552 NS_IMETHODIMP
553 nsJARChannel::GetOwner(nsISupports **aOwner)
554 {
555 // JAR signatures are not processed to avoid main-thread network I/O (bug 726125)
556 *aOwner = mOwner;
557 NS_IF_ADDREF(*aOwner);
558 return NS_OK;
559 }
561 NS_IMETHODIMP
562 nsJARChannel::SetOwner(nsISupports *aOwner)
563 {
564 mOwner = aOwner;
565 return NS_OK;
566 }
568 NS_IMETHODIMP
569 nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
570 {
571 NS_IF_ADDREF(*aCallbacks = mCallbacks);
572 return NS_OK;
573 }
575 NS_IMETHODIMP
576 nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
577 {
578 mCallbacks = aCallbacks;
579 return NS_OK;
580 }
582 NS_IMETHODIMP
583 nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
584 {
585 NS_PRECONDITION(aSecurityInfo, "Null out param");
586 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
587 return NS_OK;
588 }
590 NS_IMETHODIMP
591 nsJARChannel::GetContentType(nsACString &result)
592 {
593 // If the Jar file has not been open yet,
594 // We return application/x-unknown-content-type
595 if (!mOpened) {
596 result.Assign(UNKNOWN_CONTENT_TYPE);
597 return NS_OK;
598 }
600 if (mContentType.IsEmpty()) {
602 //
603 // generate content type and set it
604 //
605 const char *ext = nullptr, *fileName = mJarEntry.get();
606 int32_t len = mJarEntry.Length();
608 // check if we're displaying a directory
609 // mJarEntry will be empty if we're trying to display
610 // the topmost directory in a zip, e.g. jar:foo.zip!/
611 if (ENTRY_IS_DIRECTORY(mJarEntry)) {
612 mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
613 }
614 else {
615 // not a directory, take a guess by its extension
616 for (int32_t i = len-1; i >= 0; i--) {
617 if (fileName[i] == '.') {
618 ext = &fileName[i + 1];
619 break;
620 }
621 }
622 if (ext) {
623 nsIMIMEService *mimeServ = gJarHandler->MimeService();
624 if (mimeServ)
625 mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType);
626 }
627 if (mContentType.IsEmpty())
628 mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
629 }
630 }
631 result = mContentType;
632 return NS_OK;
633 }
635 NS_IMETHODIMP
636 nsJARChannel::SetContentType(const nsACString &aContentType)
637 {
638 // If someone gives us a type hint we should just use that type instead of
639 // doing our guessing. So we don't care when this is being called.
641 // mContentCharset is unchanged if not parsed
642 NS_ParseContentType(aContentType, mContentType, mContentCharset);
643 return NS_OK;
644 }
646 NS_IMETHODIMP
647 nsJARChannel::GetContentCharset(nsACString &aContentCharset)
648 {
649 // If someone gives us a charset hint we should just use that charset.
650 // So we don't care when this is being called.
651 aContentCharset = mContentCharset;
652 return NS_OK;
653 }
655 NS_IMETHODIMP
656 nsJARChannel::SetContentCharset(const nsACString &aContentCharset)
657 {
658 mContentCharset = aContentCharset;
659 return NS_OK;
660 }
662 NS_IMETHODIMP
663 nsJARChannel::GetContentDisposition(uint32_t *aContentDisposition)
664 {
665 if (mContentDispositionHeader.IsEmpty())
666 return NS_ERROR_NOT_AVAILABLE;
668 *aContentDisposition = mContentDisposition;
669 return NS_OK;
670 }
672 NS_IMETHODIMP
673 nsJARChannel::SetContentDisposition(uint32_t aContentDisposition)
674 {
675 return NS_ERROR_NOT_AVAILABLE;
676 }
678 NS_IMETHODIMP
679 nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
680 {
681 return NS_ERROR_NOT_AVAILABLE;
682 }
684 NS_IMETHODIMP
685 nsJARChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
686 {
687 return NS_ERROR_NOT_AVAILABLE;
688 }
690 NS_IMETHODIMP
691 nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
692 {
693 if (mContentDispositionHeader.IsEmpty())
694 return NS_ERROR_NOT_AVAILABLE;
696 aContentDispositionHeader = mContentDispositionHeader;
697 return NS_OK;
698 }
700 NS_IMETHODIMP
701 nsJARChannel::GetContentLength(int64_t *result)
702 {
703 *result = mContentLength;
704 return NS_OK;
705 }
707 NS_IMETHODIMP
708 nsJARChannel::SetContentLength(int64_t aContentLength)
709 {
710 // XXX does this really make any sense at all?
711 mContentLength = aContentLength;
712 return NS_OK;
713 }
715 NS_IMETHODIMP
716 nsJARChannel::Open(nsIInputStream **stream)
717 {
718 LOG(("nsJARChannel::Open [this=%x]\n", this));
720 NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
721 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
723 mJarFile = nullptr;
724 mIsUnsafe = true;
726 nsresult rv = LookupFile();
727 if (NS_FAILED(rv))
728 return rv;
730 // If mJarInput was not set by LookupFile, the JAR is a remote jar.
731 if (!mJarFile) {
732 NS_NOTREACHED("need sync downloader");
733 return NS_ERROR_NOT_IMPLEMENTED;
734 }
736 nsRefPtr<nsJARInputThunk> input;
737 rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
738 if (NS_FAILED(rv))
739 return rv;
741 input.forget(stream);
742 mOpened = true;
743 // local files are always considered safe
744 mIsUnsafe = false;
745 return NS_OK;
746 }
748 NS_IMETHODIMP
749 nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
750 {
751 LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
753 NS_ENSURE_ARG_POINTER(listener);
754 NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
755 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
757 mJarFile = nullptr;
758 mIsUnsafe = true;
760 // Initialize mProgressSink
761 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
763 nsresult rv = LookupFile();
764 if (NS_FAILED(rv))
765 return rv;
767 // These variables must only be set if we're going to trigger an
768 // OnStartRequest, either from AsyncRead or OnDownloadComplete.
769 //
770 // That means: Do not add early return statements beyond this point!
771 mListener = listener;
772 mListenerContext = ctx;
773 mIsPending = true;
775 if (!mJarFile) {
776 // Not a local file...
777 // kick off an async download of the base URI...
778 rv = NS_NewDownloader(getter_AddRefs(mDownloader), this);
779 if (NS_SUCCEEDED(rv))
780 rv = NS_OpenURI(mDownloader, nullptr, mJarBaseURI, nullptr,
781 mLoadGroup, mCallbacks,
782 mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
783 } else if (mOpeningRemote) {
784 // nothing to do: already asked parent to open file.
785 } else {
786 rv = OpenLocalFile();
787 }
789 if (NS_FAILED(rv)) {
790 mIsPending = false;
791 mListenerContext = nullptr;
792 mListener = nullptr;
793 return rv;
794 }
797 if (mLoadGroup)
798 mLoadGroup->AddRequest(this, nullptr);
800 mOpened = true;
801 return NS_OK;
802 }
804 //-----------------------------------------------------------------------------
805 // nsIJARChannel
806 //-----------------------------------------------------------------------------
807 NS_IMETHODIMP
808 nsJARChannel::GetIsUnsafe(bool *isUnsafe)
809 {
810 *isUnsafe = mIsUnsafe;
811 return NS_OK;
812 }
814 NS_IMETHODIMP
815 nsJARChannel::SetAppURI(nsIURI *aURI) {
816 NS_ENSURE_ARG_POINTER(aURI);
818 nsAutoCString scheme;
819 aURI->GetScheme(scheme);
820 if (!scheme.EqualsLiteral("app")) {
821 return NS_ERROR_INVALID_ARG;
822 }
824 mAppURI = aURI;
825 return NS_OK;
826 }
828 //-----------------------------------------------------------------------------
829 // nsIDownloadObserver
830 //-----------------------------------------------------------------------------
832 NS_IMETHODIMP
833 nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
834 nsIRequest *request,
835 nsISupports *context,
836 nsresult status,
837 nsIFile *file)
838 {
839 nsresult rv;
841 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
842 if (channel) {
843 uint32_t loadFlags;
844 channel->GetLoadFlags(&loadFlags);
845 if (loadFlags & LOAD_REPLACE) {
846 mLoadFlags |= LOAD_REPLACE;
848 if (!mOriginalURI) {
849 SetOriginalURI(mJarURI);
850 }
852 nsCOMPtr<nsIURI> innerURI;
853 rv = channel->GetURI(getter_AddRefs(innerURI));
854 if (NS_SUCCEEDED(rv)) {
855 nsCOMPtr<nsIJARURI> newURI;
856 rv = mJarURI->CloneWithJARFile(innerURI,
857 getter_AddRefs(newURI));
858 if (NS_SUCCEEDED(rv)) {
859 mJarURI = newURI;
860 }
861 }
862 if (NS_SUCCEEDED(status)) {
863 status = rv;
864 }
865 }
866 }
868 if (NS_SUCCEEDED(status) && channel) {
869 // Grab the security info from our base channel
870 channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
872 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
873 if (httpChannel) {
874 // We only want to run scripts if the server really intended to
875 // send us a JAR file. Check the server-supplied content type for
876 // a JAR type.
877 nsAutoCString header;
878 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
879 header);
880 nsAutoCString contentType;
881 nsAutoCString charset;
882 NS_ParseContentType(header, contentType, charset);
883 nsAutoCString channelContentType;
884 channel->GetContentType(channelContentType);
885 mIsUnsafe = !(contentType.Equals(channelContentType) &&
886 (contentType.EqualsLiteral("application/java-archive") ||
887 contentType.EqualsLiteral("application/x-jar")));
888 } else {
889 nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
890 if (innerJARChannel) {
891 bool unsafe;
892 innerJARChannel->GetIsUnsafe(&unsafe);
893 mIsUnsafe = unsafe;
894 }
895 }
897 channel->GetContentDispositionHeader(mContentDispositionHeader);
898 mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
899 }
901 if (NS_SUCCEEDED(status) && mIsUnsafe &&
902 !Preferences::GetBool("network.jar.open-unsafe-types", false)) {
903 status = NS_ERROR_UNSAFE_CONTENT_TYPE;
904 }
906 if (NS_SUCCEEDED(status)) {
907 // Refuse to unpack view-source: jars even if open-unsafe-types is set.
908 nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(channel);
909 if (viewSource) {
910 status = NS_ERROR_UNSAFE_CONTENT_TYPE;
911 }
912 }
914 if (NS_SUCCEEDED(status)) {
915 mJarFile = file;
917 nsRefPtr<nsJARInputThunk> input;
918 rv = CreateJarInput(nullptr, getter_AddRefs(input));
919 if (NS_SUCCEEDED(rv)) {
920 // create input stream pump
921 rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
922 if (NS_SUCCEEDED(rv))
923 rv = mPump->AsyncRead(this, nullptr);
924 }
925 status = rv;
926 }
928 if (NS_FAILED(status)) {
929 NotifyError(status);
930 }
932 return NS_OK;
933 }
935 //-----------------------------------------------------------------------------
936 // nsIRemoteOpenFileListener
937 //-----------------------------------------------------------------------------
938 nsresult
939 nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus)
940 {
941 nsresult rv = aOpenStatus;
943 // NS_ERROR_ALREADY_OPENED here means we'll hit JAR cache in
944 // OpenLocalFile().
945 if (NS_SUCCEEDED(rv) || rv == NS_ERROR_ALREADY_OPENED) {
946 rv = OpenLocalFile();
947 }
949 if (NS_FAILED(rv)) {
950 NotifyError(rv);
951 }
953 return NS_OK;
954 }
956 //-----------------------------------------------------------------------------
957 // nsIStreamListener
958 //-----------------------------------------------------------------------------
960 NS_IMETHODIMP
961 nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
962 {
963 LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
965 return mListener->OnStartRequest(this, mListenerContext);
966 }
968 NS_IMETHODIMP
969 nsJARChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
970 {
971 LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n",
972 this, mSpec.get(), status));
974 if (NS_SUCCEEDED(mStatus))
975 mStatus = status;
977 if (mListener) {
978 mListener->OnStopRequest(this, mListenerContext, status);
979 mListener = 0;
980 mListenerContext = 0;
981 }
983 if (mLoadGroup)
984 mLoadGroup->RemoveRequest(this, nullptr, status);
986 mPump = 0;
987 mIsPending = false;
988 mDownloader = 0; // this may delete the underlying jar file
990 // Drop notification callbacks to prevent cycles.
991 mCallbacks = 0;
992 mProgressSink = 0;
994 return NS_OK;
995 }
997 NS_IMETHODIMP
998 nsJARChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx,
999 nsIInputStream *stream,
1000 uint64_t offset, uint32_t count)
1001 {
1002 #if defined(PR_LOGGING)
1003 LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec.get()));
1004 #endif
1006 nsresult rv;
1008 rv = mListener->OnDataAvailable(this, mListenerContext, stream, offset, count);
1010 // simply report progress here instead of hooking ourselves up as a
1011 // nsITransportEventSink implementation.
1012 // XXX do the 64-bit stuff for real
1013 if (mProgressSink && NS_SUCCEEDED(rv) && !(mLoadFlags & LOAD_BACKGROUND))
1014 mProgressSink->OnProgress(this, nullptr, offset + count,
1015 uint64_t(mContentLength));
1017 return rv; // let the pump cancel on failure
1018 }