Skip to content

Commit

Permalink
Merge d12ce66 into cbe9827
Browse files Browse the repository at this point in the history
  • Loading branch information
xemlock committed Aug 6, 2022
2 parents cbe9827 + d12ce66 commit 257f5ec
Show file tree
Hide file tree
Showing 22 changed files with 674 additions and 1 deletion.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -56,6 +56,14 @@ $config->set('URI.SafeIframeRegexp', '%^//www\.youtube\.com/embed/%');

Apart from HTML Purifier's built-in [configuration directives](http://htmlpurifier.org/live/configdoc/plain.html), the following new directives are also supported:

* __Attr.AllowedInputTypes__

Version added: 0.1.12\
Type: [Lookup](http://htmlpurifier.org/live/configdoc/plain.html#type-lookup)\
Default: `null`

List of allowed input types, chosen from the types defined in the spec. By default, the setting is `null`, meaning there is no restriction on allowed types. Empty array means that no input types are allowed, effectively removing `input` elements from the purified output.

* __HTML.Forms__

Version added: 0.1.12\
Expand Down
47 changes: 47 additions & 0 deletions library/HTMLPurifier/AttrDef/HTML5/InputType.php
@@ -0,0 +1,47 @@
<?php

class HTMLPurifier_AttrDef_HTML5_InputType extends HTMLPurifier_AttrDef
{
/**
* Lookup table for valid values
* @var array
* @see https://www.w3.org/TR/xhtml-modularization/abstract_modules.html#s_extformsmodule
*/
protected static $values = array(
'button' => true,
'checkbox' => true,
'file' => true,
'hidden' => true,
'image' => true,
'password' => true,
'radio' => true,
'reset' => true,
'submit' => true,
'text' => true,
);

/**
* @return array
*/
public static function values()
{
return self::$values;
}

/**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$value = strtolower($this->parseCDATA($string));

if (!isset(self::$values[$value])) {
return false;
}

return $value;
}
}
145 changes: 145 additions & 0 deletions library/HTMLPurifier/AttrTransform/HTML5/Input.php
@@ -0,0 +1,145 @@
<?php

/**
* Performs miscellaneous cross attribute validation and filtering for
* HTML5 input elements. This is meant to be a post-transform.
*/
class HTMLPurifier_AttrTransform_HTML5_Input extends HTMLPurifier_AttrTransform
{
/**
* Allowed attributes vs input type lookup
* @var array
*/
protected static $attributes = array(
'accept' => array(
'file' => true,
),
'alt' => array(
'image' => true,
),
'checked' => array(
'checkbox' => true,
'radio' => true,
),
'maxlength' => array(
'password' => true,
'text' => true,
),
'readonly' => array(
'password' => true,
'text' => true,
),
'required' => array(
'checkbox' => true,
'file' => true,
'password' => true,
'radio' => true,
'text' => true,
),
'size' => array(
'password' => true,
'text' => true,
),
'src' => array(
'image' => true,
),
'value' => array(
'button' => true,
'checkbox' => true,
'hidden' => true,
'password' => true,
'radio' => true,
'reset' => true,
'submit' => true,
'text' => true,
),
);

/**
* Lookup for input types allowed in current configuration
* @var array
*/
protected $allowedInputTypes;

protected $allowedInputTypesFromConfig;

protected function setupAllowedInputTypes(HTMLPurifier_Config $config)
{
$allowedInputTypesFromConfig = isset($config->def->info['Attr.AllowedInputTypes'])
? $config->get('Attr.AllowedInputTypes')
: null;

// Check if current allowedInputTypes value is based on the latest value from config.
if ($this->allowedInputTypes !== null && $this->allowedInputTypesFromConfig === $allowedInputTypesFromConfig) {
return;
}

if (is_array($allowedInputTypesFromConfig)) {
$allowedInputTypes = array_intersect_key(
$allowedInputTypesFromConfig,
HTMLPurifier_AttrDef_HTML5_InputType::values()
);
} else {
$allowedInputTypes = HTMLPurifier_AttrDef_HTML5_InputType::values();
}

$this->allowedInputTypes = $allowedInputTypes;
$this->allowedInputTypesFromConfig = $allowedInputTypesFromConfig;
}

/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array|bool
*/
public function transform($attr, $config, $context)
{
$t = isset($attr['type']) ? $attr['type'] : 'text';

// If an unknown value is specified, the input type 'text' is used for validations,
// and the 'type' attribute is removed
if ($t === false) {
unset($attr['type']);
$t = 'text';
}

$t = strtolower($t);

// If type doesn't pass Attr.AllowedInputTypes validation, remove the element
// from the output
$this->setupAllowedInputTypes($config);
if (!isset($this->allowedInputTypes[$t])) {
return false;
}

// Remove attributes not allowed for detected input type
foreach (self::$attributes as $a => $types) {
if (array_key_exists($a, $attr) && !isset($types[$t])) {
unset($attr[$a]);
}
}

// Non-empty 'alt' attribute is required for 'image' input
if ($t === 'image' && !isset($attr['alt'])) {
$alt = trim($config->get('Attr.DefaultImageAlt'));
if ($alt === '') {
$name = isset($attr['name']) ? trim($attr['name']) : '';
$alt = $name !== '' ? $name : 'image';
}
$attr['alt'] = $alt;
}

// The value attribute is always optional, though should be considered
// mandatory for checkbox, radio, and hidden.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-value
// Nu Validator diverges from the WHATWG spec, as it defines 'value'
// attribute as required, where in fact it is optional, and may be an empty string:
// https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button)
if (!isset($attr['value']) && ($t === 'checkbox' || $t === 'radio' || $t === 'hidden')) {
$attr['value'] = '';
}

return $attr;
}
}
6 changes: 5 additions & 1 deletion library/HTMLPurifier/HTML5Config.php
Expand Up @@ -2,7 +2,7 @@

class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
{
const REVISION = 2022080501;
const REVISION = 2022080601;

/**
* @param string|array|HTMLPurifier_Config $config
Expand Down Expand Up @@ -92,6 +92,10 @@ public function __construct(HTMLPurifier_ConfigSchema $schema, HTMLPurifier_Prop
$schema->add('URI.SafeLinkRegexp', null, 'string', true);
}

if (empty($schema->info['Attr.AllowedInputTypes'])) {
$schema->add('Attr.AllowedInputTypes', null, 'lookup', true);
}

// HTMLPurifier doesn't define %CSS.DefinitionID, but it's required for
// customizing CSS definition object (in the future)
if (empty($schema->info['CSS.DefinitionID'])) {
Expand Down
22 changes: 22 additions & 0 deletions library/HTMLPurifier/HTMLModule/HTML5/Forms.php
Expand Up @@ -42,5 +42,27 @@ public function setup($config)
)
);
$form->excludes = array('form' => true);

$input = $this->addElement(
'input',
'Formctrl',
'Empty',
'Common',
array(
'accept' => 'ContentTypes',
'accesskey' => 'Character',
'alt' => 'Text',
'checked' => 'Bool#checked',
'disabled' => 'Bool#disabled',
'maxlength' => 'Pixels',
'name' => 'Text',
'readonly' => 'Bool#readonly',
'size' => 'Pixels',
'src' => 'URI#embedded',
'type*' => new HTMLPurifier_AttrDef_HTML5_InputType(),
'value' => 'Text',
)
);
$input->attr_transform_post[] = new HTMLPurifier_AttrTransform_HTML5_Input();
}
}
17 changes: 17 additions & 0 deletions tests/AttrTransformTestCase.php
@@ -0,0 +1,17 @@
<?php

abstract class AttrTransformTestCase extends BaseTestCase
{
/**
* @var HTMLPurifier_AttrTransform
*/
protected $transform;

public function assertResult($input, $expected = null)
{
if (func_num_args() < 2) {
$expected = $input;
}
$this->assertEquals($expected, $this->transform->transform($input, $this->config, $this->context));
}
}
29 changes: 29 additions & 0 deletions tests/HTMLPurifier/AttrDef/HTML5/InputTypeTest.php
@@ -0,0 +1,29 @@
<?php

/**
* @property HTMLPurifier_AttrDef_HTML5_InputType $attr
*/
class HTMLPurifier_AttrDef_HTML5_InputTypeTest extends AttrDefTestCase
{
protected function setUp()
{
parent::setUp();

$this->attr = new HTMLPurifier_AttrDef_HTML5_InputType();
}

public function testValidInputType()
{
$this->assertValidate('text');
}

public function testInvalidInputType()
{
$this->assertValidate('foo', false);
}

public function testUppercaseInputType()
{
$this->assertValidate('TEXT', 'text');
}
}

0 comments on commit 257f5ec

Please sign in to comment.