Bug 451958. Support Access-Controls for media loads. r+sr=roc

--HG--
extra : rebase_source : 4a180379a401040f16c4fce80ef116da139ba2cb
This commit is contained in:
Chris Pearce 2009-01-25 00:00:17 +13:00
Родитель 119f1b9717
Коммит 9068fac007
16 изменённых файлов: 320 добавлений и 51 удалений

Просмотреть файл

@ -138,6 +138,7 @@ NS_CP_ContentTypeName(PRUint32 contentType)
CASE_RETURN( TYPE_OBJECT_SUBREQUEST );
CASE_RETURN( TYPE_DTD );
CASE_RETURN( TYPE_FONT );
CASE_RETURN( TYPE_MEDIA );
default:
return "<Unknown Type>";
}

Просмотреть файл

@ -51,7 +51,7 @@ interface nsIDOMNode;
* by launching a dialog to prompt the user for something).
*/
[scriptable,uuid(58cf9dca-40b3-6211-a508-7351f437a53e)]
[scriptable,uuid(344f9cb0-9a17-44c5-ab96-ee707884266c)]
interface nsIContentPolicy : nsISupports
{
const unsigned long TYPE_OTHER = 1;
@ -131,6 +131,11 @@ interface nsIContentPolicy : nsISupports
*/
const unsigned long TYPE_FONT = 14;
/**
* Indicates a video or audio load.
*/
const unsigned long TYPE_MEDIA = 15;
//////////////////////////////////////////////////////////////////////
/**

Просмотреть файл

@ -138,6 +138,10 @@ public:
// events can be fired.
void ChangeReadyState(nsMediaReadyState aState);
// Gets the pref media.enforce_same_site_origin, which determines
// if we should check Access Controls, or allow cross domain loads.
PRBool ShouldCheckAllowOrigin();
// Is the media element potentially playing as defined by the HTML 5 specification.
// http://www.whatwg.org/specs/web-apps/current-work/#potentially-playing
PRBool IsPotentiallyPlaying() const;

Просмотреть файл

@ -71,6 +71,11 @@
#include "nsICategoryManager.h"
#include "nsCommaSeparatedTokenizer.h"
#include "nsIContentPolicy.h"
#include "nsContentPolicyUtils.h"
#include "nsContentErrors.h"
#include "nsCrossSiteListenerProxy.h"
#ifdef MOZ_OGG
#include "nsOggDecoder.h"
#endif
@ -215,7 +220,7 @@ PRBool nsHTMLMediaElement::AbortExistingLoads()
if (mBegun) {
mBegun = PR_FALSE;
mError = new nsHTMLMediaError(nsHTMLMediaError::MEDIA_ERR_ABORTED);
mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_ABORTED);
DispatchProgressEvent(NS_LITERAL_STRING("abort"));
return PR_TRUE;
}
@ -265,8 +270,21 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
// The listener holds a strong reference to us. This creates a reference
// cycle which is manually broken in the listener's OnStartRequest method
// after it is finished with the element.
nsCOMPtr<nsIStreamListener> listener = new nsMediaLoadListener(this);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIStreamListener> loadListener = new nsMediaLoadListener(this);
NS_ENSURE_TRUE(loadListener, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIStreamListener> listener;
if (ShouldCheckAllowOrigin()) {
listener = new nsCrossSiteListenerProxy(loadListener,
NodePrincipal(),
mChannel,
PR_FALSE,
&rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
} else {
listener = loadListener;
}
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
@ -942,7 +960,7 @@ void nsHTMLMediaElement::ResourceLoaded()
void nsHTMLMediaElement::NetworkError()
{
mError = new nsHTMLMediaError(nsHTMLMediaError::MEDIA_ERR_NETWORK);
mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_NETWORK);
mBegun = PR_FALSE;
DispatchProgressEvent(NS_LITERAL_STRING("error"));
mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
@ -973,6 +991,12 @@ void nsHTMLMediaElement::SeekCompleted()
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeked"));
}
PRBool nsHTMLMediaElement::ShouldCheckAllowOrigin()
{
return nsContentUtils::GetBoolPref("media.enforce_same_site_origin",
PR_TRUE);
}
void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
{
// Handle raising of "waiting" event during seek (see 4.8.10.9)

Просмотреть файл

@ -181,6 +181,10 @@ class nsMediaDecoder : public nsIObserver
// main thread only.
virtual void Resume() = 0;
// Returns a weak reference to the media element we're decoding for,
// if it's available.
nsHTMLMediaElement* GetMediaElement();
protected:
// Start timer to update download progress information.

Просмотреть файл

@ -42,6 +42,7 @@
#include "nsIChannel.h"
#include "nsIPrincipal.h"
#include "nsIURI.h"
#include "nsIStreamListener.h"
#include "prlock.h"
// For HTTP seeking, if number of bytes needing to be

Просмотреть файл

@ -41,6 +41,8 @@
#include "nsIScriptSecurityManager.h"
#include "nsChannelToPipeListener.h"
#include "nsICachingChannel.h"
#include "nsDOMError.h"
#include "nsHTMLMediaElement.h"
#define HTTP_OK_CODE 200
#define HTTP_PARTIAL_RESPONSE_CODE 206
@ -98,6 +100,19 @@ nsresult nsChannelToPipeListener::GetInputStream(nsIInputStream** aStream)
nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
nsHTMLMediaElement* element = mDecoder->GetMediaElement();
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (element->ShouldCheckAllowOrigin()) {
// If the request was cancelled by nsCrossSiteListenerProxy due to failing
// the Access Control check, send an error through to the media element.
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
if (NS_FAILED(rv) || status == NS_ERROR_DOM_BAD_URI) {
mDecoder->NetworkError();
return NS_ERROR_DOM_BAD_URI;
}
}
mIntervalStart = PR_IntervalNow();
mIntervalEnd = mIntervalStart;
mTotalBytes = 0;

Просмотреть файл

@ -51,6 +51,7 @@
#include "gfxImageSurface.h"
#include "nsPresContext.h"
#include "nsMediaDecoder.h"
#include "nsDOMError.h"
// Number of milliseconds between progress events as defined by spec
#define PROGRESS_MS 350
@ -101,6 +102,10 @@ void nsMediaDecoder::Shutdown()
mElement = nsnull;
}
nsHTMLMediaElement* nsMediaDecoder::GetMediaElement()
{
return mElement;
}
nsresult nsMediaDecoder::InitLogger()
{
#ifdef PR_LOGGING

Просмотреть файл

@ -51,6 +51,9 @@
#include "nsIStreamListener.h"
#include "nsIScriptSecurityManager.h"
#include "nsChannelToPipeListener.h"
#include "nsCrossSiteListenerProxy.h"
#include "nsHTMLMediaElement.h"
#include "nsIDocument.h"
class nsDefaultStreamStrategy : public nsStreamStrategy
{
@ -103,11 +106,26 @@ nsresult nsDefaultStreamStrategy::Open(nsIStreamListener** aStreamListener)
nsresult rv = mListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(mListener);
if (aStreamListener) {
*aStreamListener = mListener;
NS_ADDREF(mListener);
} else {
rv = mChannel->AsyncOpen(mListener, nsnull);
// Ensure that if we're loading cross domain, that the server is sending
// an authorizing Access-Control header.
nsHTMLMediaElement* element = mDecoder->GetMediaElement();
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (element->ShouldCheckAllowOrigin()) {
listener = new nsCrossSiteListenerProxy(mListener,
element->NodePrincipal(),
mChannel,
PR_FALSE,
&rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mChannel->AsyncOpen(listener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -381,7 +399,7 @@ public:
}
// These methods have the same thread calling requirements
// as those with the same name in nsMediaStream
// as those with the same name in nsMediaStream.
virtual nsresult Open(nsIStreamListener** aListener);
virtual nsresult Close();
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
@ -397,13 +415,14 @@ public:
// Return PR_TRUE if the stream has been cancelled.
PRBool IsCancelled() const;
// This must be called on the main thread only, and at a
// time when the strategy is not reading from the current
// channel/stream. Its primary purpose is to be called from
// a Seek to reset to the new byte range request http channel.
void Reset(nsIChannel* aChannel,
nsChannelToPipeListener* aListener,
nsIInputStream* aStream);
// This must be called on the main thread only, and at a time when the
// strategy is not reading from the current channel/stream. It's primary
// purpose is to be called from a Seek to reset to the new byte range
// request HTTP channel.
nsresult OpenInternal(nsIChannel* aChannel, PRInt64 aOffset);
// Opens the HTTP channel, using a byte range request to start at aOffset.
nsresult OpenInternal(nsIStreamListener **aStreamListener, PRInt64 aOffset);
private:
// Listener attached to channel to constantly download the
@ -433,18 +452,25 @@ private:
PRPackedBool mCancelled;
};
void nsHttpStreamStrategy::Reset(nsIChannel* aChannel,
nsChannelToPipeListener* aListener,
nsIInputStream* aStream)
nsresult nsHttpStreamStrategy::Open(nsIStreamListener **aStreamListener)
{
return OpenInternal(aStreamListener, 0);
}
nsresult nsHttpStreamStrategy::OpenInternal(nsIChannel* aChannel,
PRInt64 aOffset)
{
nsAutoLock lock(mLock);
mChannel = aChannel;
mListener = aListener;
mPipeInput = aStream;
return OpenInternal(static_cast<nsIStreamListener**>(nsnull), aOffset);
}
nsresult nsHttpStreamStrategy::Open(nsIStreamListener **aStreamListener)
nsresult nsHttpStreamStrategy::OpenInternal(nsIStreamListener **aStreamListener,
PRInt64 aOffset)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
if (aStreamListener) {
*aStreamListener = nsnull;
}
@ -455,34 +481,53 @@ nsresult nsHttpStreamStrategy::Open(nsIStreamListener **aStreamListener)
nsresult rv = mListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(mListener);
if (aStreamListener) {
*aStreamListener = mListener;
NS_ADDREF(*aStreamListener);
} else {
// Ensure that if we're loading cross domain, that the server is sending
// an authorizing Access-Control header.
nsHTMLMediaElement* element = mDecoder->GetMediaElement();
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (element->ShouldCheckAllowOrigin()) {
listener = new nsCrossSiteListenerProxy(mListener,
element->NodePrincipal(),
mChannel,
PR_FALSE,
&rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
}
// Use a byte range request from the start of the resource.
// This enables us to detect if the stream supports byte range
// requests, and therefore seeking, early.
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
NS_LITERAL_CSTRING("bytes=0-"),
PR_FALSE);
nsCAutoString rangeString("bytes=");
rangeString.AppendInt(aOffset);
rangeString.Append("-");
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
}
rv = mChannel->AsyncOpen(mListener, nsnull);
rv = mChannel->AsyncOpen(listener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
NS_ENSURE_SUCCESS(rv, rv);
mPosition = 0;
mPosition = aOffset;
return NS_OK;
}
nsresult nsHttpStreamStrategy::Close()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsAutoLock lock(mLock);
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
@ -518,11 +563,9 @@ class nsByteRangeEvent : public nsRunnable
{
public:
nsByteRangeEvent(nsHttpStreamStrategy* aStrategy,
nsMediaDecoder* aDecoder,
nsIURI* aURI,
PRInt64 aOffset) :
mStrategy(aStrategy),
mDecoder(aDecoder),
mURI(aURI),
mOffset(aOffset),
mResult(NS_OK)
@ -552,35 +595,16 @@ public:
return NS_OK;
}
nsCOMPtr<nsIChannel> channel;
mStrategy->Close();
mResult = NS_NewChannel(getter_AddRefs(mChannel),
mResult = NS_NewChannel(getter_AddRefs(channel),
mURI,
nsnull,
nsnull,
nsnull,
nsIRequest::LOAD_NORMAL);
NS_ENSURE_SUCCESS(mResult, mResult);
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
nsCAutoString rangeString("bytes=");
rangeString.AppendInt(mOffset);
rangeString.Append("-");
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
}
mListener = new nsChannelToPipeListener(mDecoder, PR_TRUE, mOffset);
NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
mResult = mListener->Init();
NS_ENSURE_SUCCESS(mResult, mResult);
mResult = mChannel->AsyncOpen(mListener, nsnull);
NS_ENSURE_SUCCESS(mResult, mResult);
mResult = mListener->GetInputStream(getter_AddRefs(mStream));
NS_ENSURE_SUCCESS(mResult, mResult);
mStrategy->Reset(mChannel, mListener, mStream);
mResult = mStrategy->OpenInternal(channel, mOffset);
return NS_OK;
}
@ -685,7 +709,7 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
// Don't acquire mLock in this scope as we do a synchronous call to the main thread
// which would deadlock if that thread is calling Close().
nsCOMPtr<nsByteRangeEvent> event = new nsByteRangeEvent(this, mDecoder, mURI, aOffset);
nsCOMPtr<nsByteRangeEvent> event = new nsByteRangeEvent(this, mURI, aOffset);
NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
// If the sync request fails, or a call to Cancel() is made during the request,

Двоичные данные
content/media/video/test/320x240.allow-origin.ogv Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1 @@
Access-Control-Allow-Origin: *

Просмотреть файл

@ -60,6 +60,8 @@ _TEST_FILES = \
ifdef MOZ_OGG
_TEST_FILES += \
test_access_control.html \
file_access_controls.html \
test_bug448534.html \
test_bug461281.html \
test_can_play_type_ogg.html \
@ -81,7 +83,10 @@ _TEST_FILES += \
test_timeupdate3.html \
320x240.ogv \
test_videoDocumentTitle.html \
bug461281.ogg \
320x240.allow-origin.ogv \
320x240.allow-origin.ogv^headers^ \
bug461281.ogg \
redirect.sjs \
seek.ogv \
$(NULL)
# test_progress1.html disabled while we figure out the random failure

Просмотреть файл

@ -0,0 +1,135 @@
<html>
<head>
<script>
// Page URL: http://example.org/tests/content/media/video/test/file_access_controls.html
var gTests = [
{
url: "redirect.sjs?http://example.com/tests/content/media/video/test/320x240.ogv",
result: "error",
description: "Won't load when redirected to different domain",
},{
url: "redirect.sjs?http://example.com/tests/content/media/video/test/320x240.allow-origin.ogv",
result: "loaded",
description: "Can load when redirected to different domain with allow-origin",
},{
url: "redirect.sjs?http://test1.example.org/tests/content/media/video/test/320x240.ogv",
result: "error",
description: "Won't load when redirected to subdomain",
},{
url: "redirect.sjs?http://test1.example.org/tests/content/media/video/test/320x240.allow-origin.ogv",
result: "loaded",
description: "Can load when redirected to subdomain with allow-origin",
},{
url: "redirect.sjs?http://example.org/tests/content/media/video/test/320x240.ogv",
result: "loaded",
description: "Can load when redirected to same domain",
},{
url: "http://example.org/tests/content/media/video/test/320x240.ogv",
result: "loaded",
description: "Can load from same domain"
},{
url: "http://example.org:8000/tests/content/media/video/test/320x240.ogv",
result: "error",
description: "Won't load from differnet port on same domain"
},{
url: "http://example.org:8000/tests/content/media/video/test/320x240.allow-origin.ogv",
result: "loaded",
description: "Can load from different port on same domain with allow-origin",
},{
url: "http://example.com/tests/content/media/video/test/320x240.ogv",
result: "error",
description: "Won't load cross domain",
},{
url: "http://example.com/tests/content/media/video/test/320x240.allow-origin.ogv",
result: "loaded",
description: "Can load cross domain with allow-origin",
},{
url: "http://test1.example.org/tests/content/media/video/test/320x240.allow-origin.ogv",
result: "loaded",
description: "Can load from subdomain with allow-origin",
},{
url: "http://test1.example.org/tests/content/media/video/test/320x240.ogv",
result: "error",
description: "Won't load from subdomain",
}
];
var gTestNum = 0;
var gExpectedResult = null;
var gTestDescription = null;
var video = null;
var gTestedRemoved = false;
var gOldPref;
function result(code) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
dump("result " + code);
opener.is(code, gExpectedResult, gTestDescription);
nextTest();
}
function load() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
opener.is(window.location.href,
"http://example.org/tests/content/media/video/test/file_access_controls.html",
"We must be on a example.org:80");
video = document.getElementById('video');
// Ensure access control check pref is on.
// media.enforce_same_site_origin
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
opener.ok(prefService!=null, "Get pref service");
var branch = prefService.getBranch("media.");
opener.ok(branch!=null, "Get media pref branch");
gOldPref = branch.getBoolPref("enforce_same_site_origin");
branch.setBoolPref("enforce_same_site_origin", true);
nextTest();
}
function nextTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (gTestNum == gTests.length) {
if (!gTestedRemoved) {
// Repeat all tests with element removed from doc, should get same result.
video.parentNode.removeChild(video);
gTestedRemoved = true;
gTestNum = 0;
} else {
// We're done, exit the test.
window.close();
return;
}
}
gExpectedResult = gTests[gTestNum].result;
gTestDescription = gTests[gTestNum].description;
video.src = gTests[gTestNum].url;
gTestNum++;
}
function done() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Undo change to access control check pref.
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var branch = prefService.getBranch("media.");
branch.setBoolPref("enforce_same_site_origin", gOldPref);
opener.done();
}
</script>
</head>
<body onload="load();" onunload="done()">
<!-- Change onloadedfirstframe to onloadeddata after bug 462570 lands -->
<video id="video"
onloadeddata="result('loaded');"
onerror="result('error');">
</video>
</body>
</html>

Просмотреть файл

@ -0,0 +1,5 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
response.setHeader("Location", request.queryString, false);
}

Просмотреть файл

@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=451958
-->
<head>
<title>Test for Bug 451958</title>
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=451958">Mozilla Bug 451958</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 451958 **/
function run() {
window.open("http://example.org:80/tests/content/media/video/test/file_access_controls.html", "", "width=500,height=500");
}
function done() {
SimpleTest.finish();
}
addLoadEvent(run);
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -131,6 +131,10 @@ pref("browser.chrome.image_icons.max_size", 1024);
pref("browser.triple_click_selects_paragraph", true);
// When loading <video> or <audio>, check for Access-Control-Allow-Origin
// header, and disallow the connection if not present or permitted.
pref("media.enforce_same_site_origin", false);
#ifdef MOZ_OGG
pref("media.ogg.enabled", true);
#endif