<?php
// Official Script Homepage: http://www.clfsrpm.net/xss/

// This is free software. No guarantees are expressed or implied that this
//  software does what it is suppose to do.
// The author(s) of this software are NOT responsible for consequences of
//  it's use.
// Anyone who distributes this software is NOT responsible for consequences
//  of it's use unless the distributor specifies otherwise.
// USE AT YOUR OWN RISK.

// Demonstration version.
//  Note that the CSP spefication is NOT yet stable so this may need some
//  modification when it is. When I think it is ready for deployment it
//  will be released as version 1.0
//
// Copyright (c) 2009 Michael A. Peters <mpeters@mac.com>
//  All rights reserved.
// Distributed under the terms and conditions of the Common Public License 1.0
//  http://www.opensource.org/licenses/cpl1.0.php
// A text version of that license should have accompanied this distribution.
//
// Patches welcome with the understanding that sending them to me gives me
//  permission to use them.

// KNOWN BUGS - see http://www.clfsrpm.net/xss/index.php#Coleoptera

// for idna support - include this in the script that includes this
//  script or uncomment next line
//include_once('idna_convert.class.php');

class cspfilter {
   
/* Public Variables
    * Set them in your script after initiating new instance of class */
    
   // not used by the class
   
public $version "0.33";

   
// parameters for Content Security Policy
   
public $csp = Array('allow' => 'none',
             
'img-src'         => '',
             
'media-src'       => '',
             
'script-src'      => '',
             
'object-src'      => '',
             
'frame-src'       => '',
             
'frame-ancestors' => '',
             
'report-uri'      => '',
             
'style-src'       => '');
               
   
// if you are going to use makeCSP() - do you want to send a header?
   //  if left false, it just adds a meta tag to document. Set to true
   //  to actually send a header.
   // Since no browser implements CSP yet - calling makeCSP() is currently
   //  rather pointless.
   
public $cspHeader false;
   
   
// set to true to only allow script nodes in document head
   
public $scriptOnlyInHead false;
   
   
// the host used for filtering when a csp setting is set to self
   
public $httphost;
   
   
// option log file location for policy violations - must exist and
   //  be writable by the web server
   
public $policyLogFile '';

   
// private variables
   
private $resourceTypes = Array('img-src','media-src','script-src','object-src','frame-src','style-src');
   
   
// internal resolution of csp policy
   
private $pv = Array('allow' => 'none',
             
'img-src'         => '',
             
'media-src'       => '',
             
'script-src'      => '',
             
'object-src'      => '',
             
'frame-src'       => '',
             
'frame-ancestors' => '',
             
'report-uri'      => '',
             
'style-src'       => '');
             
   private 
$selfstyle false;
   
   private 
$cspContent;   
   private 
$blacklist;
   private 
$base "";

   private 
$eventAttributes = Array('onabort','onactivate',
      
'onafterprint','onafterupdate','onbeforeactivate',
      
'onbeforecopy','onbeforecut','onbeforedeactivate',
      
'onbeforeeditfocus','onbeforepaste','onbeforeprint',
      
'onbeforeunload','onbeforeupdate','onblur',
      
'onbounce','oncellchange','onchange',
      
'onclick','oncontextmenu','oncontrolselect',
      
'oncopy','oncut','ondataavailable',
      
'ondatasetchanged','ondatasetcomplete','ondblclick',
      
'ondeactivate','ondrag','ondragend',
      
'ondragenter','ondragleave','ondragover',
      
'ondragstart','ondrop','onerror',
      
'onerrorupdate','onfilterchange','onfinish',
      
'onfocus','onfocusin','onfocusout',
      
'onhelp','onkeydown','onkeypress',
      
'onkeyup','onlayoutcomplete','onload',
      
'onlosecapture','onmousedown','onmouseenter',
      
'onmouseleave','onmousemove','onmouseout',
      
'onmouseover','onmouseup','onmousewheel',
      
'onmove','onmoveend','onmovestart',
      
'onpaste','onpropertychange','onreadystatechange',
      
'onreset','onresize','onresizeend',
      
'onresizestart','onrowenter','onrowexit',
      
'onrowsdelete','onrowsinserted','onscroll',
      
'onselect','onselectionchange','onselectstart',
      
'onstart','onstop','onsubmit',
      
'onunload');
   
   private 
$occurOnce = Array('html','head','title','body');
   
// isindex appears to be head only in html 3.2 - 4.0 doesn't specify head only
   
private $headOnly  = Array('base','isindex','link','meta','style');
   private 
$noChildren = Array('base','basefont','br','col','embed','frame',
      
'hr','img','input','isindex','link','meta','param','source','script');
      
   private 
$policyTriggered false;
   
   
// whether or not to use idna
   
private $useidna false;
   
   
//private var $triggerDOM;
      
   // public functions
   
   
public function cspfilter($input) {
      
$this->dom $input;
      
// I highly recommend you overwrite the value that gets set below
      //  with one you set and know is clean. Do it the same way you
      //  set other public variables
      //
      
$this->httphost $_SERVER["HTTP_HOST"];
      
//
      // for violation reporting
      
$this->triggerDOM = new DOMDocument("1.0","UTF-8");
      
$string  $_SERVER['REQUEST_METHOD'] . " " $_SERVER['REQUEST_URI'] . " " $_SERVER['SERVER_PROTOCOL'];
      
$report  $this->triggerDOM->createElement("cspfilter-report");
      
$wrapped $this->triggerDOM->createTextNode($string);
      
$request $this->triggerDOM->createElement("request");
      
$request->appendChild($wrapped);
      
$report->appendChild($request);
      
// headers
      
$rqheaders "";
      if (
function_exists('apache_request_headers')) {
         foreach (
apache_request_headers() as $name => $value) {
            
$keyArray[] = strtolower($name);
            
$harray[] = $value;
            }
         } else {
         
$keyArray[] = 'host';
         
$harray[] = $this->httphost;
         if (isset(
$_SERVER['HTTP_ACCEPT'])) {
            
$keyArray[] = 'accept';
            
$harray[] = $_SERVER['HTTP_ACCEPT'];
            }
         if (isset(
$_SERVER['HTTP_USER_AGENT'])) {
            
$keyArray[] = 'user-agent';
            
$harray[] = $_SERVER['HTTP_USER_AGENT'];
            }
         }
      for (
$i=0$i<sizeof($keyArray); $i++) {
         
$fooA explode('-',$keyArray[$i]);
         for (
$j=0;$j<sizeof($fooA);$j++) {
            
$fooA[$j] = ucfirst($fooA[$j]);
            }
         
$headername implode('-',$fooA);
         
$rqheaders .= $headername ": " $harray[$i] . "\n";
         }
         
      
$rqheaders preg_replace('/\n$/','',$rqheaders);
      
$rqTextNode $this->triggerDOM->createTextNode($rqheaders);
      
//$headers = $this->triggerDOM->createElement("request-headers",$rqheaders);
      
$headers $this->triggerDOM->createElement('request-headers');
      
$headers->appendChild($rqTextNode);
      
      
$report->appendChild($headers);
      
$this->triggerDOM->appendChild($report);
      
// punycode
      
if (class_exists('idna_convert')) {
         
$this->useidna true;
         
$this->IDN = new idna_convert();
         }
      }
      
   public function 
processData() {
      
// chews the data - call after inputDom function
      
$this->getFilterOptions();
      if (
$this->checkSource($this->httphost,$this->pv['style-src'])) {
         
$this->selfstyle true;
         }
      
$this->blacklist implode(' ',$this->eventAttributes);
      
$this->walkTheDog();
      
$this->getBase();
      
$this->deleteExtraOnce();
      
$this->deleteNotInHead();
      
$this->deleteIllegitimateChildren();
      
$this->metaFirst();
      
$this->forbiddenTags();
      
$this->removeCSPMeta();
      
$this->sendReport();
      }
      
   public function 
makeCSP() {
      
// creates the meta tag or send the header
      // not called within the class, call it from your script
      
if (strlen($this->csp['allow']) == 0) {
         
$this->cspContent "allow none";
         } else {
         
$this->cspContent "allow " $this->csp['allow'];
         }
         
      for (
$i=0$i<sizeof($this->resourceTypes); $i++) {
         
$type $this->resourceTypes[$i];
         if (
strlen($this->csp[$type]) > 0) {
            
$this->cspContent .= "; $type " $this->csp[$type];
            }
         }
      if (
strlen($this->csp['frame-ancestors']) > 0) {
         
$this->cspContent .= "; frame-ancestors " $this->csp['frame-ancestors'];
         }
      if (
strlen($this->csp['report-uri']) > 0) {
         
$this->cspContent .= "; report-uri " $this->csp['report-uri'];
         }
      if (
$this->cspHeader == true) {
         
$header "X-Content-Security-Policy: " $this->cspContent;
         
header($header);
         } else {
         
$meta $this->dom->createElement("meta");
         
$meta->setAttribute("http-equiv","X-Content-Security-Policy");
         
$meta->setAttribute("content",$this->cspContent);
         
$headtags $this->dom->getElementsByTagName("head");
         
$head $headtags->item(0);
         
//
         // darn I wish there was a prependChild function
         //
         
$newHead $this->dom->createElement('head');
         
$newHead->appendChild($meta);
         
// copy attributes
         
if ($head->hasAttribute("xmlns")) {
            
$xmlnsValue $head->getAttribute("xmlns");
            
$newHead->setAttribute("xmlns",$xmlnsValue);
            }
         
$attributes $head->attributes;
         foreach (
$attributes as $attribute) {
            if (
strlen($attribute->namespaceURI) > 0) {
               
// namespaced attribute
               
$NS $attribute->namespaceURI;
               
$NSname  $attribute->prefix ":" $attribute->name;
               
$NSvalue $attribute->value;
               
$newHead->setAttributeNS($NS,$NSname,$NSvalue);
               } elseif(
strcmp($attribute->name,'xmlns') != 0) {
               
$name $attribute->name;
               
$value $head->getAttribute($attribute->name);
               
$newHead->setAttribute($name,$value);
               }
            }
         
// get all children from old head
         
$children $head->childNodes;
         foreach (
$children as $child) {
            
// clone node and add it to newHead EXCEPT for existing cspfilter meta
            
$newChild $child->cloneNode(true);
            
$newHead->appendChild($newChild);
            }
         
// replace the old head
         
$head->parentNode->replaceChild($newHead,$head);
         }
      }
      
   
// this next function only exists for live test page
   
public function displayViolationReport() {
      if (
$this->policyTriggered == true) {
         
$this->triggerDOM->formatOutput true;
         
$report $this->triggerDOM->saveXML();
         
//$report = preg_replace('/^<\?xml[^>]*>\n/','',$report,1);
         
return $report;
         } else {
         return 
"";
         }
      }
      
   private function 
cleanHostString($input) {
      
$pre[]  ='/^self$/';
      
$post[] =$this->httphost;
      
$pre[]  ='/\sself\s/';
      
$post[] =" $this->httphost ";
      
$pre[]  ='/\sself$/';
      
$post[] =" $this->httphost";
      
$pre[]  ='/^self\s/';
      
$post[] ="$this->httphost ";
      return 
preg_replace($pre,$post,$input);
      }
      
   private function 
punifyhostlist($input) {
      
$output="";
      if (
strlen($input) > 0) {
         
$tmpArray explode(' ',$input);
         for (
$i=0$i<sizeof($tmpArray); $i++) {
            
$tmpArray[$i] = $this->IDN->encode($tmpArray[$i]);
            }
         
$output implode(' ',$tmpArray);
         }
      return 
$output;
      }
      
   private function 
getFilterOptions() {
      
$foo strtolower(trim($this->csp['allow']));
      
$this->csp['allow'] = preg_replace('/\s+/',' ',$foo);
      if (
$this->useidna == true) {
         
$this->csp['allow'] = $this->punifyhostlist($this->csp['allow']);
         }
            
      for (
$i=0$i<sizeof($this->resourceTypes); $i++) {
         
$type $this->resourceTypes[$i];
         
$foo strtolower(trim($this->csp[$type]));
         
$this->csp[$type] = preg_replace('/\s+/',' ',$foo);
         if (
$this->useidna == true) {
            
$this->csp[$type] = $this->punifyhostlist($this->csp[$type]);
            }
         }
         
      
$foo trim($this->csp['frame-ancestors']); 
      
$this->csp['frame-ancestors'] = preg_replace('/\s+/',' ',$foo);
      if (
$this->useidna == true) {
         
$this->csp['frame-ancestors'] = $this->punifyhostlist($this->csp['frame-ancestors']);
         }
      
      
$this->csp['report-uri'] = trim($this->csp['report-uri']);
      if (
strlen($this->csp['report-uri']) > 0) {
         if (! 
$this->checkSource($this->csp['report-uri'],$this->httphost)) {
            
$string "Invalid report-uri: " $this->csp['report-uri'];
            
$this->csp['report-uri'] = '';
            
$this->policyReport($string);
            }
         if (
$this->useidna == true) {
            
$this->csp['report-uri'] = $this->punifyhostlist($this->csp['report-uri']);
            }
         }
      
      if (
strlen($this->csp['allow']) == 0) {
         
$this->csp['allow'] = 'none';
         }
      
$this->pv['allow'] = $this->cleanHostString($this->csp['allow']);
      
      for (
$i=0$i<sizeof($this->resourceTypes); $i++) {
         
$type $this->resourceTypes[$i];
         if (
strlen($this->csp[$type]) > 0) {
            
$this->pv[$type] = $this->cleanHostString($this->csp[$type]);
            } else {
            
$this->pv[$type] = $this->pv['allow'];
            }
         }
      
      if (
$this->useidna == true) {
         
$this->httphost $this->IDN->encode($this->httphost);
         }
           
      if (
$this->scriptOnlyInHead == true) {
         
$this->headOnly[] = "script";
         }
      }
      
   private function 
obfus($input,$src=0) {
      
// this function only intended to operates on attribute values
      //
      // based on http://kallahar.com/smallprojects/php_xss_filter_function.php
      //  which is public domain. So as far as I'm concerned, this function
      //  is public domain.
      //
      // should I add \t \r \n to the first preg_replace ?? Need to check for them somewhere
      //  to avoid script: dodging.
      
$return $input;
      for (
$i=0;$i<9;$i++) {
         
$ser[] = '/\x0' $i '/i';
         
$ser[] = '/\x1' $i '/i';
         }
      
$ser[] = '/\x0b/i';
      
$ser[] = '/\x0c/i';
      
$ser[] = '/\x0e/i';
      
$ser[] = '/\x0f/i';
      
$ser[] = '/\x19/i';
      
      
$return preg_replace($ser,'',$return);
      
//$return = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $return);
      
$search 'abcdefghijklmnopqrstuvwxyz';
      
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      
$search .= '1234567890!@#$%^&*()';
      
$search .= '~`";:?+/={}[]-_|\'\\';
      for (
$i 0$i strlen($search); $i++) {
         
$return preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i'$search[$i], $return);
         
$return preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/'$search[$i], $return);
         }
      
$scriptArray = Array('javascript','mocha','vbscript');
      
$found true;
      while (
$found == true) {
         
$return_before $return;
         for (
$i 0$i sizeof($scriptArray); $i++) {
            
$pattern '/';
            for (
$j 0$j strlen($scriptArray[$i]); $j++) { 
               if (
$j 0) {
                  
$pattern .= '(';
                  
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
                  
$pattern .= '|\s';
                  
$pattern .= '|(&#0{0,8}([9|10|13]);)';
                  
$pattern .= ')*';
                  }
               
$pattern .= $scriptArray[$i][$j];
               }
            
$pattern .= '/i';
            
$return preg_replace($pattern$scriptArray[$i], $return);
            if (
strcmp($return_before,$return) == 0) {
               
$found false;
               }
            }
         }
      
      
$search '/(javascript|mocha|vbscript):[^\s]*/i';
      
$return preg_replace($search,'',$return);
      if (
$src != 0) {
         
// make sure relative src attributes start with only one slash
         
$return preg_replace('/^\s*[\/]+/','/',$return);
         }
      if (
strcmp($input,$return) != 0) {
         
$this->policyReport("Attribute Obfuscation");
         
$return preg_replace('/^\s+/','',$return);
         }
      return 
$return;
      }
      
   private function 
filterAttributes($node) {
      
// filters the attribute names and content
      
if ($node->hasAttribute("xmlns")) {
         
$xmlnsValue $node->getAttribute("xmlns");
         
$saniAtt[] = 'xmlns';
         
$saniVal[] = $xmlnsValue;
         
$oldAtt[]  = 'xmlns';
         }
      
$attributes $node->attributes;
      foreach (
$attributes as $attribute) {
         
$pattern '/[^a-z0-9-]+/i';
         
$clean strtolower(preg_replace($pattern,'',$attribute->name));
         if (
strcmp($clean,$attribute->name) != 0) {
            
$this->policyReport("Invalid Attribute Name");
            }
            
         if (
strlen($attribute->namespaceURI) > 0) {
            
// namespaced attribute
            
$NS[] = $attribute->namespaceURI;
            
$NSsaniAtt[] = $attribute->prefix ":" $clean;
            
$NSsaniVal[] = $this->obfus($attribute->value);
            
$NSoldAtt[]  = $attribute->name;
            } elseif(
strcmp($attribute->name,'xmlns') != 0) {
            
$saniAtt[]   = $clean;
            
$oldAtt[]    = $attribute->name;
            if (
strcmp($clean,"value") == 0) {
               
// don't mutilate value attributes
               
$saniVal[] = $attribute->value;
               } else {
               switch (
$clean) {
                  case 
'src':
                     
$saniVal[] = $this->obfus($attribute->value,1);
                     break;
                  case 
'data':
                     
$saniVal[] = $this->obfus($attribute->value,1);
                     break;
                  case 
'code':
                     
$saniVal[] = $this->obfus($attribute->value,1);
                     break;
                  default:
                     
$saniVal[] = $this->obfus($attribute->value);
                  } 
// end of switch
               
// end check for value attribute
            
// end check if namespaced
         
// end attributes loop
            
      
if (isset($oldAtt)) {
         for (
$i=0$i<sizeof($oldAtt);$i++) {
            
$node->removeAttribute($oldAtt[$i]);
            }
         }
      if (isset(
$saniAtt)) {
         for (
$i=0$i<sizeof($saniAtt);$i++) {
            
$check " " $saniAtt[$i] . " ";
            if (
substr_count($this->blacklist$check) == 0) {
               if (
$this->selfstyle == true) {
                  
$node->setAttribute($saniAtt[$i],$saniVal[$i]);
                  } else {
                  if (
strcmp($saniAtt[$i],'style') == 0) {
                     
$string "Style attribute removed";
                     
$this->policyReport($string);
                     } else {
                     
$node->setAttribute($saniAtt[$i],$saniVal[$i]);
                     }
                  }
               } else {
               
$string "Blacklisted Event Attribute: " $saniAtt[$i];
               
$this->policyReport($string);
               }
            }
         }
      if (isset(
$NSoldAtt)) {
         for (
$i=0$i<sizeof($NSoldAtt);$i++) {
            
$node->removeAttributeNS($NS[$i],$NSoldAtt[$i]);
            }
         }
      if (isset(
$NSsaniAtt)) {
         for (
$i=0$i<sizeof($NSsaniAtt);$i++) {
            
$node->setAttributeNS($NS[$i],$NSsaniAtt[$i],$NSsaniVal[$i]);
            }
         }
      }
      
   private function 
changeNodeElement($node,$element) {
      
// ugh - why doesn't php let you change the element tag easily ??
      
$newNode $this->dom->createElement($element);
      
// get all attributes from old node
      
if ($node->hasAttribute("xmlns")) {
         
$xmlnsValue $node->getAttribute("xmlns");
         
$newNode->setAttribute("xmlns",$xmlnsValue);
         }
      
$attributes $node->attributes;
      foreach (
$attributes as $attribute) {
         if (
strlen($attribute->namespaceURI) > 0) {
            
// namespaced attribute
            
$NS $attribute->namespaceURI;
            
$NSname  $attribute->prefix ":" $attribute->name;
            
$NSvalue $attribute->value;
            
$newNode->setAttributeNS($NS,$NSname,$NSvalue);
            } elseif(
strcmp($attribute->name,'xmlns') != 0) {
            
$name $attribute->name;
            
$value $node->getAttribute($attribute->name);
            
$newNode->setAttribute($name,$value);
            }
         }
      
      
// get all children from old node
      
$children $node->childNodes;
      foreach (
$children as $child) {
         
// clone node and add it to newNode
         
$newChild $child->cloneNode(true);
         
$newNode->appendChild($newChild);
         }
      
// replace the old node with the newNode
      
$node->parentNode->replaceChild($newNode,$node);
      }
      
   private function 
checkNodeTag($node) {
      
$tag $node->tagName;
      
$pattern '/[^a-z0-9]+/i';
      
$clean preg_replace($pattern,'',$tag);
      
$newTag strtolower($clean);
      if (
$tag != $newTag) {
         
$this->policyReport("Invalid Node Name");
         
$this->changeNodeElement($node,$newTag);
         }
      }
      
   private function 
walkTheDog() {
      
// Makes sure all element tags and attribute names are lower case.
      //  also triggers filter dodging checks and event attribute filtering
      
$elements $this->dom->getElementsByTagName("*");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         
$this->filterAttributes($element);
         
$this->checkNodeTag($element);
         }
      }
      
   private function 
getBase() {
      
$elements $this->dom->getElementsByTagName("base");
      foreach (
$elements as $element) {
         if (
$element->hasAttribute('href')) {
            
$this->base $element->getAttribute('href');
            }
         }
      }
      
      
   private function 
mycompare($needle,$haystack) {
      
$hayOne substr($haystack,0,1);
      if (
strcmp($hayOne,"*") == 0) {
         
$haystack preg_replace('/^[\*]/','',$haystack);
         
$length   = (strlen($haystack));
         
$needle   substr($needle,$length);
         }      
      if (
strcmp($needle,$haystack) == 0) {
         return 
1;
         } else {
         return 
0;
         }
      }
         
   private function 
checkSource($src,$pv,$base='') {
      if (
$this->useidna == true) {
         if (
strlen($src) > 0) {
            
$src $this->IDN->encode($src);
            }
         if (
strlen($base) > 0) {
            
$base $this->IDN->encode($base);
            }
         }
      
// returns true if source is valid
      
if ($pv == "none") {
         return 
false;
         } elseif (
$pv == "*") {
         return 
true;
         } else {
         
$src trim(strtolower($src));
         
// replace http:// and https:// with a |
         
$src preg_replace('/^\s*http[s]{0,1}:\/\//','|',$src);
         if (
strlen($base) > 0) {
            
$base preg_replace('/^\s*http[s]{0,1}:\/\//','',$base);
            } else {
            
$base $this->httphost;
            }
         
// replace src that don't start with a | with the $base
         
$src preg_replace('/^[^\|].*/',$base,$src);
         
// remove the | and make sure at least one / exists
         
$src preg_replace('/^[\|]/','',$src) . "/";
         
$farray explode('/',$src);
         
$src $farray[0];
         
$allowed_array explode(" ",$pv);
         
$match 0;
         for (
$i=0$i<sizeof($allowed_array); $i++) {
            
$match $match $this->mycompare($src,$allowed_array[$i]);
            }
         if (
$match == 0) {
            return 
false;
            } else {
            return 
true;
            }
         }
      }
      
   private function 
getElementsByAttribute($attribute) {
      
// not currently used - but keeping here anyway in case I decide to at some point
      
$tags $this->dom->getElementsByTagName("*");
      foreach (
$tags as $tag) {
         if (
$tag->hasAttribute($attribute)) {
            
$return[] = $tag;
            }
         }
      if (! isset(
$return)) {
         
$return = Array();
         }
      return 
$return;
      }
      
   private function 
deleteExtraOnce() {
      
// nukes extra elements that are only suppose to occure once - IE body tag
      
for ($i=0;$i<sizeof($this->occurOnce);$i++) {
         
$elements $this->dom->getElementsByTagName($this->occurOnce[$i]);
         for (
$j $elements->length; --$j 0; ) {
            
$element $elements->item($j);
            
$element->parentNode->removeChild($element);
            
$string "Extra " $this->occurOnce[$i] . " Element";
            
$this->policyReport($string);
            }
         }
      }
      
   private function 
forbiddenTags() {
      
// nukes tags that are forbidden by the CSP
      
$this->filterImg();
      
$this->filterScript();
      
$this->filterObject();
      
$this->filterEmbed();
      
$this->multimedia();
      
$this->filterApplet();
      
$this->filterFrame();
      
$this->filterStyle();
      }
      
   private function 
nodeToDiv($node) {
      
$div $this->dom->createElement("div");
      
$children $node->childNodes;
      foreach (
$children as $child) {
         
// clone node and add it to newNode
         
$clone true;
         if (
$child->nodeType == XML_ELEMENT_NODE) {
            if (
strcmp($child->tagName,"param") == 0) {
               
$clone false;
               }
            }
         if (
$clone == true) {
            
$newChild $child->cloneNode(true);
            
$div->appendChild($newChild);
            }
         }
      
// replace the old node with the newNode
      
$node->parentNode->replaceChild($div,$node);
      }
      
   private function 
metaFirst() {
      
$heads $this->dom->getElementsByTagName('head');
      
$head $heads->item(0);
      
$metaLegal true;
      
$children $head->childNodes;
      foreach (
$children as $child) {
         if (
$child->nodeType == XML_ELEMENT_NODE) {
            
$tag $child->tagName;
            if (
$tag == 'meta') {
               if (
$metaLegal == false) { 
                  
$child->parentNode->removeChild($child);
                  
$this->policyReport('meta tag after non-meta tag in head');
                  }
               } else {
               
$metaLegal false;
               }
            }
         }
      }
      
   private function 
filterImg() {
      
$elements $this->dom->getElementsByTagName("img");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         if (
$element->hasAttribute("src")) {
            
$src trim($element->getAttribute("src"));
            if (! 
$this->checkSource($src,$this->pv['img-src'],$this->base)) {
               if (
$element->hasAttribute("alt")) {
                  
$altTag $element->getAttribute("alt");
                  
$txt $this->dom->createTextNode($altTag);
                  
$element->parentNode->replaceChild($txt,$element);
                  } else {
                  
$element->parentNode->removeChild($element);
                  }
               
$this->policyReport($src,'img-src');
               }
            }
         }
      }
      
   private function 
filterStyle() {
      
$elements $this->dom->getElementsByTagName("link");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         
// is the element a style sheet ??
         
if ($element->hasAttribute('type')) {
            
$type strtolower(trim($element->getAttribute('type')));
            if (
strcmp($type,'text/css') == 0) {
               
// it's a style sheet
               
if ($element->hasAttribute('href')) {
                  
$src trim($element->getAttribute('href'));
                  if (! 
$this->checkSource($src,$this->pv['style-src'],$this->base)) {
                     
$element->parentNode->removeChild($element);
                     
$this->policyReport($src,'style-src');
                     }
                  }
               }
            }
         }
      if (
$this->selfstyle == false) {
         
$elements $this->dom->getElementsByTagName("style");
         for (
$j $elements->length; --$j >= 0; ) {
            
$element $elements->item($j);
            
$element->parentNode->removeChild($element);
            
$this->policyReport('Style node removed');
            }
         }
      }
      
   private function 
filterScript() {
      
$elements $this->dom->getElementsByTagName("script");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         if (
$element->hasAttribute("src")) {
            
$src trim($element->getAttribute("src"));
            if (! 
$this->checkSource($src,$this->pv['script-src'],$this->base)) {
               
$element->parentNode->removeChild($element);
               
$this->policyReport($src,'script-src');
               }
            } else {
            
// no src - yank it
            
$element->parentNode->removeChild($element);
            
$this->policyReport('Script with no URI');
            }
         }
      }
      
   private function 
filterObject() {
      
$elements $this->dom->getElementsByTagName("object");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         if (
$element->hasAttribute("data")) {
            
$data trim($element->getAttribute("data"));
            } elseif (
$element->hasAttribute("src")) {
            
$data trim($element->getAttribute("src"));
            }
         if (
$element->hasAttribute("codebase")) {
            
$codebase trim($element->getAttribute("src"));
            }
            
         if (! isset(
$data)) {
            
//
//            $element->parentNode->removeChild($element);
//            $this->policyReport('Object with no URI');
            
$foo 'foo';
            } else {
            if (! isset(
$codebase)) {
               
$codebase '';
               }
            
$res $this->checkSource($data,$this->pv['object-src'],$codebase);
            if (
$res == false) {
               if (
$element->hasChildNodes()) {
                  
$this->nodeToDiv($element);
                  } else {
                  
$element->parentNode->removeChild($element);
                  }
               
$this->policyReport($data,'object-src');
               }
            } 
// end check if data set
         
// end for loop
      
// end function
      
   
private function filterEmbed() {
      
$elements $this->dom->getElementsByTagName('embed');
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         if (
$element->hasAttribute("src")) {
            
$src trim($element->getAttribute("src"));
            if (! 
$this->checkSource($src,$this->pv['object-src'],$this->base)) {
               
$element->parentNode->removeChild($element);
               
$this->policyReport($src,'object-src');
               }
            } else {
            
$element->parentNode->removeChild($element);
            
$string "Embed with no URI";
            
$this->policyReport($string);
            } 
// end if has attribute source
         
// end for loop
      
// end function
      
   
private function multimedia() {
      
$this->mediaSource();
      
$multi = Array('audio','video');
      for (
$i=0;$i<sizeof($multi);$i++) {
         
$elements $this->dom->getElementsByTagName($multi[$i]);
         for (
$j $elements->length; --$j >= 0; ) {
            
$element $elements->item($j);
            if (
$element->hasAttribute("src")) {
               
$src trim($element->getAttribute("src"));
               if (! 
$this->checkSource($src,$this->pv['media-src'],$this->base)) {
                  if (
$element->hasChildNodes()) {
                     
$this->nodeToDiv($element);
                     } else {
                     
$element->parentNode->removeChild($element);
                     }
                  
$this->policyReport($src,'media-src');
                  }
               } else {
               
// CHECK for source child nodes
               
if ($element->hasChildNodes()) {
//                  $sourceChildren = 0;
                  
$sourceChildren 1;
                  
$children $element->childNodes;
//                  foreach ($children as $child) {
//                     if (strcmp($child->tagName,"source") == 0) {
//                        $sourceChildren++;
//                        }
//                     }
                  
if ($sourceChildren == 0) {
                     
$this->nodeToDiv($element);
                     }
                  } else {
                  
$element->parentNode->removeChild($element);
                  
$string ucfirst($multi[$i]) . " with no URI";
                  
$this->policyReport($string);
                  }
               }
            }
         }
      }
      
   private function 
mediaSource() {
      
$elements $this->dom->getElementsByTagName('source');
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         
// nuke if it doesn't have proper source
         
if ($element->hasAttribute("src")) {
            
$src trim($element->getAttribute("src"));
            if (! 
$this->checkSource($src,$this->pv['media-src'],$this->base)) {
               
$element->parentNode->removeChild($element);
               
$this->policyReport($src,'media-src');
               }
            } else {
            
$element->parentNode->removeChild($element);
            
$string "Multimedia Source with no URI";
            
$this->policyReport($string);
            }         
         }
      }
      
   private function 
filterApplet() {
      
$elements $this->dom->getElementsByTagName("applet");
      for (
$j $elements->length; --$j >= 0; ) {
         
$element $elements->item($j);
         if (
$element->hasAttribute("code")) {
            
$data trim($element->getAttribute("code"));
            } elseif (
$element->hasAttribute("src")) {
            
$data trim($element->getAttribute("src"));
            }
         if (
$element->hasAttribute("codebase")) {
            
$codebase trim($element->getAttribute("src"));
            }
            
         if (! isset(
$data)) {
            
$element->parentNode->removeChild($element);
            
$this->policyReport('Applet with no URI');
            } else {
            if (isset(
$codebase)) {
               
$res $this->checkSource($data,$this->pv['object-src'],$codebase);
               } else {
               
$res $this->checkSource($data,$this->pv['object-src']);
               }
            if (
$res == false) {
               if (
$element->hasChildNodes()) {
                  
$this->nodeToDiv($element);
                  } else {
                  
$element->parentNode->removeChild($element);
                  }
               
$this->policyReport($data,'object-src');
               }
            } 
// end check if data set
         
// end for loop
      
// end function
      
   
private function filterFrame() {
      
$frmScope = Array('frame','iframe');
      for (
$i=0$i<2$i++) {
         
$elements $this->dom->getElementsByTagName($frmScope[$i]);
         for (
$j $elements->length; --$j >= 0; ) {
            
$element $elements->item($j);
            if (
$element->hasAttribute("src")) {
               
$src trim($element->getAttribute("src"));
               if (! 
$this->checkSource($src,$this->pv['frame-src'],$this->base)) {
                  if (
$element->hasChildNodes()) {
                     
$this->nodeToDiv($element);
                     } else {
                     
$element->parentNode->removeChild($element);
                     }
                  
$this->policyReport($src,'frame-src');
                  }
               } else {
               
$element->parentNode->removeChild($element);
               
$string ucfirst($frmScope[$i]) . " with no URI";
               
$this->policyReport($string);
               }
            }
         }
      }
      
   private function 
deleteNotInHead() {
      
// nukes elements that are suppose to be in head but are not
      
for ($i=0;$i<sizeof($this->headOnly);$i++) {
         
$elements $this->dom->getElementsByTagName($this->headOnly[$i]);
         for (
$j $elements->length; --$j >= 0; ) {
            
$element $elements->item($j);
            
$parent $element->parentNode->tagName;
            if (
$parent != "head") {
               
$string $this->headOnly[$i] . " element not in head";
               
$element->parentNode->removeChild($element);
               
$this->policyReport($string);
               }
            }
         }
      }
      
   private function 
deleteIllegitimateChildren() {
      
// nukes children of elements that ain't suppose to have children
      
for ($i=0;$i<sizeof($this->noChildren);$i++) {
         
$elements $this->dom->getElementsByTagName($this->noChildren[$i]);
         foreach (
$elements as $element) {
            
$children $element->childNodes;
            for (
$j $children->length; --$j >= 0; ) {
               
$child $children->item($j);
               
$element->removeChild($child);
               
$string "Illegal child node for " $this->noChildren[$i];
               
$this->policyReport($string);
               }
            }
         }
      }
      
   private function 
policyReport($blocked,$policy='') {
      
$elements $this->triggerDOM->getElementsByTagName('cspfilter-report');
      
$report $elements->item(0);
      if (
strlen($policy) > 0) {
         
$blocked $this->triggerDOM->createElement("blocked-uri",$blocked);
         
$blocked->setAttribute($policy,$this->pv[$policy]);
         } else {
         
$blocked $this->triggerDOM->createElement("misc",$blocked);
         }
      
      
$report->appendChild($blocked);
      
$this->policyTriggered true;
      }
      
   private function 
sendReport() {
      if (
$this->policyTriggered == true) {
         if (
file_exists($this->policyLogFile)) {
            
$this->writeReport();
            } elseif (
strlen($this->csp['report-uri']) > 0) {
            
$this->httpPostReport();
            }
         }
      }
      
   private function 
writeReport() {
      
$this->triggerDOM->formatOutput true;
      
$report $this->triggerDOM->saveXML();
      
$report preg_replace('/^<\?xml[^>]*>\n/','',$report,1);
      
$fh fopen($this->policyLogFile'a');
      
fwrite($fh$report);
      
fclose($fh);
      }
      
   private function 
httpPostReport() {
      
$rui $this->csp['report-uri'];
      
$report $this->triggerDOM->saveXML();
      
$handle curl_init();
      
curl_setopt($handleCURLOPT_URL,$rui);
      
curl_setopt($handleCURLOPT_POST1);
      
curl_setopt($handleCURLOPT_POSTFIELDS,$report);
      
curl_setopt($handleCURLOPT_HTTP_VERSIONCURL_HTTP_VERSION_1_1);
      
curl_setopt($handleCURLOPT_CONNECTTIMEOUT5);
      
$result curl_exec ($handle);
      
curl_close ($handle);
      }
      
   private function 
removeCSPMeta() {
      
$elements $this->dom->getElementsByTagName('meta');
      foreach (
$elements as $element) {
         if (
$element->hasAttribute('http-equiv')) {
            
$equiv strtolower($element->getAttribute('http-equiv'));
            if (
strcmp($equiv,'x-content-security-policy') == 0) {
               
$element->parentNode->removeChild($element);
               
$this->policyReport('Removed existing CSP meta tag');
               }
            }
         }
      }
      
      
/* ---- */
   
// end of class
?>