PageRenderTime 74ms CodeModel.GetById 11ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/libjar/nsJARChannel.cpp

http://github.com/zpao/v8monkey
C++ | 965 lines | 688 code | 155 blank | 122 comment | 108 complexity | f1fda8c952d7ca20b235fab9334e962c MD5 | raw file
  1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2 *
  3 * ***** BEGIN LICENSE BLOCK *****
  4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5 *
  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/
 10 *
 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
 14 * License.
 15 *
 16 * The Original Code is mozilla.org Code.
 17 *
 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.
 22 *
 23 * Contributor(s):
 24 *   Jeff Walden <jwalden+code@mit.edu>
 25 *
 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.
 37 *
 38 * ***** END LICENSE BLOCK ***** */
 39
 40#include "nsJAR.h"
 41#include "nsJARChannel.h"
 42#include "nsJARProtocolHandler.h"
 43#include "nsMimeTypes.h"
 44#include "nsNetUtil.h"
 45#include "nsEscape.h"
 46#include "nsIPrefService.h"
 47#include "nsIPrefBranch.h"
 48#include "nsIViewSourceChannel.h"
 49#include "nsChannelProperties.h"
 50
 51#include "nsIScriptSecurityManager.h"
 52#include "nsIPrincipal.h"
 53#include "nsIFileURL.h"
 54
 55#include "mozilla/Preferences.h"
 56
 57using namespace mozilla;
 58
 59static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 60
 61// the entry for a directory will either be empty (in the case of the
 62// top-level directory) or will end with a slash
 63#define ENTRY_IS_DIRECTORY(_entry) \
 64  ((_entry).IsEmpty() || '/' == (_entry).Last())
 65
 66//-----------------------------------------------------------------------------
 67
 68#if defined(PR_LOGGING)
 69//
 70// set NSPR_LOG_MODULES=nsJarProtocol:5
 71//
 72static PRLogModuleInfo *gJarProtocolLog = nsnull;
 73#endif
 74
 75#define LOG(args)     PR_LOG(gJarProtocolLog, PR_LOG_DEBUG, args)
 76#define LOG_ENABLED() PR_LOG_TEST(gJarProtocolLog, 4)
 77
 78//-----------------------------------------------------------------------------
 79// nsJARInputThunk
 80//
 81// this class allows us to do some extra work on the stream transport thread.
 82//-----------------------------------------------------------------------------
 83
 84class nsJARInputThunk : public nsIInputStream
 85{
 86public:
 87    NS_DECL_ISUPPORTS
 88    NS_DECL_NSIINPUTSTREAM
 89
 90    nsJARInputThunk(nsIZipReader *zipReader,
 91                    nsIURI* fullJarURI,
 92                    const nsACString &jarEntry,
 93                    nsIZipReaderCache *jarCache)
 94        : mJarCache(jarCache)
 95        , mJarReader(zipReader)
 96        , mJarEntry(jarEntry)
 97        , mContentLength(-1)
 98    {
 99        if (fullJarURI) {
100#ifdef DEBUG
101            nsresult rv =
102#endif
103                fullJarURI->GetAsciiSpec(mJarDirSpec);
104            NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail");
105        }
106    }
107
108    virtual ~nsJARInputThunk()
109    {
110        if (!mJarCache && mJarReader)
111            mJarReader->Close();
112    }
113
114    void GetJarReader(nsIZipReader **result)
115    {
116        NS_IF_ADDREF(*result = mJarReader);
117    }
118
119    PRInt32 GetContentLength()
120    {
121        return mContentLength;
122    }
123
124    nsresult EnsureJarStream();
125
126private:
127
128    nsCOMPtr<nsIZipReaderCache> mJarCache;
129    nsCOMPtr<nsIZipReader>      mJarReader;
130    nsCString                   mJarDirSpec;
131    nsCOMPtr<nsIInputStream>    mJarStream;
132    nsCString                   mJarEntry;
133    PRInt32                     mContentLength;
134};
135
136NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputThunk, nsIInputStream)
137
138nsresult
139nsJARInputThunk::EnsureJarStream()
140{
141    if (mJarStream)
142        return NS_OK;
143
144    nsresult rv;
145    if (ENTRY_IS_DIRECTORY(mJarEntry)) {
146        // A directory stream also needs the Spec of the FullJarURI
147        // because is included in the stream data itself.
148
149        NS_ENSURE_STATE(!mJarDirSpec.IsEmpty());
150
151        rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec,
152                                                mJarEntry,
153                                                getter_AddRefs(mJarStream));
154    }
155    else {
156        rv = mJarReader->GetInputStream(mJarEntry,
157                                        getter_AddRefs(mJarStream));
158    }
159    if (NS_FAILED(rv)) {
160        // convert to the proper result if the entry wasn't found
161        // so that error pages work
162        if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
163            rv = NS_ERROR_FILE_NOT_FOUND;
164        return rv;
165    }
166
167    // ask the JarStream for the content length
168    rv = mJarStream->Available((PRUint32 *) &mContentLength);
169    if (NS_FAILED(rv)) return rv;
170
171    return NS_OK;
172}
173
174NS_IMETHODIMP
175nsJARInputThunk::Close()
176{
177    if (mJarStream)
178        return mJarStream->Close();
179
180    return NS_OK;
181}
182
183NS_IMETHODIMP
184nsJARInputThunk::Available(PRUint32 *avail)
185{
186    nsresult rv = EnsureJarStream();
187    if (NS_FAILED(rv)) return rv;
188
189    return mJarStream->Available(avail);
190}
191
192NS_IMETHODIMP
193nsJARInputThunk::Read(char *buf, PRUint32 count, PRUint32 *countRead)
194{
195    nsresult rv = EnsureJarStream();
196    if (NS_FAILED(rv)) return rv;
197
198    return mJarStream->Read(buf, count, countRead);
199}
200
201NS_IMETHODIMP
202nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer, void *closure,
203                              PRUint32 count, PRUint32 *countRead)
204{
205    // stream transport does only calls Read()
206    return NS_ERROR_NOT_IMPLEMENTED;
207}
208
209NS_IMETHODIMP
210nsJARInputThunk::IsNonBlocking(bool *nonBlocking)
211{
212    *nonBlocking = false;
213    return NS_OK;
214}
215
216//-----------------------------------------------------------------------------
217// nsJARChannel
218//-----------------------------------------------------------------------------
219
220
221nsJARChannel::nsJARChannel()
222    : mContentLength(-1)
223    , mLoadFlags(LOAD_NORMAL)
224    , mStatus(NS_OK)
225    , mIsPending(false)
226    , mIsUnsafe(true)
227    , mJarInput(nsnull)
228{
229#if defined(PR_LOGGING)
230    if (!gJarProtocolLog)
231        gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
232#endif
233
234    // hold an owning reference to the jar handler
235    NS_ADDREF(gJarHandler);
236}
237
238nsJARChannel::~nsJARChannel()
239{
240    // with the exception of certain error cases mJarInput will already be null.
241    NS_IF_RELEASE(mJarInput);
242
243    // release owning reference to the jar handler
244    nsJARProtocolHandler *handler = gJarHandler;
245    NS_RELEASE(handler); // NULL parameter
246}
247
248NS_IMPL_ISUPPORTS_INHERITED6(nsJARChannel,
249                             nsHashPropertyBag,
250                             nsIRequest,
251                             nsIChannel,
252                             nsIStreamListener,
253                             nsIRequestObserver,
254                             nsIDownloadObserver,
255                             nsIJARChannel)
256
257nsresult 
258nsJARChannel::Init(nsIURI *uri)
259{
260    nsresult rv;
261    rv = nsHashPropertyBag::Init();
262    if (NS_FAILED(rv))
263        return rv;
264
265    mJarURI = do_QueryInterface(uri, &rv);
266    if (NS_FAILED(rv))
267        return rv;
268
269    mOriginalURI = mJarURI;
270
271    // Prevent loading jar:javascript URIs (see bug 290982).
272    nsCOMPtr<nsIURI> innerURI;
273    rv = mJarURI->GetJARFile(getter_AddRefs(innerURI));
274    if (NS_FAILED(rv))
275        return rv;
276    bool isJS;
277    rv = innerURI->SchemeIs("javascript", &isJS);
278    if (NS_FAILED(rv))
279        return rv;
280    if (isJS) {
281        NS_WARNING("blocking jar:javascript:");
282        return NS_ERROR_INVALID_ARG;
283    }
284
285#if defined(PR_LOGGING)
286    mJarURI->GetSpec(mSpec);
287#endif
288    return rv;
289}
290
291nsresult
292nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache)
293{
294    // important to pass a clone of the file since the nsIFile impl is not
295    // necessarily MT-safe
296    nsCOMPtr<nsIFile> clonedFile;
297    nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile));
298    if (NS_FAILED(rv))
299        return rv;
300
301    nsCOMPtr<nsIZipReader> reader;
302    if (jarCache) {
303        if (mInnerJarEntry.IsEmpty())
304            rv = jarCache->GetZip(mJarFile, getter_AddRefs(reader));
305        else 
306            rv = jarCache->GetInnerZip(mJarFile, mInnerJarEntry,
307                                       getter_AddRefs(reader));
308    } else {
309        // create an uncached jar reader
310        nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
311        if (NS_FAILED(rv))
312            return rv;
313
314        rv = outerReader->Open(mJarFile);
315        if (NS_FAILED(rv))
316            return rv;
317
318        if (mInnerJarEntry.IsEmpty())
319            reader = outerReader;
320        else {
321            reader = do_CreateInstance(kZipReaderCID, &rv);
322            if (NS_FAILED(rv))
323                return rv;
324
325            rv = reader->OpenInner(outerReader, mInnerJarEntry);
326        }
327    }
328    if (NS_FAILED(rv))
329        return rv;
330
331    mJarInput = new nsJARInputThunk(reader, mJarURI, mJarEntry, jarCache);
332    if (!mJarInput)
333        return NS_ERROR_OUT_OF_MEMORY;
334    NS_ADDREF(mJarInput);
335    return NS_OK;
336}
337
338nsresult
339nsJARChannel::EnsureJarInput(bool blocking)
340{
341    LOG(("nsJARChannel::EnsureJarInput [this=%x %s]\n", this, mSpec.get()));
342
343    nsresult rv;
344    nsCOMPtr<nsIURI> uri;
345
346    rv = mJarURI->GetJARFile(getter_AddRefs(mJarBaseURI));
347    if (NS_FAILED(rv)) return rv;
348
349    rv = mJarURI->GetJAREntry(mJarEntry);
350    if (NS_FAILED(rv)) return rv;
351
352    // The name of the JAR entry must not contain URL-escaped characters:
353    // we're moving from URL domain to a filename domain here. nsStandardURL
354    // does basic escaping by default, which breaks reading zipped files which
355    // have e.g. spaces in their filenames.
356    NS_UnescapeURL(mJarEntry);
357
358    // try to get a nsIFile directly from the url, which will often succeed.
359    {
360        nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
361        if (fileURL)
362            fileURL->GetFile(getter_AddRefs(mJarFile));
363    }
364    // try to handle a nested jar
365    if (!mJarFile) {
366        nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
367        if (jarURI) {
368            nsCOMPtr<nsIFileURL> fileURL;
369            nsCOMPtr<nsIURI> innerJarURI;
370            rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
371            if (NS_SUCCEEDED(rv))
372                fileURL = do_QueryInterface(innerJarURI);
373            if (fileURL) {
374                fileURL->GetFile(getter_AddRefs(mJarFile));
375                jarURI->GetJAREntry(mInnerJarEntry);
376            }
377        }
378    }
379
380    if (mJarFile) {
381        mIsUnsafe = false;
382
383        // NOTE: we do not need to deal with mSecurityInfo here,
384        // because we're loading from a local file
385        rv = CreateJarInput(gJarHandler->JarCache());
386    }
387    else if (blocking) {
388        NS_NOTREACHED("need sync downloader");
389        rv = NS_ERROR_NOT_IMPLEMENTED;
390    }
391    else {
392        // kick off an async download of the base URI...
393        rv = NS_NewDownloader(getter_AddRefs(mDownloader), this);
394        if (NS_SUCCEEDED(rv))
395            rv = NS_OpenURI(mDownloader, nsnull, mJarBaseURI, nsnull,
396                            mLoadGroup, mCallbacks,
397                            mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
398    }
399    return rv;
400
401}
402
403//-----------------------------------------------------------------------------
404// nsIRequest
405//-----------------------------------------------------------------------------
406
407NS_IMETHODIMP
408nsJARChannel::GetName(nsACString &result)
409{
410    return mJarURI->GetSpec(result);
411}
412
413NS_IMETHODIMP
414nsJARChannel::IsPending(bool *result)
415{
416    *result = mIsPending;
417    return NS_OK;
418}
419
420NS_IMETHODIMP
421nsJARChannel::GetStatus(nsresult *status)
422{
423    if (mPump && NS_SUCCEEDED(mStatus))
424        mPump->GetStatus(status);
425    else
426        *status = mStatus;
427    return NS_OK;
428}
429
430NS_IMETHODIMP
431nsJARChannel::Cancel(nsresult status)
432{
433    mStatus = status;
434    if (mPump)
435        return mPump->Cancel(status);
436
437    NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
438    return NS_OK;
439}
440
441NS_IMETHODIMP
442nsJARChannel::Suspend()
443{
444    if (mPump)
445        return mPump->Suspend();
446
447    NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
448    return NS_OK;
449}
450
451NS_IMETHODIMP
452nsJARChannel::Resume()
453{
454    if (mPump)
455        return mPump->Resume();
456
457    NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
458    return NS_OK;
459}
460
461NS_IMETHODIMP
462nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
463{
464    *aLoadFlags = mLoadFlags;
465    return NS_OK;
466}
467
468NS_IMETHODIMP
469nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
470{
471    mLoadFlags = aLoadFlags;
472    return NS_OK;
473}
474
475NS_IMETHODIMP
476nsJARChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
477{
478    NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
479    return NS_OK;
480}
481
482NS_IMETHODIMP
483nsJARChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
484{
485    mLoadGroup = aLoadGroup;
486    return NS_OK;
487}
488
489//-----------------------------------------------------------------------------
490// nsIChannel
491//-----------------------------------------------------------------------------
492
493NS_IMETHODIMP
494nsJARChannel::GetOriginalURI(nsIURI **aURI)
495{
496    *aURI = mOriginalURI;
497    NS_ADDREF(*aURI);
498    return NS_OK;
499}
500
501NS_IMETHODIMP
502nsJARChannel::SetOriginalURI(nsIURI *aURI)
503{
504    NS_ENSURE_ARG_POINTER(aURI);
505    mOriginalURI = aURI;
506    return NS_OK;
507}
508
509NS_IMETHODIMP
510nsJARChannel::GetURI(nsIURI **aURI)
511{
512    NS_IF_ADDREF(*aURI = mJarURI);
513    return NS_OK;
514}
515
516NS_IMETHODIMP
517nsJARChannel::GetOwner(nsISupports **result)
518{
519    nsresult rv;
520
521    if (mOwner) {
522        NS_ADDREF(*result = mOwner);
523        return NS_OK;
524    }
525
526    if (!mJarInput) {
527        *result = nsnull;
528        return NS_OK;
529    }
530
531    //-- Verify signature, if one is present, and set owner accordingly
532    nsCOMPtr<nsIZipReader> jarReader;
533    mJarInput->GetJarReader(getter_AddRefs(jarReader));
534    if (!jarReader)
535        return NS_ERROR_NOT_INITIALIZED;
536
537    nsCOMPtr<nsIPrincipal> cert;
538    rv = jarReader->GetCertificatePrincipal(mJarEntry, getter_AddRefs(cert));
539    if (NS_FAILED(rv)) return rv;
540
541    if (cert) {
542        nsCAutoString certFingerprint;
543        rv = cert->GetFingerprint(certFingerprint);
544        if (NS_FAILED(rv)) return rv;
545
546        nsCAutoString subjectName;
547        rv = cert->GetSubjectName(subjectName);
548        if (NS_FAILED(rv)) return rv;
549
550        nsCAutoString prettyName;
551        rv = cert->GetPrettyName(prettyName);
552        if (NS_FAILED(rv)) return rv;
553
554        nsCOMPtr<nsISupports> certificate;
555        rv = cert->GetCertificate(getter_AddRefs(certificate));
556        if (NS_FAILED(rv)) return rv;
557        
558        nsCOMPtr<nsIScriptSecurityManager> secMan = 
559                 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
560        if (NS_FAILED(rv)) return rv;
561
562        rv = secMan->GetCertificatePrincipal(certFingerprint, subjectName,
563                                             prettyName, certificate,
564                                             mJarBaseURI,
565                                             getter_AddRefs(cert));
566        if (NS_FAILED(rv)) return rv;
567
568        mOwner = do_QueryInterface(cert, &rv);
569        if (NS_FAILED(rv)) return rv;
570
571        NS_ADDREF(*result = mOwner);
572    }
573    return NS_OK;
574}
575
576NS_IMETHODIMP
577nsJARChannel::SetOwner(nsISupports *aOwner)
578{
579    mOwner = aOwner;
580    return NS_OK;
581}
582
583NS_IMETHODIMP
584nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
585{
586    NS_IF_ADDREF(*aCallbacks = mCallbacks);
587    return NS_OK;
588}
589
590NS_IMETHODIMP
591nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
592{
593    mCallbacks = aCallbacks;
594    return NS_OK;
595}
596
597NS_IMETHODIMP 
598nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
599{
600    NS_PRECONDITION(aSecurityInfo, "Null out param");
601    NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
602    return NS_OK;
603}
604
605NS_IMETHODIMP
606nsJARChannel::GetContentType(nsACString &result)
607{
608    if (mContentType.IsEmpty()) {
609        //
610        // generate content type and set it
611        //
612        const char *ext = nsnull, *fileName = mJarEntry.get();
613        PRInt32 len = mJarEntry.Length();
614
615        // check if we're displaying a directory
616        // mJarEntry will be empty if we're trying to display
617        // the topmost directory in a zip, e.g. jar:foo.zip!/
618        if (ENTRY_IS_DIRECTORY(mJarEntry)) {
619            mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
620        }
621        else {
622            // not a directory, take a guess by its extension
623            for (PRInt32 i = len-1; i >= 0; i--) {
624                if (fileName[i] == '.') {
625                    ext = &fileName[i + 1];
626                    break;
627                }
628            }
629            if (ext) {
630                nsIMIMEService *mimeServ = gJarHandler->MimeService();
631                if (mimeServ)
632                    mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType);
633            }
634            if (mContentType.IsEmpty())
635                mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
636        }
637    }
638    result = mContentType;
639    return NS_OK;
640}
641
642NS_IMETHODIMP
643nsJARChannel::SetContentType(const nsACString &aContentType)
644{
645    // If someone gives us a type hint we should just use that type instead of
646    // doing our guessing.  So we don't care when this is being called.
647
648    // mContentCharset is unchanged if not parsed
649    NS_ParseContentType(aContentType, mContentType, mContentCharset);
650    return NS_OK;
651}
652
653NS_IMETHODIMP
654nsJARChannel::GetContentCharset(nsACString &aContentCharset)
655{
656    // If someone gives us a charset hint we should just use that charset.
657    // So we don't care when this is being called.
658    aContentCharset = mContentCharset;
659    return NS_OK;
660}
661
662NS_IMETHODIMP
663nsJARChannel::SetContentCharset(const nsACString &aContentCharset)
664{
665    mContentCharset = aContentCharset;
666    return NS_OK;
667}
668
669NS_IMETHODIMP
670nsJARChannel::GetContentDisposition(PRUint32 *aContentDisposition)
671{
672    if (mContentDispositionHeader.IsEmpty())
673        return NS_ERROR_NOT_AVAILABLE;
674
675    *aContentDisposition = mContentDisposition;
676    return NS_OK;
677}
678
679NS_IMETHODIMP
680nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
681{
682    return NS_ERROR_NOT_AVAILABLE;
683}
684
685NS_IMETHODIMP
686nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
687{
688    if (mContentDispositionHeader.IsEmpty())
689        return NS_ERROR_NOT_AVAILABLE;
690
691    aContentDispositionHeader = mContentDispositionHeader;
692    return NS_OK;
693}
694
695NS_IMETHODIMP
696nsJARChannel::GetContentLength(PRInt32 *result)
697{
698    // if content length is unknown, query mJarInput...
699    if (mContentLength < 0 && mJarInput)
700        mContentLength = mJarInput->GetContentLength();
701
702    *result = mContentLength;
703    return NS_OK;
704}
705
706NS_IMETHODIMP
707nsJARChannel::SetContentLength(PRInt32 aContentLength)
708{
709    // XXX does this really make any sense at all?
710    mContentLength = aContentLength;
711    return NS_OK;
712}
713
714NS_IMETHODIMP
715nsJARChannel::Open(nsIInputStream **stream)
716{
717    LOG(("nsJARChannel::Open [this=%x]\n", this));
718
719    NS_ENSURE_TRUE(!mJarInput, NS_ERROR_IN_PROGRESS);
720    NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
721
722    mJarFile = nsnull;
723    mIsUnsafe = true;
724
725    nsresult rv = EnsureJarInput(true);
726    if (NS_FAILED(rv)) return rv;
727
728    if (!mJarInput)
729        return NS_ERROR_UNEXPECTED;
730
731    // force load the jar file now so GetContentLength will return a
732    // meaningful value once we return.
733    rv = mJarInput->EnsureJarStream();
734    if (NS_FAILED(rv)) return rv;
735
736    NS_ADDREF(*stream = mJarInput);
737    return NS_OK;
738}
739
740NS_IMETHODIMP
741nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
742{
743    LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
744
745    NS_ENSURE_ARG_POINTER(listener);
746    NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
747
748    mJarFile = nsnull;
749    mIsUnsafe = true;
750
751    // Initialize mProgressSink
752    NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
753
754    nsresult rv = EnsureJarInput(false);
755    if (NS_FAILED(rv)) return rv;
756
757    // These variables must only be set if we're going to trigger an
758    // OnStartRequest, either from AsyncRead or OnDownloadComplete.
759    mListener = listener;
760    mListenerContext = ctx;
761    mIsPending = true;
762    if (mJarInput) {
763        // create input stream pump and call AsyncRead as a block
764        rv = NS_NewInputStreamPump(getter_AddRefs(mPump), mJarInput);
765        if (NS_SUCCEEDED(rv))
766            rv = mPump->AsyncRead(this, nsnull);
767
768        // If we failed to create the pump or initiate the AsyncRead,
769        // then we need to clear these variables.
770        if (NS_FAILED(rv)) {
771            mIsPending = false;
772            mListenerContext = nsnull;
773            mListener = nsnull;
774            return rv;
775        }
776    }
777
778    if (mLoadGroup)
779        mLoadGroup->AddRequest(this, nsnull);
780
781    return NS_OK;
782}
783
784//-----------------------------------------------------------------------------
785// nsIJARChannel
786//-----------------------------------------------------------------------------
787NS_IMETHODIMP
788nsJARChannel::GetIsUnsafe(bool *isUnsafe)
789{
790    *isUnsafe = mIsUnsafe;
791    return NS_OK;
792}
793
794//-----------------------------------------------------------------------------
795// nsIDownloadObserver
796//-----------------------------------------------------------------------------
797
798NS_IMETHODIMP
799nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
800                                 nsIRequest    *request,
801                                 nsISupports   *context,
802                                 nsresult       status,
803                                 nsIFile       *file)
804{
805    nsresult rv;
806
807    nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
808    if (channel) {
809        PRUint32 loadFlags;
810        channel->GetLoadFlags(&loadFlags);
811        if (loadFlags & LOAD_REPLACE) {
812            mLoadFlags |= LOAD_REPLACE;
813
814            if (!mOriginalURI) {
815                SetOriginalURI(mJarURI);
816            }
817
818            nsCOMPtr<nsIURI> innerURI;
819            rv = channel->GetURI(getter_AddRefs(innerURI));
820            if (NS_SUCCEEDED(rv)) {
821                nsCOMPtr<nsIJARURI> newURI;
822                rv = mJarURI->CloneWithJARFile(innerURI,
823                                               getter_AddRefs(newURI));
824                if (NS_SUCCEEDED(rv)) {
825                    mJarURI = newURI;
826                }
827            }
828            if (NS_SUCCEEDED(status)) {
829                status = rv;
830            }
831        }
832    }
833
834    if (NS_SUCCEEDED(status) && channel) {
835        // Grab the security info from our base channel
836        channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
837
838        nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
839        if (httpChannel) {
840            // We only want to run scripts if the server really intended to
841            // send us a JAR file.  Check the server-supplied content type for
842            // a JAR type.
843            nsCAutoString header;
844            httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
845                                           header);
846            nsCAutoString contentType;
847            nsCAutoString charset;
848            NS_ParseContentType(header, contentType, charset);
849            nsCAutoString channelContentType;
850            channel->GetContentType(channelContentType);
851            mIsUnsafe = !(contentType.Equals(channelContentType) &&
852                          (contentType.EqualsLiteral("application/java-archive") ||
853                           contentType.EqualsLiteral("application/x-jar")));
854        } else {
855            nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
856            if (innerJARChannel) {
857                bool unsafe;
858                innerJARChannel->GetIsUnsafe(&unsafe);
859                mIsUnsafe = unsafe;
860            }
861        }
862
863        channel->GetContentDispositionHeader(mContentDispositionHeader);
864        mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
865    }
866
867    if (NS_SUCCEEDED(status) && mIsUnsafe &&
868        !Preferences::GetBool("network.jar.open-unsafe-types", false)) {
869        status = NS_ERROR_UNSAFE_CONTENT_TYPE;
870    }
871
872    if (NS_SUCCEEDED(status)) {
873        // Refuse to unpack view-source: jars even if open-unsafe-types is set.
874        nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(channel);
875        if (viewSource) {
876            status = NS_ERROR_UNSAFE_CONTENT_TYPE;
877        }
878    }
879
880    if (NS_SUCCEEDED(status)) {
881        mJarFile = file;
882    
883        rv = CreateJarInput(nsnull);
884        if (NS_SUCCEEDED(rv)) {
885            // create input stream pump
886            rv = NS_NewInputStreamPump(getter_AddRefs(mPump), mJarInput);
887            if (NS_SUCCEEDED(rv))
888                rv = mPump->AsyncRead(this, nsnull);
889        }
890        status = rv;
891    }
892
893    if (NS_FAILED(status)) {
894        mStatus = status;
895        OnStartRequest(nsnull, nsnull);
896        OnStopRequest(nsnull, nsnull, status);
897    }
898
899    return NS_OK;
900}
901
902//-----------------------------------------------------------------------------
903// nsIStreamListener
904//-----------------------------------------------------------------------------
905
906NS_IMETHODIMP
907nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
908{
909    LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
910
911    return mListener->OnStartRequest(this, mListenerContext);
912}
913
914NS_IMETHODIMP
915nsJARChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
916{
917    LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n",
918        this, mSpec.get(), status));
919
920    if (NS_SUCCEEDED(mStatus))
921        mStatus = status;
922
923    if (mListener) {
924        mListener->OnStopRequest(this, mListenerContext, status);
925        mListener = 0;
926        mListenerContext = 0;
927    }
928
929    if (mLoadGroup)
930        mLoadGroup->RemoveRequest(this, nsnull, status);
931
932    mPump = 0;
933    NS_IF_RELEASE(mJarInput);
934    mIsPending = false;
935    mDownloader = 0; // this may delete the underlying jar file
936
937    // Drop notification callbacks to prevent cycles.
938    mCallbacks = 0;
939    mProgressSink = 0;
940
941    return NS_OK;
942}
943
944NS_IMETHODIMP
945nsJARChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx,
946                               nsIInputStream *stream,
947                               PRUint32 offset, PRUint32 count)
948{
949#if defined(PR_LOGGING)
950    LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec.get()));
951#endif
952
953    nsresult rv;
954
955    rv = mListener->OnDataAvailable(this, mListenerContext, stream, offset, count);
956
957    // simply report progress here instead of hooking ourselves up as a
958    // nsITransportEventSink implementation.
959    // XXX do the 64-bit stuff for real
960    if (mProgressSink && NS_SUCCEEDED(rv) && !(mLoadFlags & LOAD_BACKGROUND))
961        mProgressSink->OnProgress(this, nsnull, PRUint64(offset + count),
962                                  PRUint64(mContentLength));
963
964    return rv; // let the pump cancel on failure
965}