Location: PHPKode > scripts > CSP Filter > csp-filter/cspfilter_class.php
<?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 <hide@address.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.25";

   // 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;
   
   // array of event Attributes you don't want eaten by class.
   //  Note that if script is not allowed, whitelist ignored.
   public $eventWhitelist = Array();
   
   // 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 $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','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);
      $headers = $this->triggerDOM->createElement("request-headers",$rqheaders);
      
      $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();
      $this->makeBlackList();
      $this->walkTheDog();
      $this->getBase();
      $this->deleteExtraOnce();
      $this->deleteNotInHead();
      $this->deleteIllegitimateChildren();
      $this->metaFirst();
      $this->forbiddenTags();
      $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);
         //
         // damn 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
            $copyChild = true;
            if ($child->nodeType == XML_ELEMENT_NODE) {
               if (strcmp($child->tagName,'meta') == 0) {
                  if ($child->hasAttribute('http-equiv')) {
                     $equiv = strtolower($child->getAttribute('http-equiv'));
                     if (strcmp($equiv,'x-content-security-policy') == 0) {
                        $copyChild = false;
                        }
                     }
                  }
               }
            if ($copyChild == true) {
               $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 getFilterOptions() {
      $foo = strtolower(trim($this->csp['allow']));
      $this->csp['allow'] = preg_replace('/\s+/',' ',$foo);
      
      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);
         }
         
      $foo = trim($this->csp['frame-ancestors']); 
      $this->csp['frame-ancestors'] = preg_replace('/\s+/',' ',$foo);
      
      $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 (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->scriptOnlyInHead == true) {
         $this->headOnly[] = "script";
         }
      }
      
   private function makeBlackList() {
      // creates black list of attributes we don't allow, taking whitelist
      //  into account
      $this->blacklist = " ";
      for ($i=0; $i<sizeof($this->eventAttributes); $i++) {
         $white = false;
         if (strcmp($this->pv['script-src'],'none') != 0) {
            for ($j=0; $j<sizeof($this->eventWhitelist); $j++) {
               if (strcmp($this->eventAttributes[$i],$this->eventWhitelist[$j]) == 0) {
                  $white = true;
                  } // end specific check if in whitelist
               } // end general check if in whitelist
            } // end check if script set to none
         if ($white == false) {
            $this->blacklist .= $this->eventAttributes[$i] . " ";
            }
         } // end walk through eventAttributes
      } // end of function
      
   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) {
               $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   = (0 - 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) {
         $src = utf8_encode($src);
         $src = $this->IDN->encode($src);
         if (strlen($base) > 0) {
            $base = utf8_encode($base);
            $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');
                     }
                  }
               }
            }
         }
      }
      
   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');
            } 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() {
      $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 {
               if ($element->hasChildNodes()) {
                  $this->nodeToDiv($element);
                  } else {
                  $element->parentNode->removeChild($element);
                  }
               $string = ucfirst($multi[$i]) . " 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($handle, CURLOPT_URL,$rui);
      curl_setopt($handle, CURLOPT_POST, 1);
      curl_setopt($handle, CURLOPT_POSTFIELDS,$report);
      curl_setopt($handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
      curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
      $result = curl_exec ($handle);
      curl_close ($handle);
      }
      
      
/* ---- */
   } // end of class
?>
Return current item: CSP Filter