Bug 1161684 - Allow JAR channels to be intercepted by service workers. r=jdm

This commit is contained in:
Fernando Jimenez 2015-05-22 09:32:25 +02:00
parent c3719b6b08
commit 0d44288db9
7 changed files with 387 additions and 53 deletions

View File

@ -14,6 +14,7 @@
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIJARChannel.h"
#include "nsINetworkInterceptController.h"
#include "nsIMutableArray.h"
#include "nsIUploadChannel2.h"
@ -2594,60 +2595,70 @@ public:
rv = uri->GetSpec(mSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
NS_ENSURE_TRUE(httpChannel, NS_ERROR_NOT_AVAILABLE);
rv = httpChannel->GetRequestMethod(mMethod);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t loadFlags;
rv = channel->GetLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
uint32_t mode;
internalChannel->GetCorsMode(&mode);
switch (mode) {
case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
mRequestMode = RequestMode::Same_origin;
break;
case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
mRequestMode = RequestMode::No_cors;
break;
case nsIHttpChannelInternal::CORS_MODE_CORS:
case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
mRequestMode = RequestMode::Cors;
break;
default:
MOZ_CRASH("Unexpected CORS mode");
}
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
mRequestCredentials = RequestCredentials::Omit;
} else {
bool includeCrossOrigin;
internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
if (includeCrossOrigin) {
mRequestCredentials = RequestCredentials::Include;
}
}
rv = httpChannel->VisitRequestHeaders(this);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadInfo> loadInfo;
rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
NS_ENSURE_SUCCESS(rv, rv);
mContentPolicyType = loadInfo->GetContentPolicyType();
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
if (uploadChannel) {
MOZ_ASSERT(!mUploadStream);
rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
if (httpChannel) {
rv = httpChannel->GetRequestMethod(mMethod);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
uint32_t mode;
internalChannel->GetCorsMode(&mode);
switch (mode) {
case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
mRequestMode = RequestMode::Same_origin;
break;
case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
mRequestMode = RequestMode::No_cors;
break;
case nsIHttpChannelInternal::CORS_MODE_CORS:
case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
mRequestMode = RequestMode::Cors;
break;
default:
MOZ_CRASH("Unexpected CORS mode");
}
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
mRequestCredentials = RequestCredentials::Omit;
} else {
bool includeCrossOrigin;
internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
if (includeCrossOrigin) {
mRequestCredentials = RequestCredentials::Include;
}
}
rv = httpChannel->VisitRequestHeaders(this);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
if (uploadChannel) {
MOZ_ASSERT(!mUploadStream);
rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
// If it is not an HTTP channel it must be a JAR one.
NS_ENSURE_TRUE(jarChannel, NS_ERROR_NOT_AVAILABLE);
mMethod = "GET";
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
mRequestCredentials = RequestCredentials::Omit;
}
}
return NS_OK;

View File

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "InterceptedJARChannel.h"
#include "nsIPipe.h"
using namespace mozilla::net;
NS_IMPL_ISUPPORTS(InterceptedJARChannel, nsIInterceptedChannel)
InterceptedJARChannel::InterceptedJARChannel(nsJARChannel* aChannel,
nsINetworkInterceptController* aController,
bool aIsNavigation)
: mController(aController)
, mChannel(aChannel)
, mIsNavigation(aIsNavigation)
{
}
NS_IMETHODIMP
InterceptedJARChannel::GetResponseBody(nsIOutputStream** aStream)
{
NS_IF_ADDREF(*aStream = mResponseBody);
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::GetIsNavigation(bool* aIsNavigation)
{
*aIsNavigation = mIsNavigation;
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::GetChannel(nsIChannel** aChannel)
{
NS_IF_ADDREF(*aChannel = mChannel);
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::ResetInterception()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
mResponseBody = nullptr;
mSynthesizedInput = nullptr;
mChannel->ResetInterception();
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::SynthesizeStatus(uint16_t aStatus,
const nsACString& aReason)
{
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::SynthesizeHeader(const nsACString& aName,
const nsACString& aValue)
{
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::FinishSynthesizedResponse()
{
if (NS_WARN_IF(!mChannel)) {
return NS_ERROR_NOT_AVAILABLE;
}
mChannel->OverrideWithSynthesizedResponse(mSynthesizedInput);
mResponseBody = nullptr;
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::Cancel()
{
if (!mChannel) {
return NS_ERROR_FAILURE;
}
nsresult rv = mChannel->Cancel(NS_BINDING_ABORTED);
NS_ENSURE_SUCCESS(rv, rv);
mResponseBody = nullptr;
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedJARChannel::SetSecurityInfo(nsISupports* aSecurityInfo)
{
if (!mChannel) {
return NS_ERROR_FAILURE;
}
return mChannel->OverrideSecurityInfo(aSecurityInfo);
}
void
InterceptedJARChannel::NotifyController()
{
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
getter_AddRefs(mResponseBody),
0, UINT32_MAX, true, true);
NS_ENSURE_SUCCESS_VOID(rv);
rv = mController->ChannelIntercepted(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
rv = ResetInterception();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
"Failed to resume intercepted network request");
}
mController = nullptr;
}

View File

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef InterceptedJARChannel_h
#define InterceptedJARChannel_h
#include "nsJAR.h"
#include "nsJARChannel.h"
#include "nsIInputStream.h"
#include "nsIInputStreamPump.h"
#include "nsINetworkInterceptController.h"
#include "nsIOutputStream.h"
#include "nsRefPtr.h"
#include "mozilla/Maybe.h"
class nsIStreamListener;
class nsJARChannel;
namespace mozilla {
namespace net {
// An object representing a channel that has been intercepted. This avoids
// complicating the actual channel implementation with the details of
// synthesizing responses.
class InterceptedJARChannel : public nsIInterceptedChannel
{
// The interception controller to notify about the successful channel
// interception.
nsCOMPtr<nsINetworkInterceptController> mController;
// The actual channel being intercepted.
nsRefPtr<nsJARChannel> mChannel;
// Reader-side of the synthesized response body.
nsCOMPtr<nsIInputStream> mSynthesizedInput;
// The stream to write the body of the synthesized response.
nsCOMPtr<nsIOutputStream> mResponseBody;
// Wether this intercepted channel was performing a navigation.
bool mIsNavigation;
virtual ~InterceptedJARChannel() {};
public:
InterceptedJARChannel(nsJARChannel* aChannel,
nsINetworkInterceptController* aController,
bool aIsNavigation);
NS_DECL_ISUPPORTS
NS_DECL_NSIINTERCEPTEDCHANNEL
void NotifyController();
};
} // namespace net
} // namespace mozilla
#endif // InterceptedJARChannel_h

View File

@ -23,12 +23,14 @@ XPIDL_SOURCES += [
XPIDL_MODULE = 'jar'
EXPORTS += [
'InterceptedJARChannel.h',
'nsJARURI.h',
'nsZipArchive.h',
'zipstruct.h',
]
UNIFIED_SOURCES += [
'InterceptedJARChannel.cpp',
'nsJARProtocolHandler.cpp',
'nsJARURI.cpp',
]

View File

@ -24,6 +24,9 @@
#include "mozilla/net/RemoteOpenFileChild.h"
#include "nsITabChild.h"
#include "private/pprio.h"
#include "nsINetworkInterceptController.h"
#include "InterceptedJARChannel.h"
#include "nsInputStreamPump.h"
using namespace mozilla;
using namespace mozilla::net;
@ -199,6 +202,7 @@ nsJARChannel::nsJARChannel()
, mIsUnsafe(true)
, mOpeningRemote(false)
, mEnsureChildFd(false)
, mSynthesizedStreamLength(0)
{
if (!gJarProtocolLog)
gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
@ -234,7 +238,7 @@ NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel,
nsIThreadRetargetableStreamListener,
nsIJARChannel)
nsresult
nsresult
nsJARChannel::Init(nsIURI *uri)
{
nsresult rv;
@ -530,6 +534,8 @@ nsJARChannel::GetStatus(nsresult *status)
{
if (mPump && NS_SUCCEEDED(mStatus))
mPump->GetStatus(status);
else if (mSynthesizedResponsePump && NS_SUCCEEDED(mStatus))
mSynthesizedResponsePump->GetStatus(status);
else
*status = mStatus;
return NS_OK;
@ -541,6 +547,8 @@ nsJARChannel::Cancel(nsresult status)
mStatus = status;
if (mPump)
return mPump->Cancel(status);
if (mSynthesizedResponsePump)
return mSynthesizedResponsePump->Cancel(status);
NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
return NS_OK;
@ -551,6 +559,8 @@ nsJARChannel::Suspend()
{
if (mPump)
return mPump->Suspend();
if (mSynthesizedResponsePump)
return mSynthesizedResponsePump->Suspend();
NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
return NS_OK;
@ -561,6 +571,8 @@ nsJARChannel::Resume()
{
if (mPump)
return mPump->Resume();
if (mSynthesizedResponsePump)
return mSynthesizedResponsePump->Resume();
NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
return NS_OK;
@ -670,7 +682,20 @@ nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
return NS_OK;
}
NS_IMETHODIMP
nsresult
nsJARChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
{
MOZ_RELEASE_ASSERT(!mSecurityInfo,
"This can only be called when we don't have a security info object already");
MOZ_RELEASE_ASSERT(aSecurityInfo,
"This can only be called with a valid security info object");
MOZ_RELEASE_ASSERT(ShouldIntercept(),
"This can only be called on channels that can be intercepted");
mSecurityInfo = aSecurityInfo;
return NS_OK;
}
NS_IMETHODIMP
nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
{
NS_PRECONDITION(aSecurityInfo, "Null out param");
@ -836,6 +861,66 @@ nsJARChannel::Open(nsIInputStream **stream)
return NS_OK;
}
bool
nsJARChannel::ShouldIntercept()
{
LOG(("nsJARChannel::ShouldIntercept [this=%x]\n", this));
// We only intercept app:// requests
if (!mAppURI) {
return false;
}
nsCOMPtr<nsINetworkInterceptController> controller;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsINetworkInterceptController),
getter_AddRefs(controller));
bool shouldIntercept = false;
if (controller) {
bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI;
nsresult rv = controller->ShouldPrepareForIntercept(mAppURI,
isNavigation,
&shouldIntercept);
NS_ENSURE_SUCCESS(rv, false);
}
return shouldIntercept;
}
void nsJARChannel::ResetInterception()
{
LOG(("nsJARChannel::ResetInterception [this=%x]\n", this));
// Continue with the origin request.
nsresult rv = ContinueAsyncOpen();
NS_ENSURE_SUCCESS_VOID(rv);
}
void
nsJARChannel::OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput)
{
// In our current implementation, the FetchEvent handler will copy the
// response stream completely into the pipe backing the input stream so we
// can treat the available as the length of the stream.
uint64_t available;
nsresult rv = aSynthesizedInput->Available(&available);
if (NS_WARN_IF(NS_FAILED(rv))) {
mSynthesizedStreamLength = -1;
} else {
mSynthesizedStreamLength = int64_t(available);
}
rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
aSynthesizedInput,
int64_t(-1), int64_t(-1), 0, 0, true);
if (NS_WARN_IF(NS_FAILED(rv))) {
aSynthesizedInput->Close();
return;
}
rv = mSynthesizedResponsePump->AsyncRead(this, nullptr);
NS_ENSURE_SUCCESS_VOID(rv);
}
NS_IMETHODIMP
nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
{
@ -851,18 +936,40 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
// Initialize mProgressSink
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
nsresult rv = LookupFile(true);
if (NS_FAILED(rv))
return rv;
// These variables must only be set if we're going to trigger an
// OnStartRequest, either from AsyncRead or OnDownloadComplete.
//
// That means: Do not add early return statements beyond this point!
mListener = listener;
mListenerContext = ctx;
mIsPending = true;
// Check if this channel should intercept the network request and prepare
// for a possible synthesized response instead.
if (ShouldIntercept()) {
nsCOMPtr<nsINetworkInterceptController> controller;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsINetworkInterceptController),
getter_AddRefs(controller));
bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI;
nsRefPtr<InterceptedJARChannel> intercepted =
new InterceptedJARChannel(this, controller, isNavigation);
intercepted->NotifyController();
return NS_OK;
}
return ContinueAsyncOpen();
}
nsresult
nsJARChannel::ContinueAsyncOpen()
{
LOG(("nsJARChannel::ContinueAsyncOpen [this=%x]\n", this));
nsresult rv = LookupFile(true);
if (NS_FAILED(rv)) {
mIsPending = false;
mListenerContext = nullptr;
mListener = nullptr;
return rv;
}
nsCOMPtr<nsIChannel> channel;
if (!mJarFile) {

View File

@ -10,6 +10,7 @@
#include "nsIJARChannel.h"
#include "nsIJARURI.h"
#include "nsIInputStreamPump.h"
#include "InterceptedJARChannel.h"
#include "nsIInterfaceRequestor.h"
#include "nsIProgressEventSink.h"
#include "nsIStreamListener.h"
@ -27,6 +28,13 @@
#include "mozilla/Logging.h"
class nsJARInputThunk;
class nsInputStreamPump;
namespace mozilla {
namespace net {
class InterceptedJARChannel;
} // namespace net
} // namespace mozilla
//-----------------------------------------------------------------------------
@ -69,6 +77,18 @@ private:
mozilla::net::MemoryDownloader::Data aData)
override;
// Returns true if this channel should intercept the network request and
// prepare for a possible synthesized response instead.
bool ShouldIntercept();
nsresult ContinueAsyncOpen();
// Discard the prior interception and continue with the original network
// request.
void ResetInterception();
// Override this channel's pending response with a synthesized one. The
// content will be asynchronously read from the pump.
void OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput);
nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
nsCString mSpec;
bool mOpened;
@ -107,6 +127,11 @@ private:
nsCOMPtr<nsIURI> mJarBaseURI;
nsCString mJarEntry;
nsCString mInnerJarEntry;
nsRefPtr<nsInputStreamPump> mSynthesizedResponsePump;
int64_t mSynthesizedStreamLength;
friend class mozilla::net::InterceptedJARChannel;
};
#endif // nsJARChannel_h__

View File

@ -144,6 +144,7 @@ EXPORTS += [
'nsASocketHandler.h',
'nsAsyncRedirectVerifyHelper.h',
'nsFileStreams.h',
'nsInputStreamPump.h',
'nsMIMEInputStream.h',
'nsNetUtil.h',
'nsReadLine.h',