diff --git a/NEWS b/NEWS
index 992c2304..0088fb69 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,9 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier
a standards compliant way, by moving them into the preceding
+ Whether or not to permit iframe tags in untrusted documents. This
+ directive must be accompanied by a whitelist of permitted iframes,
+ such as %URI.SafeIframeRegexp, otherwise it will fatally error.
+ This directive has no effect on strict doctypes, as iframes are not
+ valid.
+
+ A PCRE regular expression that will be matched against an iframe URI. This is
+ a relatively inflexible scheme, but works well enough for the most common
+ use-case of iframes: embedded video. This directive only has an effect if
+ %HTML.SafeIframe is enabled. Here are some example values:
+
%^http://(www.youtube.com/embed/|player.vimeo.com/video/)% - Allow both
+
+
+ Note that this directive does not give you enough granularity to, say, disable
+ all autoplay videos. Pipe up on the HTML Purifier forums if this
+ is a capability you want.
+
+--# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/Forms.php b/library/HTMLPurifier/HTMLModule/Forms.php
index 44c22f6f..139df2d7 100644
--- a/library/HTMLPurifier/HTMLModule/Forms.php
+++ b/library/HTMLPurifier/HTMLModule/Forms.php
@@ -35,7 +35,7 @@ class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
'name' => 'CDATA',
'readonly' => 'Bool#readonly',
'size' => 'Number',
- 'src' => 'URI#embeds',
+ 'src' => 'URI#embedded',
'tabindex' => 'Number',
'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
'value' => 'CDATA',
diff --git a/library/HTMLPurifier/HTMLModule/Iframe.php b/library/HTMLPurifier/HTMLModule/Iframe.php
new file mode 100644
index 00000000..287071ed
--- /dev/null
+++ b/library/HTMLPurifier/HTMLModule/Iframe.php
@@ -0,0 +1,38 @@
+get('HTML.SafeIframe')) {
+ $this->safe = true;
+ }
+ $this->addElement(
+ 'iframe', 'Inline', 'Flow', 'Common',
+ array(
+ 'src' => 'URI#embedded',
+ 'width' => 'Length',
+ 'height' => 'Length',
+ 'name' => 'ID',
+ 'scrolling' => 'Enum#yes,no,auto',
+ 'frameborder' => 'Enum#0,1',
+ 'longdesc' => 'URI',
+ 'marginheight' => 'Pixels',
+ 'marginwidth' => 'Pixels',
+ )
+ );
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php
index 92a05705..468cf46f 100644
--- a/library/HTMLPurifier/HTMLModuleManager.php
+++ b/library/HTMLPurifier/HTMLModuleManager.php
@@ -65,11 +65,11 @@ class HTMLPurifier_HTMLModuleManager
'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
'StyleAttribute',
// Unsafe:
- 'Scripting', 'Object', 'Forms',
+ 'Scripting', 'Object', 'Forms',
// Sorta legacy, but present in strict:
'Name',
);
- $transitional = array('Legacy', 'Target');
+ $transitional = array('Legacy', 'Target', 'Iframe');
$xml = array('XMLCommonAttributes');
$non_xml = array('NonXMLCommonAttributes');
@@ -112,7 +112,9 @@ class HTMLPurifier_HTMLModuleManager
$this->doctypes->register(
'XHTML 1.1', true,
- array_merge($common, $xml, array('Ruby')),
+ // Iframe is a real XHTML 1.1 module, despite being
+ // "transitional"!
+ array_merge($common, $xml, array('Ruby', 'Iframe')),
array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
array(),
'-//W3C//DTD XHTML 1.1//EN',
diff --git a/library/HTMLPurifier/URIDefinition.php b/library/HTMLPurifier/URIDefinition.php
index ded5b9b6..40e57bb7 100644
--- a/library/HTMLPurifier/URIDefinition.php
+++ b/library/HTMLPurifier/URIDefinition.php
@@ -27,6 +27,7 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
$this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
+ $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
$this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
$this->registerFilter(new HTMLPurifier_URIFilter_Munge());
}
@@ -52,9 +53,13 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
protected function setupFilters($config) {
foreach ($this->registeredFilters as $name => $filter) {
- $conf = $config->get('URI.' . $name);
- if ($conf !== false && $conf !== null) {
+ if ($filter->always_load) {
$this->addFilter($filter, $config);
+ } else {
+ $conf = $config->get('URI.' . $name);
+ if ($conf !== false && $conf !== null) {
+ $this->addFilter($filter, $config);
+ }
}
}
unset($this->registeredFilters);
diff --git a/library/HTMLPurifier/URIFilter.php b/library/HTMLPurifier/URIFilter.php
index 5d54c158..6a1b0b08 100644
--- a/library/HTMLPurifier/URIFilter.php
+++ b/library/HTMLPurifier/URIFilter.php
@@ -4,7 +4,9 @@
* Chainable filters for custom URI processing.
*
* These filters can perform custom actions on a URI filter object,
- * including transformation or blacklisting.
+ * including transformation or blacklisting. A filter named Foo
+ * must have a corresponding configuration directive %URI.Foo,
+ * unless always_load is specified to be true.
*
* The following contexts may be available while URIFilters are being
* processed:
@@ -37,7 +39,15 @@ abstract class HTMLPurifier_URIFilter
public $post = false;
/**
- * Performs initialization for the filter
+ * True if this filter should always be loaded (this permits
+ * a filter to be named Foo without the corresponding %URI.Foo
+ * directive existing.)
+ */
+ public $always_load = false;
+
+ /**
+ * Performs initialization for the filter. If the filter returns
+ * false, this means that it shouldn't be considered active.
*/
public function prepare($config) {return true;}
diff --git a/library/HTMLPurifier/URIFilter/SafeIframe.php b/library/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644
index 00000000..284bb13d
--- /dev/null
+++ b/library/HTMLPurifier/URIFilter/SafeIframe.php
@@ -0,0 +1,35 @@
+regexp = $config->get('URI.SafeIframeRegexp');
+ return true;
+ }
+ public function filter(&$uri, $config, $context) {
+ // check if filter not applicable
+ if (!$config->get('HTML.SafeIframe')) return true;
+ // check if the filter should actually trigger
+ if (!$context->get('EmbeddedURI', true)) return true;
+ $token = $context->get('CurrentToken', true);
+ if (!($token && $token->name == 'iframe')) return true;
+ // check if we actually have some whitelists enabled
+ if ($this->regexp === null) return false;
+ // actually check the whitelists
+ return preg_match($this->regexp, $uri->toString());
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt b/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt
new file mode 100644
index 00000000..40fac62d
--- /dev/null
+++ b/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%^http://maps.google.com/%"
+--HTML--
+
+--EXPECT--
+
+--# vim: et sw=4 sts=4
diff --git a/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt b/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt
new file mode 100644
index 00000000..1abc2c82
--- /dev/null
+++ b/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt
@@ -0,0 +1,8 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%^http://www.youtube.com/embed/%"
+--HTML--
+
+--EXPECT--
+
+--# vim: et sw=4 sts=4
diff --git a/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt b/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt
new file mode 100644
index 00000000..7c0b60d2
--- /dev/null
+++ b/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt
@@ -0,0 +1,14 @@
+--INI--
+HTML.SafeIframe = true
+URI.SafeIframeRegexp = "%(^http://www.example.com/|^https?://dev.example.com/)%"
+--HTML--
+
+
+
+
+--EXPECT--
+
+
+
+
+--# vim: et sw=4 sts=4