1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is mozilla.org Code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
24 * Jeff Walden <jwalden+code@mit.edu>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 #include "nsJARChannel.h"
42 #include "nsJARProtocolHandler.h"
43 #include "nsMimeTypes.h"
44 #include "nsNetUtil.h"
47 #include "nsIPrefService.h"
48 #include "nsIPrefBranch.h"
50 #include "nsIScriptSecurityManager.h"
51 #include "nsIPrincipal.h"
52 #include "nsIFileURL.h"
55 static NS_DEFINE_CID(kZipReaderCID
, NS_ZIPREADER_CID
);
57 // the entry for a directory will either be empty (in the case of the
58 // top-level directory) or will end with a slash
59 #define ENTRY_IS_DIRECTORY(_entry) \
60 ((_entry).IsEmpty() || '/' == (_entry).Last())
62 //-----------------------------------------------------------------------------
64 #if defined(PR_LOGGING)
66 // set NSPR_LOG_MODULES=nsJarProtocol:5
68 static PRLogModuleInfo
*gJarProtocolLog
= nsnull
;
71 #define LOG(args) PR_LOG(gJarProtocolLog, PR_LOG_DEBUG, args)
72 #define LOG_ENABLED() PR_LOG_TEST(gJarProtocolLog, 4)
74 //-----------------------------------------------------------------------------
77 // this class allows us to do some extra work on the stream transport thread.
78 //-----------------------------------------------------------------------------
80 class nsJARInputThunk
: public nsIInputStream
84 NS_DECL_NSIINPUTSTREAM
86 nsJARInputThunk(nsIFile
*jarFile
,
88 const nsACString
&jarEntry
,
89 nsIZipReaderCache
*jarCache
)
92 , mFullJarURI(fullJarURI
)
96 NS_ASSERTION(mJarFile
, "no jar file");
99 virtual ~nsJARInputThunk()
101 if (!mJarCache
&& mJarReader
)
105 void GetJarReader(nsIZipReader
**result
)
107 NS_IF_ADDREF(*result
= mJarReader
);
110 PRInt32
GetContentLength()
112 return mContentLength
;
115 nsresult
EnsureJarStream();
119 nsCOMPtr
<nsIZipReaderCache
> mJarCache
;
120 nsCOMPtr
<nsIZipReader
> mJarReader
;
121 nsCOMPtr
<nsIFile
> mJarFile
;
122 nsCOMPtr
<nsIURI
> mFullJarURI
;
123 nsCOMPtr
<nsIInputStream
> mJarStream
;
125 PRInt32 mContentLength
;
128 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputThunk
, nsIInputStream
)
131 nsJARInputThunk::EnsureJarStream()
138 rv
= mJarCache
->GetZip(mJarFile
, getter_AddRefs(mJarReader
));
140 // create an uncached jar reader
141 mJarReader
= do_CreateInstance(kZipReaderCID
, &rv
);
142 if (NS_FAILED(rv
)) return rv
;
144 rv
= mJarReader
->Open(mJarFile
);
146 if (NS_FAILED(rv
)) return rv
;
148 if (ENTRY_IS_DIRECTORY(mJarEntry
)) {
149 // A directory stream also needs the Spec of the FullJarURI
150 // because is included in the stream data itself.
152 nsCAutoString jarDirSpec
;
153 rv
= mFullJarURI
->GetAsciiSpec(jarDirSpec
);
154 if (NS_FAILED(rv
)) return rv
;
156 rv
= mJarReader
->GetInputStreamWithSpec(jarDirSpec
,
158 getter_AddRefs(mJarStream
));
161 rv
= mJarReader
->GetInputStream(mJarEntry
.get(),
162 getter_AddRefs(mJarStream
));
165 // convert to the proper result if the entry wasn't found
166 // so that error pages work
167 if (rv
== NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
)
168 rv
= NS_ERROR_FILE_NOT_FOUND
;
172 // ask the JarStream for the content length
173 mJarStream
->Available((PRUint32
*) &mContentLength
);
179 nsJARInputThunk::Close()
182 return mJarStream
->Close();
188 nsJARInputThunk::Available(PRUint32
*avail
)
190 nsresult rv
= EnsureJarStream();
191 if (NS_FAILED(rv
)) return rv
;
193 return mJarStream
->Available(avail
);
197 nsJARInputThunk::Read(char *buf
, PRUint32 count
, PRUint32
*countRead
)
199 nsresult rv
= EnsureJarStream();
200 if (NS_FAILED(rv
)) return rv
;
202 return mJarStream
->Read(buf
, count
, countRead
);
206 nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer
, void *closure
,
207 PRUint32 count
, PRUint32
*countRead
)
209 // stream transport does only calls Read()
210 return NS_ERROR_NOT_IMPLEMENTED
;
214 nsJARInputThunk::IsNonBlocking(PRBool
*nonBlocking
)
216 *nonBlocking
= PR_FALSE
;
220 //-----------------------------------------------------------------------------
222 nsJARChannel::nsJARChannel()
224 , mLoadFlags(LOAD_NORMAL
)
226 , mIsPending(PR_FALSE
)
230 #if defined(PR_LOGGING)
231 if (!gJarProtocolLog
)
232 gJarProtocolLog
= PR_NewLogModule("nsJarProtocol");
235 // hold an owning reference to the jar handler
236 NS_ADDREF(gJarHandler
);
239 nsJARChannel::~nsJARChannel()
241 // with the exception of certain error cases mJarInput will already be null.
242 NS_IF_RELEASE(mJarInput
);
244 // release owning reference to the jar handler
245 nsJARProtocolHandler
*handler
= gJarHandler
;
246 NS_RELEASE(handler
); // NULL parameter
249 NS_IMPL_ISUPPORTS6(nsJARChannel
,
258 nsJARChannel::Init(nsIURI
*uri
)
261 mJarURI
= do_QueryInterface(uri
, &rv
);
265 mOriginalURI
= mJarURI
;
267 // Prevent loading jar:javascript URIs (see bug 290982).
268 nsCOMPtr
<nsIURI
> innerURI
;
269 rv
= mJarURI
->GetJARFile(getter_AddRefs(innerURI
));
273 rv
= innerURI
->SchemeIs("javascript", &isJS
);
277 NS_WARNING("blocking jar:javascript:");
278 return NS_ERROR_INVALID_ARG
;
281 #if defined(PR_LOGGING)
282 mJarURI
->GetSpec(mSpec
);
288 nsJARChannel::CreateJarInput(nsIZipReaderCache
*jarCache
)
290 // important to pass a clone of the file since the nsIFile impl is not
291 // necessarily MT-safe
292 nsCOMPtr
<nsIFile
> clonedFile
;
293 nsresult rv
= mJarFile
->Clone(getter_AddRefs(clonedFile
));
294 if (NS_FAILED(rv
)) return rv
;
296 mJarInput
= new nsJARInputThunk(clonedFile
, mJarURI
, mJarEntry
, jarCache
);
298 return NS_ERROR_OUT_OF_MEMORY
;
299 NS_ADDREF(mJarInput
);
304 nsJARChannel::EnsureJarInput(PRBool blocking
)
306 LOG(("nsJARChannel::EnsureJarInput [this=%x %s]\n", this, mSpec
.get()));
309 nsCOMPtr
<nsIURI
> uri
;
311 rv
= mJarURI
->GetJARFile(getter_AddRefs(mJarBaseURI
));
312 if (NS_FAILED(rv
)) return rv
;
314 rv
= mJarURI
->GetJAREntry(mJarEntry
);
315 if (NS_FAILED(rv
)) return rv
;
317 // The name of the JAR entry must not contain URL-escaped characters:
318 // we're moving from URL domain to a filename domain here. nsStandardURL
319 // does basic escaping by default, which breaks reading zipped files which
320 // have e.g. spaces in their filenames.
321 NS_UnescapeURL(mJarEntry
);
323 // try to get a nsIFile directly from the url, which will often succeed.
325 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(mJarBaseURI
);
327 fileURL
->GetFile(getter_AddRefs(mJarFile
));
331 mIsUnsafe
= PR_FALSE
;
333 // NOTE: we do not need to deal with mSecurityInfo here,
334 // because we're loading from a local file
335 rv
= CreateJarInput(gJarHandler
->JarCache());
338 NS_NOTREACHED("need sync downloader");
339 rv
= NS_ERROR_NOT_IMPLEMENTED
;
342 // kick off an async download of the base URI...
343 rv
= NS_NewDownloader(getter_AddRefs(mDownloader
), this);
344 if (NS_SUCCEEDED(rv
))
345 rv
= NS_OpenURI(mDownloader
, nsnull
, mJarBaseURI
, nsnull
,
346 mLoadGroup
, mCallbacks
,
347 mLoadFlags
& ~(LOAD_DOCUMENT_URI
| LOAD_CALL_CONTENT_SNIFFERS
));
353 //-----------------------------------------------------------------------------
355 //-----------------------------------------------------------------------------
358 nsJARChannel::GetName(nsACString
&result
)
360 return mJarURI
->GetSpec(result
);
364 nsJARChannel::IsPending(PRBool
*result
)
366 *result
= mIsPending
;
371 nsJARChannel::GetStatus(nsresult
*status
)
373 if (mPump
&& NS_SUCCEEDED(mStatus
))
374 mPump
->GetStatus(status
);
381 nsJARChannel::Cancel(nsresult status
)
385 return mPump
->Cancel(status
);
387 NS_ASSERTION(!mIsPending
, "need to implement cancel when downloading");
392 nsJARChannel::Suspend()
395 return mPump
->Suspend();
397 NS_ASSERTION(!mIsPending
, "need to implement suspend when downloading");
402 nsJARChannel::Resume()
405 return mPump
->Resume();
407 NS_ASSERTION(!mIsPending
, "need to implement resume when downloading");
412 nsJARChannel::GetLoadFlags(nsLoadFlags
*aLoadFlags
)
414 *aLoadFlags
= mLoadFlags
;
419 nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags
)
421 mLoadFlags
= aLoadFlags
;
426 nsJARChannel::GetLoadGroup(nsILoadGroup
**aLoadGroup
)
428 NS_IF_ADDREF(*aLoadGroup
= mLoadGroup
);
433 nsJARChannel::SetLoadGroup(nsILoadGroup
*aLoadGroup
)
435 mLoadGroup
= aLoadGroup
;
439 //-----------------------------------------------------------------------------
441 //-----------------------------------------------------------------------------
444 nsJARChannel::GetOriginalURI(nsIURI
**aURI
)
446 *aURI
= mOriginalURI
;
452 nsJARChannel::SetOriginalURI(nsIURI
*aURI
)
454 NS_ENSURE_ARG_POINTER(aURI
);
460 nsJARChannel::GetURI(nsIURI
**aURI
)
462 NS_IF_ADDREF(*aURI
= mJarURI
);
467 nsJARChannel::GetOwner(nsISupports
**result
)
472 NS_ADDREF(*result
= mOwner
);
481 //-- Verify signature, if one is present, and set owner accordingly
482 nsCOMPtr
<nsIZipReader
> jarReader
;
483 mJarInput
->GetJarReader(getter_AddRefs(jarReader
));
485 return NS_ERROR_NOT_INITIALIZED
;
487 nsCOMPtr
<nsIJAR
> jar
= do_QueryInterface(jarReader
, &rv
);
489 NS_ERROR("nsIJAR not supported");
493 nsCOMPtr
<nsIPrincipal
> cert
;
494 rv
= jar
->GetCertificatePrincipal(mJarEntry
.get(), getter_AddRefs(cert
));
495 if (NS_FAILED(rv
)) return rv
;
498 nsCAutoString certFingerprint
;
499 rv
= cert
->GetFingerprint(certFingerprint
);
500 if (NS_FAILED(rv
)) return rv
;
502 nsCAutoString subjectName
;
503 rv
= cert
->GetSubjectName(subjectName
);
504 if (NS_FAILED(rv
)) return rv
;
506 nsCAutoString prettyName
;
507 rv
= cert
->GetPrettyName(prettyName
);
508 if (NS_FAILED(rv
)) return rv
;
510 nsCOMPtr
<nsISupports
> certificate
;
511 rv
= cert
->GetCertificate(getter_AddRefs(certificate
));
512 if (NS_FAILED(rv
)) return rv
;
514 nsCOMPtr
<nsIScriptSecurityManager
> secMan
=
515 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID
, &rv
);
516 if (NS_FAILED(rv
)) return rv
;
518 rv
= secMan
->GetCertificatePrincipal(certFingerprint
, subjectName
,
519 prettyName
, certificate
,
521 getter_AddRefs(cert
));
522 if (NS_FAILED(rv
)) return rv
;
524 mOwner
= do_QueryInterface(cert
, &rv
);
525 if (NS_FAILED(rv
)) return rv
;
527 NS_ADDREF(*result
= mOwner
);
533 nsJARChannel::SetOwner(nsISupports
*aOwner
)
540 nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor
**aCallbacks
)
542 NS_IF_ADDREF(*aCallbacks
= mCallbacks
);
547 nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor
*aCallbacks
)
549 mCallbacks
= aCallbacks
;
554 nsJARChannel::GetSecurityInfo(nsISupports
**aSecurityInfo
)
556 NS_PRECONDITION(aSecurityInfo
, "Null out param");
557 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
562 nsJARChannel::GetContentType(nsACString
&result
)
564 if (mContentType
.IsEmpty()) {
566 // generate content type and set it
568 const char *ext
= nsnull
, *fileName
= mJarEntry
.get();
569 PRInt32 len
= mJarEntry
.Length();
571 // check if we're displaying a directory
572 // mJarEntry will be empty if we're trying to display
573 // the topmost directory in a zip, e.g. jar:foo.zip!/
574 if (ENTRY_IS_DIRECTORY(mJarEntry
)) {
575 mContentType
.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT
);
578 // not a directory, take a guess by its extension
579 for (PRInt32 i
= len
-1; i
>= 0; i
--) {
580 if (fileName
[i
] == '.') {
581 ext
= &fileName
[i
+ 1];
586 nsIMIMEService
*mimeServ
= gJarHandler
->MimeService();
588 mimeServ
->GetTypeFromExtension(nsDependentCString(ext
), mContentType
);
590 if (mContentType
.IsEmpty())
591 mContentType
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
594 result
= mContentType
;
599 nsJARChannel::SetContentType(const nsACString
&aContentType
)
601 // If someone gives us a type hint we should just use that type instead of
602 // doing our guessing. So we don't care when this is being called.
604 // mContentCharset is unchanged if not parsed
605 NS_ParseContentType(aContentType
, mContentType
, mContentCharset
);
610 nsJARChannel::GetContentCharset(nsACString
&aContentCharset
)
612 // If someone gives us a charset hint we should just use that charset.
613 // So we don't care when this is being called.
614 aContentCharset
= mContentCharset
;
619 nsJARChannel::SetContentCharset(const nsACString
&aContentCharset
)
621 mContentCharset
= aContentCharset
;
626 nsJARChannel::GetContentLength(PRInt32
*result
)
628 // if content length is unknown, query mJarInput...
629 if (mContentLength
< 0 && mJarInput
)
630 mContentLength
= mJarInput
->GetContentLength();
632 *result
= mContentLength
;
637 nsJARChannel::SetContentLength(PRInt32 aContentLength
)
639 // XXX does this really make any sense at all?
640 mContentLength
= aContentLength
;
645 nsJARChannel::Open(nsIInputStream
**stream
)
647 LOG(("nsJARChannel::Open [this=%x]\n", this));
649 NS_ENSURE_TRUE(!mJarInput
, NS_ERROR_IN_PROGRESS
);
650 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
655 nsresult rv
= EnsureJarInput(PR_TRUE
);
656 if (NS_FAILED(rv
)) return rv
;
659 return NS_ERROR_UNEXPECTED
;
661 // force load the jar file now so GetContentLength will return a
662 // meaningful value once we return.
663 mJarInput
->EnsureJarStream();
665 NS_ADDREF(*stream
= mJarInput
);
670 nsJARChannel::AsyncOpen(nsIStreamListener
*listener
, nsISupports
*ctx
)
672 LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
674 NS_ENSURE_ARG_POINTER(listener
);
675 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
680 // Initialize mProgressSink
681 NS_QueryNotificationCallbacks(mCallbacks
, mLoadGroup
, mProgressSink
);
683 nsresult rv
= EnsureJarInput(PR_FALSE
);
684 if (NS_FAILED(rv
)) return rv
;
687 // create input stream pump
688 rv
= NS_NewInputStreamPump(getter_AddRefs(mPump
), mJarInput
);
689 if (NS_FAILED(rv
)) return rv
;
691 rv
= mPump
->AsyncRead(this, nsnull
);
692 if (NS_FAILED(rv
)) return rv
;
696 mLoadGroup
->AddRequest(this, nsnull
);
698 mListener
= listener
;
699 mListenerContext
= ctx
;
700 mIsPending
= PR_TRUE
;
704 //-----------------------------------------------------------------------------
706 //-----------------------------------------------------------------------------
708 nsJARChannel::GetIsUnsafe(PRBool
*isUnsafe
)
710 *isUnsafe
= mIsUnsafe
;
714 //-----------------------------------------------------------------------------
715 // nsIDownloadObserver
716 //-----------------------------------------------------------------------------
719 nsJARChannel::OnDownloadComplete(nsIDownloader
*downloader
,
721 nsISupports
*context
,
727 nsCOMPtr
<nsIChannel
> channel(do_QueryInterface(request
));
730 channel
->GetLoadFlags(&loadFlags
);
731 if (loadFlags
& LOAD_REPLACE
) {
732 mLoadFlags
|= LOAD_REPLACE
;
735 SetOriginalURI(mJarURI
);
738 nsCOMPtr
<nsIURI
> innerURI
;
739 rv
= channel
->GetURI(getter_AddRefs(innerURI
));
740 if (NS_SUCCEEDED(rv
)) {
741 nsCOMPtr
<nsIJARURI
> newURI
;
742 rv
= mJarURI
->CloneWithJARFile(innerURI
,
743 getter_AddRefs(newURI
));
744 if (NS_SUCCEEDED(rv
)) {
748 if (NS_SUCCEEDED(status
)) {
754 if (NS_SUCCEEDED(status
) && channel
) {
755 // Grab the security info from our base channel
756 channel
->GetSecurityInfo(getter_AddRefs(mSecurityInfo
));
758 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(channel
));
760 // We only want to run scripts if the server really intended to
761 // send us a JAR file. Check the server-supplied content type for
763 nsCAutoString header
;
764 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
767 nsCAutoString contentType
;
768 nsCAutoString charset
;
769 NS_ParseContentType(header
, contentType
, charset
);
771 mIsUnsafe
= !contentType
.EqualsLiteral("application/java-archive") &&
772 !contentType
.EqualsLiteral("application/x-jar");
774 nsCOMPtr
<nsIJARChannel
> innerJARChannel(do_QueryInterface(channel
));
775 if (innerJARChannel
) {
777 innerJARChannel
->GetIsUnsafe(&unsafe
);
783 if (NS_SUCCEEDED(status
) && mIsUnsafe
) {
784 PRBool allowUnpack
= PR_FALSE
;
786 nsCOMPtr
<nsIPrefBranch
> prefs
=
787 do_GetService(NS_PREFSERVICE_CONTRACTID
);
789 prefs
->GetBoolPref("network.jar.open-unsafe-types", &allowUnpack
);
793 status
= NS_ERROR_UNSAFE_CONTENT_TYPE
;
797 if (NS_SUCCEEDED(status
)) {
800 rv
= CreateJarInput(nsnull
);
801 if (NS_SUCCEEDED(rv
)) {
802 // create input stream pump
803 rv
= NS_NewInputStreamPump(getter_AddRefs(mPump
), mJarInput
);
804 if (NS_SUCCEEDED(rv
))
805 rv
= mPump
->AsyncRead(this, nsnull
);
810 if (NS_FAILED(status
)) {
812 OnStartRequest(nsnull
, nsnull
);
813 OnStopRequest(nsnull
, nsnull
, status
);
819 //-----------------------------------------------------------------------------
821 //-----------------------------------------------------------------------------
824 nsJARChannel::OnStartRequest(nsIRequest
*req
, nsISupports
*ctx
)
826 LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec
.get()));
828 return mListener
->OnStartRequest(this, mListenerContext
);
832 nsJARChannel::OnStopRequest(nsIRequest
*req
, nsISupports
*ctx
, nsresult status
)
834 LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n",
835 this, mSpec
.get(), status
));
837 if (NS_SUCCEEDED(mStatus
))
841 mListener
->OnStopRequest(this, mListenerContext
, status
);
843 mListenerContext
= 0;
847 mLoadGroup
->RemoveRequest(this, nsnull
, status
);
850 NS_IF_RELEASE(mJarInput
);
851 mIsPending
= PR_FALSE
;
852 mDownloader
= 0; // this may delete the underlying jar file
854 // Drop notification callbacks to prevent cycles.
862 nsJARChannel::OnDataAvailable(nsIRequest
*req
, nsISupports
*ctx
,
863 nsIInputStream
*stream
,
864 PRUint32 offset
, PRUint32 count
)
866 #if defined(PR_LOGGING)
867 LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec
.get()));
872 rv
= mListener
->OnDataAvailable(this, mListenerContext
, stream
, offset
, count
);
874 // simply report progress here instead of hooking ourselves up as a
875 // nsITransportEventSink implementation.
876 // XXX do the 64-bit stuff for real
877 if (mProgressSink
&& NS_SUCCEEDED(rv
) && !(mLoadFlags
& LOAD_BACKGROUND
))
878 mProgressSink
->OnProgress(this, nsnull
, nsUint64(offset
+ count
),
879 nsUint64(mContentLength
));
881 return rv
; // let the pump cancel on failure