elements with a dynamic 'name' attribute could be used to bypass this * restriction. For the same reason, DisallowCopy, DisallowDisableOutputEscaping, * DisallowDynamicAttributeNames, DisallowDynamicElementNames and DisallowUnsafeCopyOf should * all be enabled too */ abstract class AbstractFlashRestriction extends TemplateCheck { /** * @var string Name of the default setting */ public $defaultSetting; /** * @var string Name of the highest setting allowed */ public $maxSetting; /** * @var bool Whether this restriction applies only to elements using any kind of dynamic markup: * XSL elements or attribute value templates */ public $onlyIfDynamic; /** * @var string Name of the restricted setting */ protected $settingName; /** * @var array Valid settings */ protected $settings; /** * @var DOMElement node */ protected $template; /** * Constructor * * @param string $maxSetting Max setting allowed * @param bool $onlyIfDynamic Whether this restriction applies only to elements using any kind * of dynamic markup: XSL elements or attribute value templates */ public function __construct($maxSetting, $onlyIfDynamic = false) { $this->maxSetting = $maxSetting; $this->onlyIfDynamic = $onlyIfDynamic; } /** * Test for the set Flash restriction * * @param DOMElement $template node * @param Tag $tag Tag this template belongs to * @return void */ public function check(DOMElement $template, Tag $tag) { $this->template = $template; $this->checkEmbeds(); $this->checkObjects(); } /** * Test given element's attributes * * @param DOMElement $embed Context element * @return void */ protected function checkAttributes(DOMElement $embed) { $settingName = strtolower($this->settingName); $useDefault = true; foreach ($embed->attributes as $attribute) { $attrName = strtolower($attribute->name); if ($attrName === $settingName) { $this->checkSetting($attribute, $attribute->value); $useDefault = false; } } if ($useDefault) { $this->checkSetting($embed, $this->defaultSetting); } } /** * Test whether given element has dynamic attributes that match the setting's name * * @param DOMElement $embed Context element * @return void */ protected function checkDynamicAttributes(DOMElement $embed) { $settingName = strtolower($this->settingName); foreach ($embed->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute) { $attrName = strtolower($attribute->getAttribute('name')); if ($attrName === $settingName) { throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute); } } } /** * Test the presence of dynamic params in given object * * @param DOMElement $object Context element * @return void */ protected function checkDynamicParams(DOMElement $object) { foreach ($this->getObjectParams($object) as $param) { foreach ($param->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute) { // Test for a dynamic "value" attribute if (strtolower($attribute->getAttribute('name')) === 'value') { throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute); } } } } /** * Check embed elements in given template * * @return void */ protected function checkEmbeds() { foreach ($this->getElements('embed') as $embed) { // Test descendants $this->checkDynamicAttributes($embed); // Test the element's attributes $this->checkAttributes($embed); } } /** * Check object elements in given template * * @return void */ protected function checkObjects() { foreach ($this->getElements('object') as $object) { // Make sure this object doesn't have dynamic params $this->checkDynamicParams($object); // Test the element's descendants $params = $this->getObjectParams($object); foreach ($params as $param) { $this->checkSetting($param, $param->getAttribute('value')); } if (empty($params)) { $this->checkSetting($object, $this->defaultSetting); } } } /** * Test whether given setting is allowed * * @param DOMNode $node Target node * @param string $setting Setting * @return void */ protected function checkSetting(DOMNode $node, $setting) { if (!isset($this->settings[strtolower($setting)])) { // Test whether the value contains an odd number of { if (preg_match('/(?settingName . " setting '" . $setting . "'", $node); } throw new UnsafeTemplateException('Unknown ' . $this->settingName . " value '" . $setting . "'", $node); } $value = $this->settings[strtolower($setting)]; $maxValue = $this->settings[strtolower($this->maxSetting)]; if ($value > $maxValue) { throw new UnsafeTemplateException($this->settingName . " setting '" . $setting . "' exceeds restricted value '" . $this->maxSetting . "'", $node); } } /** * Test whether given node contains dynamic content (XSL elements or attribute value template) * * @param DOMElement $node Node * @return bool */ protected function isDynamic(DOMElement $node) { if ($node->getElementsByTagNameNS(self::XMLNS_XSL, '*')->length) { return true; } // Look for any attributes containing "{" in this element or its descendants $xpath = new DOMXPath($node->ownerDocument); $query = './/@*[contains(., "{")]'; foreach ($xpath->query($query, $node) as $attribute) { // Test whether the value contains an odd number of { if (preg_match('/(?value)) { return true; } } return false; } /** * Get all elements the restriction applies to * * @param string $tagName Element's name * @return DOMElement[] */ protected function getElements($tagName) { $nodes = []; foreach ($this->template->ownerDocument->getElementsByTagName($tagName) as $node) { if (!$this->onlyIfDynamic || $this->isDynamic($node)) { $nodes[] = $node; } } return $nodes; } /** * Get all param elements attached to given object * * @param DOMElement $object Context element * @return DOMElement[] */ protected function getObjectParams(DOMElement $object) { $params = []; $settingName = strtolower($this->settingName); foreach ($object->getElementsByTagName('param') as $param) { $paramName = strtolower($param->getAttribute('name')); if ($paramName === $settingName && $param->parentNode->isSameNode($object)) { $params[] = $param; } } return $params; } }