Mozilla Home
Privacy
Cookies
Legal
Bugzilla
Browse
Advanced Search
New Bug
Reports
Documentation
Log In
Log In with GitHub
or
Remember me
Browse
Advanced Search
New Bug
Reports
Documentation
Attachment 387569 Details for
Bug 493857
[patch]
CSP Work in Progress (v3.6)
CSP.wip3.6.patch (text/plain), 82.96 KB, created by
Sid Stamm [:geekboy or :sstamm]
(
hide
)
Description:
CSP Work in Progress (v3.6)
Filename:
MIME Type:
Creator:
Sid Stamm [:geekboy or :sstamm]
Size:
82.96 KB
patch
obsolete
>diff -r f46e6aee1335 caps/idl/IContentSecurityPolicy.idl >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/caps/idl/IContentSecurityPolicy.idl Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,107 @@ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is the nsCSPContentPolicy service. >+ * >+ * The Initial Developer of the Original Code is >+ * Sid Stamm <sid@mozilla.com> >+ * >+ * Contributor(s): >+ * Brandon Sterne <bsterne@mozilla.com> >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either the GNU General Public License Version 2 or later (the "GPL"), or >+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+#include "nsISupports.idl" >+#include "nsIURI.idl" >+#include "nsIHttpChannel.idl" >+#include "nsIDocShell.idl" >+ >+/** >+ * IContentSecurityPolicy >+ * Describes an XPCOM component used to model an enforce CSPs. >+ */ >+ >+[scriptable, uuid(AB36A2BF-CB32-4AA6-AB41-6B4E4444A221)] >+interface IContentSecurityPolicy : nsISupports >+{ >+ >+ /** >+ * Set to true when the CSP has been read in and parsed and is ready to >+ * enforce. This is a barrier for the nsDocument so it doesn't load any >+ * sub-content until either it knows that a CSP is ready or will not be used. >+ */ >+ attribute boolean isInitialized; >+ >+ /** >+ * A read-only string version of the policy for debugging. >+ */ >+ readonly attribute AString policy; >+ >+ /** >+ * Called after the CSP object is created to fill in the appropriate request >+ * and request header information needed in case a report needs to be sent. >+ */ >+ void scanRequestData(in nsIHttpChannel aChannel); >+ >+ /** >+ * Updates the policy currently stored in the CSP to be "refined" or >+ * tightened by the one specified in the string policyString. >+ */ >+ void refinePolicy(in AString policyString, in nsIURI selfURI); >+ >+ /** >+ * Verifies ancestry as permitted by the policy. >+ * @param docShell >+ * containing the protected resource >+ * @return >+ * true if the frame's ancestors are all permitted by policy >+ */ >+ boolean permitsAncestry(in nsIDocShell docShell); >+ >+ /** >+ * Delegate method called by the service when sub-elements of the protected >+ * document are being loaded. Given a bit of information about the request, >+ * decides whether or not the policy is satisfied. >+ */ >+ short shouldLoad(in unsigned long aContentType, >+ in nsIURI aContentLocation, >+ in nsIURI aRequestOrigin, >+ in nsISupports aContext, >+ in ACString aMimeTypeGuess, >+ in nsISupports aExtra); >+ >+ /** >+ * Delegate method called by the service when sub-elements of the protected >+ * document are being processed. Given a bit of information about the request, >+ * decides whether or not the policy is satisfied. >+ */ >+ short shouldProcess(in unsigned long aContentType, >+ in nsIURI aContentLocation, >+ in nsIURI aRequestOrigin, >+ in nsISupports aContext, >+ in ACString aMimeType, >+ in nsISupports aExtra); >+ >+}; >diff -r f46e6aee1335 caps/idl/Makefile.in >--- a/caps/idl/Makefile.in Tue Jul 07 10:10:04 2009 +1200 >+++ b/caps/idl/Makefile.in Wed Jul 08 17:05:02 2009 -0700 >@@ -45,12 +45,13 @@ include $(DEPTH)/config/autoconf.mk > MODULE = caps > GRE_MODULE = 1 > > XPIDLSRCS = \ > nsIScriptSecurityManager.idl \ > nsIPrincipal.idl \ > nsISignatureVerifier.idl \ > nsISecurityCheckedComponent.idl \ >+ IContentSecurityPolicy.idl \ > $(NULL) > > include $(topsrcdir)/config/rules.mk > >diff -r f46e6aee1335 caps/src/CSPUtils.jsm >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/caps/src/CSPUtils.jsm Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,932 @@ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is the Content Security Policy data structures. >+ * >+ * The Initial Developer of the Original Code is >+ * Sid Stamm <sid@mozilla.com> >+ * >+ * Contributor(s): >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either the GNU General Public License Version 2 or later (the "GPL"), or >+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+/** >+ * Content Security Policy Utilities >+ * >+ * Overview >+ * This contains a set of classes and utilities for CSP. It is in this >+ * separate file for testing purposes. >+ */ >+ >+// Module stuff >+var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", >+ "CSPHost", "CSPWarning", "CSPError", "CSPdebug"]; >+ >+function CSPWarning(aMsg) { >+ //customize this to redirect output. >+ aMsg = 'CSP WARN: ' + aMsg + "\n"; >+ dump(aMsg); >+ Components.classes["@mozilla.org/consoleservice;1"] >+ .getService(Components.interfaces.nsIConsoleService) >+ .logStringMessage(aMsg); >+} >+function CSPError(aMsg) { >+ aMsg = 'CSP ERROR: ' + aMsg + "\n"; >+ dump(aMsg); >+ Components.classes["@mozilla.org/consoleservice;1"] >+ .getService(Components.interfaces.nsIConsoleService) >+ .logStringMessage(aMsg); >+} >+function CSPdebug(aMsg) { >+ aMsg = 'CSP debug: ' + aMsg + "\n"; >+ dump(aMsg); >+ Components.classes["@mozilla.org/consoleservice;1"] >+ .getService(Components.interfaces.nsIConsoleService) >+ .logStringMessage(aMsg); >+} >+ >+ >+//:::::::::::::::::::::::: CLASSES ::::::::::::::::::::::::::// >+ >+/** >+ * Class that represents a parsed policy structure. >+ */ >+function CSPRep() { >+ // this gets set to true when the policy is done parsing, or when a >+ // URI-borne policy has finished loading. >+ this._isInitialized = false; >+ >+ this._allowEval = false; >+ this._allowInlineScripts = false; >+ >+ // don't auto-populate _directives, so it is easier to find bugs >+ this._directives = {}; >+ >+} >+CSPRep.SRC_DIRECTIVES = { >+ ALLOW: "allow", >+ SCRIPT_SRC: "script-src", >+ STYLE_SRC: "style-src", >+ MEDIA_SRC: "media-src", >+ IMG_SRC: "img-src", >+ OBJECT_SRC: "object-src", >+ FRAME_SRC: "frame-src", >+ FRAME_ANCESTORS: "frame-ancestors" >+}; >+CSPRep.URI_DIRECTIVES = { >+ REPORT_URI: "report-uri", >+ POLICY_URI: "policy-uri" >+}; >+ >+ >+/** >+ * Factory to create a new CSPRep, parsed from a string. >+ * >+ * @param aStr >+ * string rep of a CSP >+ * @param self (optional) >+ * string or CSPSource representing the "self" source >+ * @returns >+ * an instance of CSPRep >+ */ >+CSPRep.fromString = function(aStr, self) { >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var UD = CSPRep.URI_DIRECTIVES; >+ var pieces = aStr.split(/;\s*/); >+ var aCSPR = new CSPRep(); >+ piece:for(var i in pieces) { >+ //split off first token and figure out what to do >+ var dirname = pieces[i].match(/(\S+)/)[0]; >+ >+ //careful to trim the stuff so there's no extra tokens >+ var dirvalue = pieces[i].substring(dirname.length) >+ .replace(/^\s+/,'') >+ .replace(/\s+$/,''); >+ >+ //If it is the script-src directive, pull out "eval" and "inline" >+ if(dirname == SD.STYLE_SRC) { >+ let listchunks = dirvalue.split(/\s+/); >+ for(let i in listchunks) { >+ if(listchunks[i] == "eval") { >+ listchunks.splice(i,1); >+ aCSPR._allowEval = true; >+ } >+ if(listchunks[i] == "inline") { >+ listchunks.splice(i,1); >+ aCSPR._allowInlineScripts = true; >+ } >+ } >+ // repair source list without the keywords >+ dirvalue = listchunks.join(" "); >+ } >+ >+ //is it a SRC directive? >+ for(var sdi in SD) { >+ if(dirname == SD[sdi]) { >+ var dv = CSPSourceList.fromString(dirvalue, self); >+ //TODO: more robust check for errors in making DV >+ aCSPR._directives[SD[sdi]] = dv; >+ continue piece; >+ } >+ } >+ >+ //not a SRC directive, check URI directives >+ if(dirname == UD.POLICY_URI) { >+ // POLICY_URI can only be alone >+ if(aCSPR._directives.length > 0 || pieces.length > 1) { >+ CSPError("policy-uri directive can only appear alone"); >+ return CSPRep.fromString("allow none"); >+ } >+ >+ //return; >+ //TODO: verify that policy URI is same-origin >+ var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] >+ .createInstance(Components.interfaces.nsIXMLHttpRequest); >+ >+ // insert error hook >+ req.onerror = CSPError; >+ >+ // synchronous -- otherwise we need to architect a callback into the >+ // xpcom component so that whomever creates the policy object gets >+ // notified when it's loaded and ready to go. >+ req.open("GET", dirvalue, false); >+ >+ // make request anonymous >+ // This prevents sending cookies with the request, in case the policy URI >+ // is injected, it can't be abused for CSRF. >+ req.channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS; >+ >+ //CSPdebug("Fetching URI " + dirvalue); >+ req.send(null); >+ if(req.status == 200) { >+ //CSPdebug("XHR returned: " + req.responseText); >+ aCSPR = CSPRep.fromString(req.responseText, self); >+ //remember where we got the policy >+ aCSPR._directives[UD.POLICY_URI] = dirvalue; >+ return aCSPR; >+ } >+ CSPError("Error fetching policy URI: server response was " + req.status); >+ return CSPRep.fromString("allow none"); >+ } >+ if(dirname == UD.REPORT_URI) { >+ //TODO: do we want to support multiple URIs? >+ //TODO: verify that report URI(s) is(are) same-origin >+ aCSPR._directives[UD.REPORT_URI] = dirvalue; >+ continue piece; >+ } >+ >+ //no directives matched >+ CSPWarning("Couldn't process unknown directive '" + dirname + "'"); >+ } >+ >+ if(aCSPR._directives.length < 1) { >+ CSPError("invalid policy -- no recognized directives"); >+ return CSPRep.fromString("allow none"); >+ } >+ >+ if(!aCSPR._directives[SD.ALLOW]) { >+ CSPWarning("allow directive is not present, using 'allow none'"); >+ aCSPR._directives[SD.ALLOW] = CSPSourceList.fromString("none"); >+ } >+ >+ aCSPR.makeExplicit(); >+ return aCSPR; >+}; >+ >+CSPRep.prototype = { >+ /** >+ * Returns array of all report uris defined, or none if there are none. >+ */ >+ getReportURIs: >+ function() { >+ if(!this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI]) >+ return []; >+ >+ var uris = this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI].split(","); >+ for(let i in uris) { >+ //trim off surrounding whitespace >+ uris[i] = uris[i].replace(/^\s+/, "").replace(/\s+$/, ""); >+ } >+ return uris; >+ }, >+ >+ /** >+ * Compares this CSPRep instance to another. >+ */ >+ equals: >+ function(that) { >+ if(this._directives.length != that._directives.length) { >+ return false; >+ } >+ for(var i in this._directives) { >+ if(!that._directives[i] || !this._directives[i].equals(that._directives[i])) { >+ return false; >+ } >+ } >+ return true; >+ }, >+ >+ /** >+ * Generates string representation of the policy. Should be fairly similar >+ * to the original. >+ */ >+ toString: >+ function csp_toString() { >+ var dirs = []; >+ for(var i in this._directives) { >+ if(this._directives[i]) { >+ dirs.push(i + " " + this._directives[i].toString()); >+ } >+ } >+ return dirs.join("; "); >+ }, >+ >+ >+ /** >+ * Determines if this directive accepts a URI. >+ * @param aContext >+ * one of the SRC_DIRECTIVES defined above >+ * @returns >+ * true if the policy permits the URI in given context. >+ */ >+ permits: >+ function csp_permits(aURI, aContext) { >+ //make sure the context is valid >+ for(var i in CSPRep.SRC_DIRECTIVES) { >+ if(CSPRep.SRC_DIRECTIVES[i] == aContext) { >+ return this._directives[aContext].permits(aURI); >+ } >+ } >+ return false; >+ }, >+ >+ /** >+ * Intersects with another CSPRep, deciding the subset policy >+ * that should be enforced, and returning a new instance. >+ * @param aCSPRep >+ * a CSPRep instance to use as "other" CSP >+ * @returns >+ * a new CSPRep instance of the intersection >+ */ >+ intersectWith: >+ function cspsd_intersectWith(aCSPRep) { >+ var newRep = new CSPRep(); >+ >+ for(var dir in CSPRep.SRC_DIRECTIVES) { >+ var dirv = CSPRep.SRC_DIRECTIVES[dir]; >+ newRep._directives[dirv] = this._directives[dirv] >+ .intersectWith(aCSPRep._directives[dirv]); >+ } >+ return newRep; >+ }, >+ >+ >+ /** >+ * Copies default source list to each unspecified directive. >+ * @returns >+ * true if the makeExplicit succeeds >+ * false if it fails (for some weird reason) >+ */ >+ makeExplicit: >+ function cspsd_makeExplicit() { >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var allowDir = this._directives[SD.ALLOW]; >+ if(!allowDir) { >+ return false; >+ } >+ >+ for(var dir in SD) { >+ var dirv = SD[dir]; >+ if(dirv == SD.ALLOW) { >+ continue; >+ } >+ if(!this._directives[dirv]) { >+ //implicit directive, make explicit >+ this._directives[dirv] = allowDir.clone(); >+ } >+ } >+ this._isInitialized = true; >+ return true; >+ }, >+ >+ /** >+ * Returns true if "eval" is enabled through the "eval" keyword. >+ */ >+ get allowsEvalInScripts () { >+ return this._allowEval; >+ }, >+ >+ /** >+ * Returns true if inline scripts are enabled through the "inline" >+ * keyword. >+ */ >+ get allowsInlineScripts () { >+ return this._allowInlineScripts; >+ }, >+}; >+ >+ >+////////////////////////////////////////////////////////////////////// >+/** >+ * Class to represent a list of sources >+ */ >+function CSPSourceList() { >+ this._sources = []; >+ this._permitAllSources = false; >+} >+ >+/** >+ * Factory to create a new CSPSourceList, parsed from a string. >+ * >+ * @param aStr >+ * string rep of a CSP Source List >+ * @param self (optional) >+ * string or CSPSource representing the "self" source >+ * @returns >+ * an instance of CSPSourceList >+ */ >+CSPSourceList.fromString = function(aStr, self) { >+ //Source list is: >+ // <host-dir-value> ::= <source-list> >+ // | "none" >+ // <source-list> ::= <source> >+ // | <source-list>" "<source> >+ >+ var slObj = new CSPSourceList(); >+ if(aStr == "none") { >+ return slObj; >+ } >+ >+ var ss = aStr.split(/\s+/); >+ for(var i in ss) { >+ var o = CSPSource.fromString(ss[i], self); >+ //make sure none came back undefined >+ if(!o) { return null; } >+ slObj._sources[i] = o; >+ } >+ return slObj; >+}; >+ >+CSPSourceList.prototype = { >+ >+ /** >+ * Compares one CSPSourceList to another. >+ * >+ * @param that >+ * another CSPSourceList >+ * @returns >+ * true if they have the same data >+ */ >+ equals: >+ function(that) { >+ if(that._sources.length != this._sources.length) { >+ return false; >+ } >+ //sort both arrays and compare like a zipper >+ //XXX (sid): I think we can make this more efficient >+ var sortfn = function(a,b) { >+ return a.toString() > b.toString(); >+ }; >+ var a_sorted = this._sources.sort(sortfn); >+ var b_sorted = that._sources.sort(sortfn); >+ for(var i in a_sorted) { >+ if(!a_sorted[i].equals(b_sorted[i])) { >+ return false; >+ } >+ } >+ return true; >+ }, >+ >+ /** >+ * Generates string representation of the Source List. >+ * Should be fairly similar to the original. >+ */ >+ toString: >+ function() { >+ if(this.isNone()) { >+ return "none"; >+ } >+ return this._sources.map(function(x) { return x.toString(); }).join(" "); >+ }, >+ >+ /** >+ * Returns whether or not this source list represents the "none" special >+ * case. >+ */ >+ isNone: >+ function() { >+ return (!this._permitAllSources) && (this._sources.length < 1); >+ }, >+ >+ /** >+ * Makes a new instance that resembles this object. >+ * @returns >+ * a new CSPSourceList >+ */ >+ clone: >+ function() { >+ var aSL = new CSPSourceList(); >+ aSL._permitAllSources = this._permitAllSources; >+ for(var i in this._sources) { >+ aSL._sources[i] = this._sources[i].clone(); >+ } >+ return aSL; >+ }, >+ >+ >+ /** >+ * Determines if this directive accepts a URI. >+ * @param aURI >+ * the URI in question >+ * @returns >+ * true if the URI matches a source in this source list. >+ */ >+ permits: >+ function cspsd_permits(aURI) { >+ if(this.isNone()) { >+ return false; >+ } >+ for(var i in this._sources) { >+ if(this._sources[i].permits(aURI)) { >+ return true; >+ } >+ } >+ return false; >+ }, >+ >+ >+ /** >+ * Intersects with another CSPSourceList, deciding the subset directive >+ * that should be enforced, and returning a new instance. >+ * @param that >+ * the other CSPSourceList to intersect "this" with >+ * @returns >+ * a new instance of a CSPSourceList representing the intersection >+ */ >+ intersectWith: >+ function cspsd_intersectWith(that) { >+ if(this._permitAllSources) { >+ return that.clone(); >+ } >+ if(that._permitAllSources) { >+ return this.clone(); >+ } >+ >+ // "none" case >+ if(this.isNone() || that.isNone()) { >+ return CSPSourceList.fromString("none"); >+ } >+ >+ // -- find only common sources >+ >+ //XXX (sid): figure out a good algorithm for this. >+ // This is horribly inefficient. O(n^2) >+ var isrcs = []; >+ for(var i in this._sources) { >+ for(var j in that._sources) { >+ var s = that._sources[j].intersectWith(this._sources[i]); >+ if(s) { >+ isrcs.push(s); >+ } >+ } >+ } >+ //Next, remove duplicates >+ dup:for(var i = 0; i < isrcs.length; i++) { >+ for(var j = 0; j < i; j++) { >+ if(isrcs[i].equals(isrcs[j])) { >+ isrcs.splice(i, 1); >+ i--; >+ continue dup; >+ } >+ } >+ } >+ var newCSPSrcList = new CSPSourceList(); >+ newCSPSrcList._sources = isrcs; >+ return newCSPSrcList; >+ } >+} >+ >+ >+////////////////////////////////////////////////////////////////////// >+/** >+ * Class to model a source (scheme, host, port) >+ */ >+function CSPSource() { >+ this._scheme = "*"; >+ this._port = "*"; >+ this._host = "*"; >+} >+ >+CSPSource._PORT_RE = /(:(\*|\d+))$/; >+CSPSource._SCHM_RE = /^((\*|[a-zA-Z][a-zA-Z\+\.\-]*):\/{0,2})/; >+CSPSource._HOST_RE = /^(localhost|\*|(((\*|[a-zA-Z0-9\-]+)\.)([a-zA-Z0-9\-]+\.)*([a-zA-Z0-9\-]+)))$/; >+ >+/** >+ * Factory to create a new CSPSource, from a nsIURI. >+ * >+ * @param aURI >+ * nsIURI rep of a URI >+ * @param self (optional) >+ * string or CSPSource representing the "self" source >+ * @returns >+ * an instance of CSPSource >+ */ >+CSPSource.fromURI = function(aURI, self) { >+ >+ if( !(aURI instanceof Components.interfaces.nsIURI) ){ >+ CSPError("Provided argument is not an nsIURI"); >+ return null; >+ } >+ >+ var sObj = new CSPSource(); >+ >+ try { // try block in case there's no host specified in the URI >+ if(!aURI.host) >+ sObj._host = "*"; >+ else >+ sObj._host = aURI.host; >+ } catch(e) {} >+ >+ // Don't forget to handle "self" tokens in the URI >+ if(sObj._host == "self") { >+ if(!self) { >+ CSPError("self keyword used, but no self data specified"); >+ return null; >+ } >+ if(self == "self") { >+ CSPError("self reference cannot be 'self'"); >+ return null; >+ } >+ if(!(self instanceof CSPSource)) { >+ self = CSPSource.fromString(self); >+ } >+ sObj._host = self._host; >+ if(!sObj._port) { >+ sObj._port = self._port; >+ } >+ } >+ >+ try { // it's possible there's not an available port >+ if(aURI.port) >+ sObj._port = aURI.port; >+ else >+ sObj._port = (self ? self._port : "*"); >+ } catch(e) {} >+ >+ if(aURI.scheme) >+ sObj._scheme = aURI.scheme; >+ else >+ sObj._scheme = (self ? self._scheme : "*"); >+ >+ return sObj; >+}; >+ >+/** >+ * Factory to create a new CSPSource, parsed from a string. >+ * >+ * @param aStr >+ * string rep of a CSP Source >+ * @param self (optional) >+ * string or CSPSource representing the "self" source >+ * @returns >+ * an instance of CSPSource >+ */ >+CSPSource.fromString = function(aStr, self) { >+ if(aStr == "self") { >+ if(!self) { >+ CSPError("self keyword used, but no self data specified"); >+ return null; >+ } >+ if(self == "self") { >+ CSPError("self reference cannot be 'self'"); >+ return null; >+ } >+ if(self instanceof CSPSource) { >+ return self.clone(); >+ } >+ return CSPSource.fromString(self); >+ } >+ >+ var s = aStr; >+ var sObj = new CSPSource(); >+ >+ if(self && !(self instanceof CSPSource)) { >+ self = CSPSource.fromString(self); >+ } >+ >+ // IMPORTANT: scan for port first, because host can be mistaken for scheme >+ // due to the allowance of dots in the scheme (and a colon before the port) >+ if(CSPSource._PORT_RE.test(s)) { >+ sObj._port = s.match(CSPSource._PORT_RE)[0].replace(/:/,''); >+ s = s.replace(CSPSource._PORT_RE, ''); >+ } else if(self) { >+ sObj._port = self._port; >+ } >+ >+ if(CSPSource._SCHM_RE.test(s)) { >+ sObj._scheme = s.match(CSPSource._SCHM_RE)[0].replace(/[:\/]+/,''); >+ s = s.replace(CSPSource._SCHM_RE, ''); >+ } else if(self) { >+ sObj._scheme = self._scheme; >+ } >+ >+ >+ if(CSPSource._HOST_RE.test(s)) { >+ sObj._host = CSPHost.fromString(s.match(CSPSource._HOST_RE)[0]); >+ if(sObj._host) { >+ return sObj; >+ } >+ } >+ CSPError("failed parsing host: '" + s + "'"); >+ return null; >+}; >+ >+CSPSource.prototype = { >+ >+ /** >+ * Generates string representation of the Source. >+ * Should be fairly similar to the original. >+ */ >+ toString: >+ function() { >+ var s = this._host; >+ if(this._port && this._port != "*") { >+ s = s + ":" + this._port; >+ } >+ if(this._scheme && this._scheme != "*") { >+ s = this._scheme + "://" + s; >+ } >+ return s; >+ }, >+ >+ /** >+ * Makes a new instance that resembles this object. >+ * @returns >+ * a new CSPSource >+ */ >+ clone: >+ function() { >+ var aClone = new CSPSource(); >+ aClone._scheme = this._scheme; >+ aClone._port = this._port; >+ aClone._host = this._host.clone(); >+ return aClone; >+ }, >+ >+ /** >+ * Determines if this Source accepts a URI. >+ * @param aURI >+ * the URI in question >+ * @returns >+ * true if the URI matches a source in this source list. >+ */ >+ permits: >+ function(aSource) { >+ if(aSource instanceof Components.interfaces.nsIURI) { >+ //figure out based on URI >+ return this.permits(CSPSource.fromURI(aSource)); >+ } >+ if(!(aSource instanceof CSPSource)) { >+ //convert to CSPSource first >+ return this.permits(CSPSource.fromString(aSource)); >+ } >+ >+ //verify scheme, host, and port >+ if( (this._scheme == "*" || this._scheme == aSource._scheme) >+ && (this._port == "*" || this._port == aSource._port) ){ >+ return this._host.permits(aSource._host); >+ } >+ // something didn't match. >+ return false; >+ }, >+ >+ /** >+ * Determines the intersection of two sources. >+ * Returns a null object if intersection generates no >+ * hosts that satisfy it. >+ * @param that >+ * the other CSPSource to intersect "this" with >+ * @returns >+ * a new instance of a CSPSource representing the intersection >+ */ >+ intersectWith: >+ function(that) { >+ var newSource = new CSPSource(); >+ >+ //port >+ if(this._port == "*") { >+ newSource._port = that._port; >+ } else if(that._port == "*") { >+ newSource._port = this._port; >+ } else if(that._port == this._port) { >+ newSource._port = this._port; >+ } else { >+ //CSPError("Could not intersect " + this + " with " + that >+ // + " due to port problems."); >+ return null; >+ } >+ >+ //scheme >+ if(this._scheme == "*") { >+ newSource._scheme = that._scheme; >+ } else if(that._scheme == "*") { >+ newSource._scheme = this._scheme; >+ } else if(that._scheme == this._scheme) { >+ newSource._scheme = this._scheme; >+ } else { >+ //CSPError("Could not intersect " + this + " with " + that >+ // + " due to scheme problems."); >+ return null; >+ } >+ >+ //host >+ newSource._host = this._host.intersectWith(that._host); >+ >+ return newSource._host ? newSource : null; >+ }, >+ >+ /** >+ * Compares one CSPSource to another. >+ * >+ * @param that >+ * another CSPSource >+ * @returns >+ * true if they have the same data >+ */ >+ equals: >+ function(aSrc) { >+ return this._scheme == aSrc._scheme >+ && this._port == aSrc._port >+ && this._host.equals(aSrc._host); >+ } >+}; >+ >+ >+////////////////////////////////////////////////////////////////////// >+/** >+ * Class to model a host *.x.y. >+ */ >+function CSPHost() { >+ this._segments = []; >+} >+ >+/** >+ * Factory to create a new CSPHost, parsed from a string. >+ * >+ * @param aStr >+ * string rep of a CSP Host >+ * @returns >+ * an instance of CSPHost >+ */ >+CSPHost.fromString = function(aStr) { >+ var hObj = new CSPHost(); >+ hObj._segments = aStr.split(/\./); >+ if(hObj._segments.length < 1 || >+ hObj._segments.length == 1 && hObj._segments[0] != "*" >+ && hObj._segments[0] != "localhost") { >+ //only short hosts allowed are "*" and "localhost" >+ return null; >+ } >+ //TODO: double-check that each segment is appropriate. >+ return hObj; >+}; >+ >+CSPHost.prototype = { >+ /** >+ * Generates string representation of the Source. >+ * Should be fairly similar to the original. >+ */ >+ toString: >+ function() { >+ return this._segments.join("."); >+ }, >+ >+ /** >+ * Makes a new instance that resembles this object. >+ * @returns >+ * a new CSPHost >+ */ >+ clone: >+ function() { >+ var aHost = new CSPHost(); >+ for(var i in this._segments) { >+ aHost._segments[i] = this._segments[i]; >+ } >+ return aHost; >+ }, >+ >+ /** >+ * Returns true if this host accepts the provided host (or the other way >+ * around). >+ * @param aHost >+ * the FQDN in question (CSPHost or String) >+ * @returns >+ */ >+ permits: >+ function(aHost) { >+ if(!(aHost instanceof CSPHost)) { >+ // -- compare CSPHost to String >+ return this.permits(CSPHost.fromString(aHost)); >+ } >+ var thislen = this._segments.length; >+ var thatlen = aHost._segments.length; >+ >+ // don't accept a less specific host: >+ // \--> *.b.a doesn't accept b.a. >+ if(thatlen < thislen) { return false; } >+ >+ // check for more specific host (and wildcard): >+ // \--> *.b.a accepts d.c.b.a. >+ // \--> c.b.a doesn't accept d.c.b.a. >+ if((thatlen > thislen) && this._segments[0] != "*") { >+ return false; >+ } >+ >+ // Given the wildcard condition (from above), >+ // only necessary to compare elements that are present >+ // in this host. Extra tokens in aHost are ok. >+ // * Compare from right to left. >+ for(var i=1; i <= thislen; i++) { >+ if( this._segments[thislen-i] != "*" && >+ (this._segments[thislen-i] != aHost._segments[thatlen-i]) ) { >+ return false; >+ } >+ } >+ >+ //at this point, all conditions are met, so the host is allowed >+ return true; >+ }, >+ >+ /** >+ * Determines the intersection of two Hosts. >+ * Basically, they must be the same, or one must have a wildcard. >+ * @param that >+ * the other CSPHost to intersect "this" with >+ * @returns >+ * a new instance of a CSPHost representing the intersection >+ * (or null, if they can't be intersected) >+ */ >+ intersectWith: >+ function(that) { >+ if(!(this.permits(that) || that.permits(this))) { >+ // host definitions cannot co-exist without a more general host >+ // ... one must be a subset of the other, or intersection makes no sense. >+ return null; >+ } >+ >+ // pick the more specific one, if both are same length. >+ if(this._segments.length == that._segments.length) { >+ // *.a vs b.a : b.a >+ return (this._segments[0] == "*") ? that.clone() : this.clone(); >+ } >+ >+ //different lengths... >+ // *.b.a vs *.a : *.b.a >+ // *.b.a vs d.c.b.a : d.c.b.a >+ return (this._segments.length > that._segments.length) >+ ? this.clone() : that.clone(); >+ }, >+ >+ /** >+ * Compares one CSPHost to another. >+ * >+ * @param that >+ * another CSPHost >+ * @returns >+ * true if they have the same data >+ */ >+ equals: >+ function(that) { >+ if(this._segments.length != that._segments.length) { >+ return false; >+ } >+ for(var i=0; i<this._segments.length; i++) { >+ if(this._segments[i] != that._segments[i]) { >+ return false; >+ } >+ } >+ return true; >+ } >+}; >diff -r f46e6aee1335 caps/src/Makefile.in >--- a/caps/src/Makefile.in Tue Jul 07 10:10:04 2009 +1200 >+++ b/caps/src/Makefile.in Wed Jul 08 17:05:02 2009 -0700 >@@ -83,12 +83,21 @@ endif > > EXTRA_DSO_LDOPTS = \ > $(LIBS_DIR) \ > $(ZLIB_LIBS) \ > $(MOZ_JS_LIBS) \ > $(MOZ_COMPONENT_LIBS) \ > $(NULL) > >+EXTRA_PP_COMPONENTS = \ >+ contentSecurityPolicy.js \ >+ $(NULL) >+ >+EXTRA_JS_MODULES = \ >+ CSPUtils.jsm \ >+ $(NULL) >+ >+ > include $(topsrcdir)/config/rules.mk > > INCLUDES += -I$(srcdir)/../include > >diff -r f46e6aee1335 caps/src/contentSecurityPolicy.js >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/caps/src/contentSecurityPolicy.js Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,351 @@ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is the nsCSPContentPolicy service. >+ * >+ * The Initial Developer of the Original Code is >+ * Sid Stamm <sid@mozilla.com> >+ * >+ * Contributor(s): >+ * Brandon Sterne <bsterne@mozilla.com> >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either the GNU General Public License Version 2 or later (the "GPL"), or >+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+ >+/** >+ * Content Security Policy >+ * >+ * Overview >+ * This is a stub component that will be fleshed out to do all the fancy stuff >+ * that ContentSecurityPolicy has to do. >+ */ >+ >+/* :::::::: Constants and Helpers ::::::::::::::: */ >+ >+const Cc = Components.classes; >+const Ci = Components.interfaces; >+const Cr = Components.results; >+const Cu = Components.utils; >+ >+Cu.import("resource://gre/modules/XPCOMUtils.jsm"); >+Cu.import("resource://gre/modules/CSPUtils.jsm"); >+ >+/* ::::: Policy Parsing & Data structures :::::: */ >+ >+function ContentSecurityPolicy() { >+ CSPdebug("CSP CREATED"); >+ this._isInitialized = false; >+ this._policy = CSPRep.fromString("allow *"); >+ this._requestHeaders = []; >+ this._request = ""; >+ CSPdebug("CSP POLICY INITED TO 'allow *'"); >+} >+ >+/* >+ * Set up mappings from nsIContentPolicy content types to CSP directives. >+ */ >+with(Ci.nsIContentPolicy) { >+ let csp = ContentSecurityPolicy; >+ let cspr_sd = CSPRep.SRC_DIRECTIVES; >+ >+ csp._MAPPINGS=[]; >+ >+ /* default, catch-all case */ >+ csp._MAPPINGS[TYPE_OTHER] = cspr_sd.ALLOW; >+ >+ /* self */ >+ csp._MAPPINGS[TYPE_DOCUMENT] = null; >+ >+ /* shouldn't see this one */ >+ csp._MAPPINGS[TYPE_REFRESH] = null; >+ >+ /* categorized content types */ >+ csp._MAPPINGS[TYPE_SCRIPT] = cspr_sd.SCRIPT_SRC; >+ csp._MAPPINGS[TYPE_IMAGE] = cspr_sd.IMG_SRC; >+ csp._MAPPINGS[TYPE_STYLESHEET] = cspr_sd.STYLE_SRC; >+ csp._MAPPINGS[TYPE_OBJECT] = cspr_sd.OBJECT_SRC; >+ csp._MAPPINGS[TYPE_SUBDOCUMENT] = cspr_sd.FRAME_SRC; >+ csp._MAPPINGS[TYPE_MEDIA] = cspr_sd.MEDIA_SRC; >+ >+ >+ /* These must go through the catch-all */ >+ csp._MAPPINGS[TYPE_XBL] = cspr_sd.ALLOW; >+ csp._MAPPINGS[TYPE_PING] = cspr_sd.ALLOW; >+ csp._MAPPINGS[TYPE_XMLHTTPREQUEST] = cspr_sd.ALLOW; >+ csp._MAPPINGS[TYPE_OBJECT_SUBREQUEST] = cspr_sd.ALLOW; >+ csp._MAPPINGS[TYPE_DTD] = cspr_sd.ALLOW; >+ csp._MAPPINGS[TYPE_FONT] = cspr_sd.ALLOW; >+} >+ >+ContentSecurityPolicy.prototype = { >+ classDescription: "Content Security Policy Component", >+ contractID: "@mozilla.org/contentsecuritypolicy;1", >+ classID: Components.ID("{AB36A2BF-CB32-4AA6-AB41-6B4E4444A221}"), >+ QueryInterface: XPCOMUtils.generateQI([Ci.IContentSecurityPolicy]), >+ >+ // get this contractID registered for certain categories via XPCOMUtils >+ _xpcom_categories: [ ], >+ >+ get isInitialized() { >+ return this._isInitialized; >+ }, >+ >+ set isInitialized (foo) { >+ this._isInitialized = foo; >+ }, >+ >+ get policy () { >+ return this._policy.toString(); >+ }, >+ >+ /* >+ // Having a setter is a bad idea... opens up the policy to "loosening" >+ // Instead, use "refinePolicy." >+ set policy (aStr) { >+ this._policy = CSPRep.fromString(aStr); >+ }, >+ */ >+ >+ /** >+ * Given an nsIHttpChannel, fill out the appropriate data. >+ */ >+ scanRequestData: >+ function(aChannel) { >+ // grab the request line >+ var internalChannel = aChannel.QueryInterface(Ci.nsIHttpChannelInternal); >+ var reqMaj = {}; >+ var reqMin = {}; >+ var reqVersion = internalChannel.getRequestVersion(reqMaj, reqMin); >+ this._request = aChannel.requestMethod + " " >+ + aChannel.URI >+ + " HTTP/" + reqMaj.value + "." + reqMin.value; >+ >+ // grab the request headers >+ var self = this; >+ aChannel.visitRequestHeaders({ >+ visitHeader: function(aHeader, aValue) { >+ self._requestHeaders.push(aHeader + ": " + aValue); >+ }}); >+ }, >+ >+/* ........ Methods .............. */ >+ >+ /** >+ * Given a new policy, intersects the currently enforced policy with the new >+ * one and stores the result. The effect is a "tightening" or refinement of >+ * an old policy. This is called any time a new policy is encountered and >+ * the effective policy has to be refined. >+ */ >+ refinePolicy: >+ function csp_refinePolicy(aPolicy, selfURI) { >+ CSPdebug("REFINE POLICY: " + aPolicy); >+ CSPdebug(" SELF: " + selfURI.asciiSpec); >+ >+ // stay uninitialized until policy merging is done >+ this._isInitialized = false; >+ >+ // If there is a policy-uri, fetch the policy, then re-call this function. >+ // (1) parse and create a CSPRep object >+ var newpolicy = CSPRep.fromString(aPolicy, >+ selfURI.scheme + "://" + selfURI.hostPort); >+ >+ // (2) Intersect the currently installed CSPRep object with the new one >+ var intersect = this._policy.intersectWith(newpolicy); >+ //TODO: make sure intersect worked >+ >+ // (3) Save the result >+ this._policy = intersect; >+ this._isInitialized = true; >+ }, >+ >+ /** >+ * Generates and sends a violation report to the specified report URIs. >+ */ >+ sendReports: >+ function(blockedUri, violatedDirective) { >+ var uris = this._policy.getReportURIs(); >+ if(uris.length > 0) { >+ // Generate report to send composed of: >+ // <csp-report> >+ // <request>GET /index.html HTTP/1.1</request> >+ // <request-headers>Host: example.com >+ // User-Agent: ... >+ // ... >+ // </request-headers> >+ // <blocked-uri>...</blocked-uri> >+ // <violated-directive>...</violated-directive> >+ // </csp-report> >+ // >+ var strHeaders = ""; >+ for(let i in this._requestHeaders) { >+ strHeaders += this._requestHeaders[i] + "\n"; >+ } >+ >+ var report = "<csp-report>\n" + >+ " <request>" + this._request + "</request>\n" + >+ " <request-headers><![CDATA[\n" + >+ strHeaders + >+ " ]]></request-headers>\n" + >+ " <blocked-uri>" + blockedUri + "</blocked-uri>\n" + >+ " <violated-directive>" + violatedDirective + "</violated-directive>\n" + >+ "</csp-report>\n"; >+ >+ // For each URI in the report list, send out a report. >+ for(let i in uris) { >+ var failure = function(aEvt) { >+ if(req.readyState == 4 && req.status != 200) { >+ CSPError("Failed to send report to " + reportURI); >+ } >+ }; >+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] >+ .createInstance(Ci.nsIXMLHttpRequest); >+ >+ req.open("POST", uris[i], true); >+ req.upload.addEventListener("error", failure, false); >+ req.upload.addEventListener("abort", failure, false); >+ //req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; >+ >+ // make request anonymous >+ // This prevents sending cookies with the request, >+ // in case the policy URI is injected, it can't be >+ // abused for CSRF. >+ req.channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; >+ >+ req.send(report); >+ } >+ } >+ }, >+ >+ /** >+ * Exposed Method to analyze docShell for approved frame ancestry. >+ * Also sends violation reports if necessary. >+ * @param docShell >+ * the docShell for this policy's resource. >+ * @return >+ * true if the frame ancestry is allowed by this policy. >+ */ >+ permitsAncestry: >+ function(docShell) { >+ if(!docShell) { return false; } >+ CSPdebug(" in permitsAncestry(), docShell = " + docShell); >+ >+ // walk up this docShell tree until we hit chrome >+ var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor) >+ .getInterface(Ci.nsIDocShellTreeItem); >+ >+ // collect ancestors and make sure they're allowed. >+ var ancestors = []; >+ while(dst.parent) { >+ dst = dst.parent; >+ let it = dst.QueryInterface(Ci.nsIInterfaceRequestor) >+ .getInterface(Ci.nsIWebNavigation); >+ if(it.currentURI) { >+ if(it.currentURI.scheme == "chrome") { >+ break; >+ } >+ let ancestor = it.currentURI.scheme + "://" + it.currentURI.hostPort; >+ CSPdebug(" found frame ancestor " + ancestor); >+ ancestors.push(ancestor); >+ } >+ } >+ >+ // scan the discovered ancestors >+ let cspContext = CSPRep.SRC_DIRECTIVES.FRAME_ANCESTORS; >+ for(let i in ancestors) { >+ if(!this._policy.permits(ancestors[i], cspContext)) { >+ // report the frame-ancestor violation >+ this.sendReports("self", cspContext +" "+ this._policy._directives[cspContext]); >+ return false; >+ } >+ } >+ return true; >+ }, >+ >+ /** >+ * Delegate method called by the service when sub-elements of the protected >+ * document are being loaded. Given a bit of information about the request, >+ * decides whether or not the policy is satisfied. >+ */ >+ shouldLoad: >+ function csp_shouldLoad(aContentType, >+ aContentLocation, >+ aRequestOrigin, >+ aContext, >+ aMimeTypeGuess, >+ aExtra) { >+ >+ //don't filter chrome stuff >+ if(aContentLocation.scheme == 'chrome') { >+ return Ci.nsIContentPolicy.ACCEPT; >+ } >+ >+ //interpret the context, and then pass off to the decision structure >+ CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec); >+ CSPdebug("shouldLoad content type = " + aContentType); >+ var cspContext = ContentSecurityPolicy._MAPPINGS[aContentType]; >+ //CSPdebug("shouldLoad CSP directive =" + cspContext); >+ >+ // if the mapping is null, there's no policy, let it through. >+ if(!cspContext) { >+ return Ci.nsIContentPolicy.ACCEPT; >+ } >+ >+ // otherwise, honor the translation >+ //var source = aContentLocation.scheme + "://" + aContentLocation.hostPort; >+ var res = this._policy.permits(aContentLocation, cspContext) >+ ? Ci.nsIContentPolicy.ACCEPT >+ : Ci.nsIContentPolicy.REJECT_SERVER; >+ >+ // frame-ancestors is taken care of early on (as this document is loaded) >+ >+ // If the result is *NOT* ACCEPT, then send report >+ if(res != Ci.nsIContentPolicy.ACCEPT) { >+ CSPdebug("blocking request for " + aContentLocation.asciiSpec); >+ this.sendReports(aContentLocation, >+ cspContext + " " + this._policy._directives[cspContext]); >+ } >+ >+ return res; >+ }, >+ >+ shouldProcess: >+ function csp_shouldProcess(aContentType, >+ aContentLocation, >+ aRequestOrigin, >+ aContext, >+ aMimeType, >+ aExtra) { >+ // frame-ancestors check is done outside the ContentPolicy >+ var res = Ci.nsIContentPolicy.ACCEPT; >+ CSPdebug("shouldProcess aContext=" + aContext); >+ return res; >+ }, >+ >+}; >+ >+ >+ >+function NSGetModule(aComMgr, aFileSpec) >+ XPCOMUtils.generateModule([ContentSecurityPolicy]); >diff -r f46e6aee1335 caps/tests/Makefile.in >--- a/caps/tests/Makefile.in Tue Jul 07 10:10:04 2009 +1200 >+++ b/caps/tests/Makefile.in Wed Jul 08 17:05:02 2009 -0700 >@@ -40,9 +40,13 @@ DEPTH = ../.. > topsrcdir = @top_srcdir@ > srcdir = @srcdir@ > VPATH = @srcdir@ > > include $(DEPTH)/config/autoconf.mk > > DIRS = mochitest > >+MODULE = test_caps >+ >+XPCSHELL_TESTS = unit >+ > include $(topsrcdir)/config/rules.mk >diff -r f46e6aee1335 caps/tests/unit/test_csputils.js >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/caps/tests/unit/test_csputils.js Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,472 @@ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is the Content Security Policy Data Structures testing code. >+ * >+ * The Initial Developer of the Original Code is >+ * Sid Stamm <sid@mozilla.com> >+ * >+ * Contributor(s): >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either the GNU General Public License Version 2 or later (the "GPL"), or >+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+//load('CSPUtils.jsm'); >+Components.utils.import('resource://gre/modules/CSPUtils.jsm'); >+ >+// load the HTTP server >+do_load_httpd_js(); >+ >+var httpServer = new nsHttpServer(); >+ >+const POLICY_FROM_URI = "allow self; img-src *"; >+const POLICY_PORT = 9000; >+const POLICY_URI = "http://localhost:" + POLICY_PORT + "/policy"; >+ >+// helper to assert that an object or array must have a given key >+function do_check_has_key(foo, key, stack) { >+ if (!stack) >+ stack = Components.stack.caller; >+ >+ var keys = []; >+ for(let k in keys) { keys.push(k); } >+ var text = key + " in [" + keys.join(",") + "]"; >+ >+ for(var x in foo) { >+ if(x == key) { >+ //succeed >+ ++_passedChecks; >+ dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + >+ stack.lineNumber + "] " + text + "\n"); >+ return; >+ } >+ } >+ do_throw(text, stack); >+} >+ >+// helper to use .equals on stuff >+function do_check_equivalent(foo, bar, stack) { >+ if (!stack) >+ stack = Components.stack.caller; >+ >+ var text = foo + ".equals(" + bar + ")"; >+ >+ if(foo.equals && foo.equals(bar)) { >+ ++_passedChecks; >+ dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " + >+ stack.lineNumber + "] " + text + "\n"); >+ return; >+ } >+ do_throw(text, stack); >+} >+ >+var tests = []; >+function test(fcn) { >+ tests.push(fcn); >+} >+ >+test( >+ function test_CSPHost_fromstring() { >+ var h; >+ >+ h = CSPHost.fromString("*"); >+ do_check_neq(null, h); // "* lone wildcard should work" >+ >+ h = CSPHost.fromString("foo.bar"); >+ do_check_neq(null, h); // "standard tuple failed" >+ >+ h = CSPHost.fromString("*.bar"); >+ do_check_neq(null, h); // "wildcard failed" >+ >+ h = CSPHost.fromString("com"); >+ do_check_eq(null, h); // "lone symbol should fail" >+ >+ h = CSPHost.fromString("f00b4r.com"); >+ do_check_neq(null, h); // "Numbers in hosts should work" >+ >+ h = CSPHost.fromString("foo-bar.com"); >+ do_check_neq(null, h); // "dashes in hosts should work" >+ >+ // Not enforced in CSPHost >+ //h = CSPHost.fromString("foo!bar.com"); >+ //ASSERT_NULL(h, "special chars in hosts should fail"); >+ }); >+ >+test( >+ function test_CSPHost_clone() { >+ h = CSPHost.fromString("*.a.b.c"); >+ h2 = h.clone(); >+ for(var i in h._segments) { >+ // "cloned segments should match" >+ do_check_eq(h._segments[i], h2._segments[i]); >+ } >+ }); >+ >+test( >+ function test_CSPHost_permits() { >+ var h = CSPHost.fromString("*.b.c"); >+ var h2 = CSPHost.fromString("a.b.c"); >+ do_check_true( h.permits(h2)); //"CSPHost *.b.c should allow CSPHost a.b.c" >+ do_check_true( h.permits("a.b.c")); //"CSPHost *.b.c should allow string a.b.c" >+ do_check_true(!h.permits("b.c")); //"CSPHost *.b.c should not allow string b.c" >+ do_check_true(!h.permits("a.a.c")); //"CSPHost *.b.c should not allow string a.a.c" >+ do_check_true(!h2.permits(h)); //"CSPHost a.b.c should not allow CSPHost *.b.c" >+ do_check_true(!h2.permits("b.c")); //"CSPHost a.b.c should not allow string b.c" >+ do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c" >+ }); >+ >+test( >+ function test_CSPHost_intersectWith() { >+ var h = CSPHost.fromString("*.b.c"); >+ //"*.a.b.c ^ *.b.c should be *.a.b.c" >+ do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString()); >+ >+ //"*.b.c ^ *.d.e should not work (null)" >+ do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e"))); >+ }); >+ >+///////////////////// Test the Source object ////////////////////// >+ >+test( >+ function test_CSPSource_fromString() { >+ //"basic source should not be null."); >+ /* >+ do_check_neq(null, CSPSource.fromString("a.com")); >+ >+ //"ldh characters should all work for host."); >+ do_check_neq(null, CSPSource.fromString("a2-c.com")); >+ >+ //"wildcard should work in first token for host."); >+ do_check_neq(null, CSPSource.fromString("*.a.com")); >+ >+ //print(" --- Ignore the following two errors if they print ---"); >+ //"wildcard should not work in non-first token for host."); >+ do_check_eq(null, CSPSource.fromString("x.*.a.com")); >+ >+ //"funny characters (#) should not work for host, _HOST_RE is probably broken."); >+ do_check_eq(null, CSPSource.fromString("a#2-c.com")); >+ //print(" --- Stop ignoring errors that print ---\n"); >+ */ >+ >+ //"failed to parse host with port."); >+ do_check_neq(null, CSPSource.fromString("a.com:23")); >+ //"failed to parse host with scheme."); >+ do_check_neq(null, CSPSource.fromString("https://a.com")); >+ //"failed to parse host with scheme and port."); >+ do_check_neq(null, CSPSource.fromString("https://a.com:200")); >+ }); >+ >+test( >+ function test_CSPSource_fromString_withSelf() { >+ var src; >+ src = CSPSource.fromString("a.com", "https://self.com"); >+ //"src should inherit port * >+ do_check_true(src.permits("https://a.com:443")); >+ //"src should inherit and require https scheme >+ do_check_false(src.permits("http://a.com")); >+ //"src should inherit scheme 'https'" >+ do_check_true(src.permits("https://a.com")); >+ >+ src = CSPSource.fromString("self", "https://self.com"); >+ //"src should inherit port * >+ do_check_true(src.permits("https://self.com:443")); >+ //"src should inherit and require https scheme >+ do_check_false(src.permits("http://self.com")); >+ //"src should inherit scheme 'https'" >+ do_check_true(src.permits("https://self.com")); >+ //"src should reject other hosts" >+ do_check_false(src.permits("https://a.com")); >+ }); >+ >+///////////////////// Test the source list ////////////////////// >+ >+test( >+ function test_CSPSourceList_fromString() { >+ var sd = CSPSourceList.fromString("none"); >+ //"'none' -- should parse" >+ do_check_neq(null,sd); >+ // "'none' should be a zero-length list" >+ do_check_eq(0, sd._sources.length); >+ >+ sd = CSPSourceList.fromString("*"); >+ //"'*' should be a one-length list" >+ do_check_eq(1, sd._sources.length); >+ >+ //print(" --- Ignore the following three errors if they print ---"); >+ //"funny char in host" >+ do_check_eq(null,CSPSourceList.fromString("f!oo.bar")); >+ //"funny char in scheme" >+ do_check_eq(null,CSPSourceList.fromString("ht1ps://f-oo.bar")); >+ //"funny char in port" >+ do_check_eq(null,CSPSourceList.fromString("https://f-oo.bar:3f")); >+ //print(" --- Stop ignoring errors that print ---\n"); >+ }); >+ >+test( >+ function test_CSPSourceList_fromString_twohost() { >+ var str = "foo.bar:21 https://ras.bar"; >+ var sd = CSPSourceList.fromString(str); >+ //"two-host list should parse" >+ do_check_neq(null,sd); >+ //"two-host list should parse to two hosts" >+ do_check_eq(2, sd._sources.length); >+ //"two-host list should contain original data" >+ do_check_eq(str, sd.toString()); >+ }); >+ >+test( >+ function test_CSPSourceList_permits() { >+ var nullSourceList = CSPSourceList.fromString("none"); >+ var simpleSourceList = CSPSourceList.fromString("a.com"); >+ var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88"); >+ var allSourceList = CSPSourceList.fromString("*"); >+ >+ //'none' should permit none." >+ do_check_true( !nullSourceList.permits("a.com")); >+ //a.com should permit a.com" >+ do_check_true( simpleSourceList.permits("a.com")); >+ //wrong host" >+ do_check_true( !simpleSourceList.permits("b.com")); >+ //double list permits http://bar.com:88" >+ do_check_true( doubleSourceList.permits("http://bar.com:88")); >+ //double list permits https://bar.com:88" >+ do_check_true( !doubleSourceList.permits("https://bar.com:88")); >+ //double list does not permit http://bar.com:443" >+ do_check_true( !doubleSourceList.permits("http://bar.com:443")); >+ //"double list permits https://foo.com:88" >+ do_check_true( doubleSourceList.permits("https://foo.com:88")); >+ //"double list does not permit foo.com" >+ do_check_true( !doubleSourceList.permits("foo.com")); >+ >+ //"* does not permit specific host" >+ do_check_true( allSourceList.permits("http://x.com:23")); >+ //"* does not permit a long host with no port" >+ do_check_true( allSourceList.permits("http://a.b.c.d.e.f.x.com")); >+ >+ }); >+ >+test( >+ function test_CSPSourceList_intersect() { >+ var nullSourceList = CSPSourceList.fromString("none"); >+ var simpleSourceList = CSPSourceList.fromString("a.com"); >+ var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88"); >+ var singleFooSourceList = CSPSourceList.fromString("https://foo.com"); >+ var allSourceList = CSPSourceList.fromString("*"); >+ >+ //"Intersection of one source with 'none' source list should be none."); >+ do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone()); >+ //"Intersection of two sources with 'none' source list should be none."); >+ do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone()); >+ //"Intersection of '*' with 'none' source list should be none."); >+ do_check_true(nullSourceList.intersectWith(allSourceList).isNone()); >+ >+ //"Intersection of one source with '*' source list should be one source."); >+ do_check_equivalent(allSourceList.intersectWith(simpleSourceList), >+ simpleSourceList); >+ //"Intersection of two sources with '*' source list should be two sources."); >+ do_check_equivalent(allSourceList.intersectWith(doubleSourceList), >+ doubleSourceList); >+ >+ //"Non-overlapping source lists should intersect to 'none'"); >+ do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone()); >+ >+ //"subset and superset should intersect to subset."); >+ do_check_equivalent(singleFooSourceList, >+ doubleSourceList.intersectWith(singleFooSourceList)); >+ >+ //TODO: write more tests? >+ >+ }); >+ >+///////////////////// Test the Whole CSP rep object ////////////////////// >+ >+test( >+ function test_CSPRep_fromString() { >+ >+ // check default init >+ //ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.") >+ >+ var cspr; >+ var cspr_allowval; >+ >+ // check default policy "allow *" >+ cspr = CSPRep.fromString("allow *"); >+ //"ALLOW directive is missing when specified in fromString" >+ do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW); >+ >+ // ... and check that the other directives were auto-filled with the >+ // ALLOW one. >+ var SD = CSPRep.SRC_DIRECTIVES; >+ cspr_allowval = cspr._directives[SD.ALLOW]; >+ for(var d in CSPRep.SRC_DIRECTIVES) { >+ //"Missing key " + d >+ do_check_has_key(cspr._directives, SD[d]); >+ //"Implicit directive " + d + " has non-allow value." >+ do_check_eq(cspr._directives[SD[d]].toString(), cspr_allowval.toString()); >+ } >+ }); >+ >+ >+test( >+ function test_CSPRep_fromString_oneDir() { >+ >+ var cspr; >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.IMG_SRC, >+ SD.FRAME_ANCESTORS, SD.FRAME_SRC]; >+ >+ // check one-directive policies >+ cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com"); >+ >+ for(var x in DEFAULTS) { >+ //DEFAULTS[x] + " does not use default rule." >+ do_check_true(cspr.permits("http://bar.com:22", DEFAULTS[x])); >+ //DEFAULTS[x] + " does not use default rule." >+ do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x])); >+ } >+ //"script-src false positive in policy. >+ do_check_false(cspr.permits("http://bar.com:22", SD.SCRIPT_SRC)); >+ //"script-src false negative in policy. >+ do_check_true(cspr.permits("https://foo.com:400", SD.SCRIPT_SRC)); >+ }); >+ >+test( >+ function test_CSPRep_fromString_twodir() { >+ var cspr; >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.FRAME_ANCESTORS, SD.FRAME_SRC]; >+ >+ // check two-directive policies >+ cspr = CSPRep.fromString("allow allow.com; " >+ + "script-src https://foo.com; " >+ + "img-src bar.com"); >+ for(var x in DEFAULTS) { >+ do_check_true(cspr.permits("http://allow.com:22", DEFAULTS[x])); >+ //DEFAULTS[x] + " does not use default rule. >+ do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x])); >+ //DEFAULTS[x] + " does not use default rule. >+ do_check_false(cspr.permits("http://bar.com:400", DEFAULTS[x])); >+ //DEFAULTS[x] + " does not use default rule. >+ } >+ //"img-src does not use default rule. >+ do_check_false(cspr.permits("http://allow.com:22", SD.IMG_SRC)); >+ //"img-src does not use default rule. >+ do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC)); >+ //"img-src does not use default rule. >+ do_check_true(cspr.permits("http://bar.com:400", SD.IMG_SRC)); >+ >+ //"script-src does not use default rule. >+ do_check_false(cspr.permits("http://allow.com:22", SD.SCRIPT_SRC)); >+ //"script-src does not use default rule. >+ do_check_true(cspr.permits("https://foo.com:400", SD.SCRIPT_SRC)); >+ //"script-src does not use default rule. >+ do_check_false(cspr.permits("http://bar.com:400", SD.SCRIPT_SRC)); >+ }); >+ >+test(function test_CSPRep_fromString_withself() { >+ var cspr; >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var self = "https://self.com:34"; >+ >+ // check one-directive policies >+ cspr = CSPRep.fromString("allow self; script-src self https://*:*", >+ self); >+ //"img-src does not enforce default rule, 'self'. >+ do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC)); >+ //"img-src does not allow self >+ do_check_true(cspr.permits(self, SD.IMG_SRC)); >+ //"script-src is too relaxed >+ do_check_false(cspr.permits("http://evil.com", SD.SCRIPT_SRC)); >+ //"script-src should allow self >+ do_check_true(cspr.permits(self, SD.SCRIPT_SRC)); >+ //"script-src is too strict on host/port >+ do_check_true(cspr.permits("https://evil.com:100", SD.SCRIPT_SRC)); >+ }); >+ >+///////////////////// TEST POLICY_URI ////////////////////// >+test(function test_CSPRep_fromPolicyURI() { >+ var cspr; >+ var SD = CSPRep.SRC_DIRECTIVES; >+ var self = "http://localhost:" + POLICY_PORT; >+ >+ cspr = CSPRep.fromString("policy-uri " + POLICY_URI, self); >+ cspr_static = CSPRep.fromString(POLICY_FROM_URI, self); >+ >+ //"policy-uri failed to load" >+ do_check_neq(null,cspr); >+ >+ // other directives inherit self >+ for(var i in SD) { >+ //SD[i] + " parsed wrong from policy uri" >+ do_check_equivalent(cspr._directives[SD[i]], >+ cspr_static._directives[SD[i]]); >+ } >+ }); >+/* >+ >+test(function test_CSPRep_fromPolicyURI_failswhenmixed() { >+ var cspr; >+ var self = "http://localhost:" + POLICY_PORT; >+ var closed_policy = CSPRep.fromString("allow none"); >+ var my_uri_policy = "policy-uri " + POLICY_URI; >+ >+ //print(" --- Ignore the following two errors if they print ---"); >+ cspr = CSPRep.fromString("allow *; " + my_uri_policy, self); >+ >+ //"Parsing should fail when 'policy-uri' is mixed with allow directive" >+ do_check_equivalent(cspr, closed_policy); >+ cspr = CSPRep.fromString("img-src self; " + my_uri_policy, self); >+ >+ //"Parsing should fail when 'policy-uri' is mixed with other directives" >+ do_check_equivalent(cspr, closed_policy); >+ //print(" --- Stop ignoring errors that print ---\n"); >+ >+ }); >+*/ >+ >+// TODO: test reporting >+// TODO: test refinements (?) >+// TODO: test 'eval' and 'inline' keywords >+ >+function run_test() { >+ function policyresponder(request,response) { >+ response.setStatusLine(request.httpVersion, 200, "OK"); >+ response.setHeader("Content-Type", "text/csp", false); >+ response.bodyOutputStream.write(POLICY_FROM_URI, POLICY_FROM_URI.length); >+ } >+ //server.registerDirectory("/", nsILocalFileForBasePath); >+ httpServer.registerPathHandler("/policy", policyresponder); >+ httpServer.start(POLICY_PORT); >+ >+ for(let i in tests) { >+ tests[i](); >+ } >+ >+ //teardown >+ httpServer.stop(function() { }); >+ do_test_finished(); >+} >+ >+ >+ >diff -r f46e6aee1335 content/base/public/nsIDocument.h >--- a/content/base/public/nsIDocument.h Tue Jul 07 10:10:04 2009 +1200 >+++ b/content/base/public/nsIDocument.h Wed Jul 08 17:05:02 2009 -0700 >@@ -52,16 +52,17 @@ > #include "nsCompatibility.h" > #include "nsTObserverArray.h" > #include "nsTHashtable.h" > #include "nsHashKeys.h" > #include "nsNodeInfoManager.h" > #include "nsIStreamListener.h" > #include "nsIObserver.h" > #include "nsAutoPtr.h" >+#include "IContentSecurityPolicy.h" > #ifdef MOZ_SMIL > class nsSMILAnimationController; > #endif // MOZ_SMIL > > class nsIContent; > class nsPresContext; > class nsIPresShell; > class nsIDocShell; >@@ -1057,16 +1058,24 @@ public: > "have a presentation or a docshell or a window"); > NS_PRECONDITION(aDisplayDocument != this, "Should be different document"); > NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(), > "Display documents should not nest"); > mDisplayDocument = aDisplayDocument; > } > > /** >+ * Return the Content Security Policy object for the document >+ */ >+ IContentSecurityPolicy* GetContentSecurityPolicy() const >+ { >+ return mCSP; >+ } >+ >+ /** > * A class that represents an external resource load that has begun but > * doesn't have a document yet. Observers can be registered on this object, > * and will be notified after the document is created. Observers registered > * after the document has been created will NOT be notified. When observers > * are notified, the subject will be the newly-created document, the topic > * will be "external-resource-document-created", and the data will be null. > * If document creation fails for some reason, observers will still be > * notified, with a null document pointer. >@@ -1258,16 +1267,19 @@ protected: > PRUint32 mSubtreeModifiedDepth; > > // If we're an external resource document, this will be non-null and will > // point to our "display document": the one that all resource lookups should > // go to. > nsCOMPtr<nsIDocument> mDisplayDocument; > > PRUint32 mEventsSuppressed; >+ >+ // CSP policy object/data >+ nsCOMPtr<IContentSecurityPolicy> mCSP; > }; > > NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID) > > /** > * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified > * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified > * object is deleted. >diff -r f46e6aee1335 content/base/src/Makefile.in >--- a/content/base/src/Makefile.in Tue Jul 07 10:10:04 2009 +1200 >+++ b/content/base/src/Makefile.in Wed Jul 08 17:05:02 2009 -0700 >@@ -117,16 +117,17 @@ CPPSRCS = \ > nsContentAreaDragDrop.cpp \ > nsContentIterator.cpp \ > nsContentList.cpp \ > nsContentPolicy.cpp \ > nsContentSink.cpp \ > nsContentUtils.cpp \ > nsCopySupport.cpp \ > nsCrossSiteListenerProxy.cpp \ >+ nsCSPService.cpp \ > nsDataDocumentContentPolicy.cpp \ > nsDOMAttribute.cpp \ > nsDOMAttributeMap.cpp \ > nsDOMDocumentType.cpp \ > nsDOMFile.cpp \ > nsDOMLists.cpp \ > nsDOMParser.cpp \ > nsDOMSerializer.cpp \ >diff -r f46e6aee1335 content/base/src/nsCSPService.cpp >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/content/base/src/nsCSPService.cpp Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,177 @@ >+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is mozilla.org code. >+ * >+ * The Initial Developer of the Original Code is >+ * Mozilla Corporation. >+ * Portions created by the Initial Developer are Copyright (C) 2009 >+ * the Initial Developer. All Rights Reserved. >+ * >+ * Contributor(s): >+ * Brandon Sterne <bsterne@mozilla.com> >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either of the GNU General Public License Version 2 or later (the "GPL"), >+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+#include "nsIURI.h" >+#include "nsString.h" >+#include "nsCSPService.h" >+#include "nsIDocument.h" >+#include "nsIContent.h" >+#include "IContentSecurityPolicy.h" >+ >+CSPService::CSPService() >+{ >+} >+ >+CSPService::~CSPService() >+{ >+} >+ >+NS_IMPL_ISUPPORTS1(CSPService, nsIContentPolicy) >+ >+/* nsIContentPolicy implementation */ >+NS_IMETHODIMP >+CSPService::ShouldLoad(PRUint32 aContentType, >+ nsIURI *aContentLocation, >+ nsIURI *aRequestOrigin, >+ nsISupports *aRequestContext, >+ const nsACString &aMimeTypeGuess, >+ nsISupports *aExtra, >+ PRInt16 *aDecision) >+{ >+ if (!aContentLocation) >+ return NS_ERROR_FAILURE; >+ >+#ifdef DEBUG >+ { >+ nsCAutoString location("None"); >+ aContentLocation->GetSpec(location); >+ printf(" *** CSPService::ShouldLoad called for %s\n", >+ location.get()); >+ } >+#endif >+ >+ // find the nsDocument that initiated this request and see if it has a >+ // CSP policy object >+ nsCOMPtr<nsIDocument> doc; >+ nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext)); >+ if (node) { >+ doc = node->GetOwnerDoc(); >+ } >+ if (!doc) { >+ doc = do_QueryInterface(aRequestContext); >+ } >+ >+ // default decision, CSP can revise it if there's a policy to enforce >+ *aDecision = nsIContentPolicy::ACCEPT; >+ if (doc) { >+ IContentSecurityPolicy* csp = doc->GetContentSecurityPolicy(); >+ if (csp) { >+#ifdef DEBUG >+ nsAutoString policy; >+ csp->GetPolicy(policy); >+ printf(" *** CSPService - document has policy: %s\n", >+ ToNewCString(policy)); >+#endif >+ // obtain the enforcement decision >+ csp->ShouldLoad(aContentType, >+ aContentLocation, >+ aRequestOrigin, >+ aRequestContext, >+ aMimeTypeGuess, >+ aExtra, >+ aDecision); >+ } >+ } >+#ifdef DEBUG >+ else { >+ nsCAutoString uriSpec("None"); >+ aContentLocation->GetSpec(uriSpec); >+ printf(" *** CSPService - COULD NOT get nsIDocument for location: %s\n", >+ uriSpec.get()); >+ } >+#endif >+ >+ return NS_OK; >+} >+ >+NS_IMETHODIMP >+CSPService::ShouldProcess(PRUint32 aContentType, >+ nsIURI *aContentLocation, >+ nsIURI *aRequestOrigin, >+ nsISupports *aRequestContext, >+ const nsACString &aMimeTypeGuess, >+ nsISupports *aExtra, >+ PRInt16 *aDecision) >+{ >+ if (!aContentLocation) >+ return NS_ERROR_FAILURE; >+ >+ // This check is only useful for deciding about frame-ancestors >+ >+ // default decision is to accept the item >+ *aDecision = nsIContentPolicy::ACCEPT; >+ >+ // find the nsDocument that initiated this request and see if it has a >+ // CSP policy object >+ nsCOMPtr<nsIDocument> doc; >+ nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext)); >+ if (node) { >+ doc = node->GetOwnerDoc(); >+ } >+ if (!doc) { >+ doc = do_QueryInterface(aRequestContext); >+ } >+ if (doc) { >+ IContentSecurityPolicy* csp = doc->GetContentSecurityPolicy(); >+ if (csp) { >+#ifdef DEBUG >+ nsAutoString policy; >+ csp->GetPolicy(policy); >+ printf(" *** CSPService - shouldProcess - document has policy: %s\n", >+ ToNewCString(policy)); >+#endif >+ // obtain the enforcement decision >+ csp->ShouldProcess(aContentType, >+ aContentLocation, >+ aRequestOrigin, >+ aRequestContext, >+ aMimeTypeGuess, >+ aExtra, >+ aDecision); >+ } >+ } >+#ifdef DEBUG >+ else { >+ nsCAutoString uriSpec("None"); >+ aContentLocation->GetSpec(uriSpec); >+ printf(" *** CSPService - COULD NOT get nsIDocument for location: %s\n", >+ uriSpec.get()); >+ } >+#endif >+ return NS_OK; >+} >diff -r f46e6aee1335 content/base/src/nsCSPService.h >--- /dev/null Thu Jan 01 00:00:00 1970 +0000 >+++ b/content/base/src/nsCSPService.h Wed Jul 08 17:05:02 2009 -0700 >@@ -0,0 +1,67 @@ >+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ >+/* ***** BEGIN LICENSE BLOCK ***** >+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1 >+ * >+ * The contents of this file are subject to the Mozilla Public License Version >+ * 1.1 (the "License"); you may not use this file except in compliance with >+ * the License. You may obtain a copy of the License at >+ * http://www.mozilla.org/MPL/ >+ * >+ * Software distributed under the License is distributed on an "AS IS" basis, >+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License >+ * for the specific language governing rights and limitations under the >+ * License. >+ * >+ * The Original Code is mozilla.org code. >+ * >+ * The Initial Developer of the Original Code is >+ * Mozilla Corporation. >+ * Portions created by the Initial Developer are Copyright (C) 2009 >+ * the Initial Developer. All Rights Reserved. >+ * >+ * Contributor(s): >+ * Brandon Sterne <bsterne@mozilla.com> >+ * >+ * Alternatively, the contents of this file may be used under the terms of >+ * either of the GNU General Public License Version 2 or later (the "GPL"), >+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), >+ * in which case the provisions of the GPL or the LGPL are applicable instead >+ * of those above. If you wish to allow use of your version of this file only >+ * under the terms of either the GPL or the LGPL, and not to allow others to >+ * use your version of this file under the terms of the MPL, indicate your >+ * decision by deleting the provisions above and replace them with the notice >+ * and other provisions required by the GPL or the LGPL. If you do not delete >+ * the provisions above, a recipient may use your version of this file under >+ * the terms of any one of the MPL, the GPL or the LGPL. >+ * >+ * ***** END LICENSE BLOCK ***** */ >+ >+#include "nsIGenericFactory.h" >+ >+#include "nsCOMPtr.h" >+#include "nsXPCOM.h" >+#include "nsIServiceManager.h" >+#include "nsICategoryManager.h" >+ >+#include "nsMemory.h" >+ >+#include "nsIContentPolicy.h" >+#include "nsIObserver.h" >+ >+#define CSPSERVICE_CONTRACTID "@mozilla.org/cspservice;1" >+#define CSPSERVICE_CID \ >+{ 0x8d2f40b2, 0x4875, 0x4c95, \ >+ { 0x97, 0xd9, 0x3f, 0x7d, 0xca, 0x2c, 0xb4, 0x60 } } >+ >+class CSPService : public nsIContentPolicy >+{ >+public: >+ NS_DECL_ISUPPORTS >+ NS_DECL_NSICONTENTPOLICY >+ >+ CSPService(); >+ virtual ~CSPService(); >+ >+private: >+ PRBool mEnabled; >+}; >diff -r f46e6aee1335 content/base/src/nsDocument.cpp >--- a/content/base/src/nsDocument.cpp Tue Jul 07 10:10:04 2009 +1200 >+++ b/content/base/src/nsDocument.cpp Wed Jul 08 17:05:02 2009 -0700 >@@ -174,16 +174,19 @@ static NS_DEFINE_CID(kDOMEventGroupCID, > #include "mozAutoDocUpdate.h" > > #ifdef MOZ_SMIL > #include "nsSMILAnimationController.h" > #include "imgIContainer.h" > #include "nsSVGUtils.h" > #endif // MOZ_SMIL > >+// FOR CSP (autogenerated by xpidl) >+#include "IContentSecurityPolicy.h" >+ > > #ifdef MOZ_LOGGING > // so we can get logging even in release builds > #define FORCE_PR_LOG 1 > #endif > #include "prlog.h" > > #ifdef PR_LOGGING >@@ -2227,16 +2230,83 @@ nsDocument::StartDocumentLoad(const char > semicolon = start; > FindCharInReadable(';', semicolon, end); > mContentType = Substring(start, semicolon); > } > > RetrieveRelevantHeaders(aChannel); > > mChannel = aChannel; >+ >+// -------------- Initialize CSP >+ nsresult rv; >+ PRBool system; >+ nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager(); >+ nsAutoString cspHeaderValue; >+ nsIAtom* cspHeaderAtom = NS_NewAtom("x-content-security-policy"); >+ >+ this->GetHeaderData(cspHeaderAtom, cspHeaderValue); >+ >+ if(NS_SUCCEEDED(ssm->IsSystemPrincipal(NodePrincipal(), &system)) && system) { >+ //only makes sense to register new CSP if this document is not priviliged >+ return NS_OK; >+ } >+ >+ if(cspHeaderValue.IsEmpty()) { >+ // no CSP header present >+ return NS_OK; >+ } >+ >+#ifdef DEBUG >+ printf(" >> *** DOCUMENT specified CSP header\n"); >+#endif >+ >+ mCSP = do_CreateInstance("@mozilla.org/contentsecuritypolicy;1", &rv); >+ >+ if(NS_FAILED(rv)) { >+#ifdef DEBUG >+ printf("failed to create CSP object: %6x", rv); >+#endif >+ return rv; >+ } >+ >+#ifdef DEBUG >+ printf(" >> *** CSP Created \"%s\"\n", ToNewCString(cspHeaderValue)); >+#endif >+ >+ // Store the request context for violation reports >+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); >+ mCSP->ScanRequestData(httpChannel); >+ >+ // Start parsing the policy >+ nsCOMPtr<nsIURI> chanURI; >+ aChannel->GetURI(getter_AddRefs(chanURI)); >+ mCSP->RefinePolicy(cspHeaderValue, chanURI); >+#ifdef DEBUG >+ printf(" >> *** CSP policy refined.\n"); >+#endif >+ >+ // Check for frame-ancestor violation >+ nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer); >+ if(docShell != NULL) { >+ //printf(" >> *** CSP has a docShell\n"); >+ >+ PRBool safeAncestry = false; >+ // PermitsAncestry sends violation reports when necessary >+ rv = mCSP->PermitsAncestry(docShell, &safeAncestry); >+ if(NS_FAILED(rv)) { return rv; } >+ >+ if(!safeAncestry) { >+ printf(" >> *** CSP, ancestry is NOT ok.\n"); >+ // stop! ERROR page! >+ aChannel->Cancel( NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, 99) ); >+ } >+ } >+ >+// -------------- CSP initialized > > return NS_OK; > } > > void > nsDocument::StopDocumentLoad() > { > if (mParser) { >@@ -6561,16 +6631,17 @@ nsDocument::RetrieveRelevantHeaders(nsIC > > static const char *const headers[] = { > "default-style", > "content-style-type", > "content-language", > "content-disposition", > "refresh", > "x-dns-prefetch-control", >+ "x-content-security-policy", > // add more http headers if you need > // XXXbz don't add content-location support without reading bug > // 238654 and its dependencies/dups first. > 0 > }; > > nsCAutoString headerVal; > const char *const *name = headers; >@@ -6864,16 +6935,19 @@ nsDocument::CanSavePresentation(nsIReque > > void > nsDocument::Destroy() > { > // The ContentViewer wants to release the document now. So, tell our content > // to drop any references to the document so that it can be destroyed. > if (mIsGoingAway) > return; >+ >+ // Take down CSP >+ mCSP = 0; > > mIsGoingAway = PR_TRUE; > > RemovedFromDocShell(); > > PRUint32 i, count = mChildren.ChildCount(); > for (i = 0; i < count; ++i) { > mChildren.ChildAt(i)->DestroyContent(); >diff -r f46e6aee1335 docshell/base/nsDocShell.cpp >--- a/docshell/base/nsDocShell.cpp Tue Jul 07 10:10:04 2009 +1200 >+++ b/docshell/base/nsDocShell.cpp Wed Jul 08 17:05:02 2009 -0700 >@@ -3559,16 +3559,24 @@ nsDocShell::DisplayLoadError(nsresult aE > NS_ENSURE_ARG_POINTER(aURI); > // Get the host > nsCAutoString host; > aURI->GetHost(host); > CopyUTF8toUTF16(host, formatStrs[0]); > formatStrCount = 1; > error.AssignLiteral("netTimeout"); > } >+ else if ( NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY >+ && NS_ERROR_GET_CODE(aError) == 99) { >+ // CSP error >+ error.AssignLiteral("forbiddenFrameAncestor"); >+ cssClass.AssignLiteral("neterror"); >+ // TODO: localize this, or at least put the message into a resource >+ messageStr.AssignLiteral("CSP Blocked loading of this page because it doesn't trust the page embedding it."); >+ } > else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { > nsCOMPtr<nsINSSErrorsService> nsserr = > do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); > > PRUint32 errorClass; > if (!nsserr || > NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { > errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; >diff -r f46e6aee1335 layout/build/nsLayoutModule.cpp >--- a/layout/build/nsLayoutModule.cpp Tue Jul 07 10:10:04 2009 +1200 >+++ b/layout/build/nsLayoutModule.cpp Wed Jul 08 17:05:02 2009 -0700 >@@ -269,16 +269,17 @@ static nsresult Initialize(nsIModule* aS > static void Shutdown(); > > #ifdef MOZ_XTF > #include "nsIXTFService.h" > #include "nsIXMLContentBuilder.h" > #endif > > #include "nsGeolocation.h" >+#include "nsCSPService.h" > > // Transformiix > /* {0C351177-0159-4500-86B0-A219DFDE4258} */ > #define TRANSFORMIIX_XPATH1_SCHEME_CID \ > { 0xc351177, 0x159, 0x4500, { 0x86, 0xb0, 0xa2, 0x19, 0xdf, 0xde, 0x42, 0x58 } } > > /* 5d5d92cd-6bf8-11d9-bf4a-000a95dc234c */ > #define TRANSFORMIIX_NODESET_CID \ >@@ -852,16 +853,90 @@ CreateWindowControllerWithSingletonComma > NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMScriptObjectFactory) > NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseDOMException) > > #define NS_GEOLOCATION_SERVICE_CID \ > { 0x404d02a, 0x1CA, 0xAAAB, { 0x47, 0x62, 0x94, 0x4b, 0x1b, 0xf2, 0xf7, 0xb5 } } > > NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService) > >+static NS_METHOD >+CSPServiceRegistration(nsIComponentManager *aCompMgr, >+ nsIFile *aPath, >+ const char *registryLocation, >+ const char *componentType, >+ const nsModuleComponentInfo *info) >+{ >+ nsresult rv; >+ nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); >+ if (NS_FAILED(rv)) >+ return rv; >+ >+ nsCOMPtr<nsICategoryManager> catman; >+ rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, >+ NS_GET_IID(nsICategoryManager), >+ getter_AddRefs(catman)); >+ if (NS_FAILED(rv)) >+ return rv; >+ >+ char* previous = nsnull; >+ rv = catman->AddCategoryEntry("xpcom-startup", >+ "CSPService", >+ CSPSERVICE_CONTRACTID, >+ PR_TRUE, >+ PR_TRUE, >+ &previous); >+ if (previous) >+ nsMemory::Free(previous); >+ >+ rv = catman->AddCategoryEntry("content-policy", >+ "CSPService", >+ CSPSERVICE_CONTRACTID, >+ PR_TRUE, >+ PR_TRUE, >+ &previous); >+ if (previous) >+ nsMemory::Free(previous); >+ >+ return rv; >+} >+ >+static NS_METHOD >+CSPServiceUnregistration(nsIComponentManager *aCompMgr, >+ nsIFile *aPath, >+ const char *registryLocation, >+ const nsModuleComponentInfo *info){ >+ nsresult rv; >+ >+ nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); >+ if (NS_FAILED(rv)) >+ return rv; >+ >+ nsCOMPtr<nsICategoryManager> catman; >+ rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, >+ NS_GET_IID(nsICategoryManager), >+ getter_AddRefs(catman)); >+ if (NS_FAILED(rv)) >+ return rv; >+ >+ rv = catman->DeleteCategoryEntry("xpcom-startup", >+ "CSPService", >+ PR_TRUE); >+ >+ rv = catman->DeleteCategoryEntry("content-policy", >+ "CSPService", >+ PR_TRUE); >+ >+ return rv; >+} >+ >+NS_GENERIC_FACTORY_CONSTRUCTOR(CSPService) >+ >+ >+ > // The list of components we register > static const nsModuleComponentInfo gComponents[] = { > #ifdef DEBUG > { "Frame utility", > NS_FRAME_UTIL_CID, > nsnull, > CreateNewFrameUtil }, > { "Layout debugger", >@@ -1461,17 +1536,23 @@ static const nsModuleComponentInfo gComp > nsTextServicesDocumentConstructor }, > #endif > > { "Geolocation Service", > NS_GEOLOCATION_SERVICE_CID, > "@mozilla.org/geolocation/service;1", > nsGeolocationServiceConstructor }, > >- >+ { "Content Security Policy Service", >+ CSPSERVICE_CID, >+ CSPSERVICE_CONTRACTID, >+ CSPServiceConstructor, >+ CSPServiceRegistration, >+ CSPServiceUnregistration >+ }, > > { "Focus Manager", > NS_FOCUSMANAGER_CID, > "@mozilla.org/focus-manager;1", > CreateFocusManager }, > }; > > NS_IMPL_NSGETMODULE_WITH_CTOR(nsLayoutModule, gComponents, Initialize)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
Actions:
View
|
Diff
|
Review
Attachments on
bug 493857
:
378464
|
378466
|
382041
|
382990
|
384026
|
387098
|
387470
|
387569
|
388837
|
389817
|
390366
|
394402
|
394403
|
394589
|
395460
|
396000