1<?php
2
3/**
4 * Validates the attributes of a token. Doesn't manage required attributes
5 * very well. The only reason we factored this out was because RemoveForeignElements
6 * also needed it besides ValidateAttributes.
7 */
8class HTMLPurifier_AttrValidator
9{
10
11    /**
12     * Validates the attributes of a token, mutating it as necessary.
13     * that has valid tokens
14     * @param HTMLPurifier_Token $token Token to validate.
15     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
16     * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
17     */
18    public function validateToken($token, $config, $context)
19    {
20        $definition = $config->getHTMLDefinition();
21        $e =& $context->get('ErrorCollector', true);
22
23        // initialize IDAccumulator if necessary
24        $ok =& $context->get('IDAccumulator', true);
25        if (!$ok) {
26            $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
27            $context->register('IDAccumulator', $id_accumulator);
28        }
29
30        // initialize CurrentToken if necessary
31        $current_token =& $context->get('CurrentToken', true);
32        if (!$current_token) {
33            $context->register('CurrentToken', $token);
34        }
35
36        if (!$token instanceof HTMLPurifier_Token_Start &&
37            !$token instanceof HTMLPurifier_Token_Empty
38        ) {
39            return;
40        }
41
42        // create alias to global definition array, see also $defs
43        // DEFINITION CALL
44        $d_defs = $definition->info_global_attr;
45
46        // don't update token until the very end, to ensure an atomic update
47        $attr = $token->attr;
48
49        // do global transformations (pre)
50        // nothing currently utilizes this
51        foreach ($definition->info_attr_transform_pre as $transform) {
52            $attr = $transform->transform($o = $attr, $config, $context);
53            if ($e) {
54                if ($attr != $o) {
55                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
56                }
57            }
58        }
59
60        // do local transformations only applicable to this element (pre)
61        // ex. <p align="right"> to <p style="text-align:right;">
62        foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
63            $attr = $transform->transform($o = $attr, $config, $context);
64            if ($e) {
65                if ($attr != $o) {
66                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
67                }
68            }
69        }
70
71        // create alias to this element's attribute definition array, see
72        // also $d_defs (global attribute definition array)
73        // DEFINITION CALL
74        $defs = $definition->info[$token->name]->attr;
75
76        $attr_key = false;
77        $context->register('CurrentAttr', $attr_key);
78
79        // iterate through all the attribute keypairs
80        // Watch out for name collisions: $key has previously been used
81        foreach ($attr as $attr_key => $value) {
82
83            // call the definition
84            if (isset($defs[$attr_key])) {
85                // there is a local definition defined
86                if ($defs[$attr_key] === false) {
87                    // We've explicitly been told not to allow this element.
88                    // This is usually when there's a global definition
89                    // that must be overridden.
90                    // Theoretically speaking, we could have a
91                    // AttrDef_DenyAll, but this is faster!
92                    $result = false;
93                } else {
94                    // validate according to the element's definition
95                    $result = $defs[$attr_key]->validate(
96                        $value,
97                        $config,
98                        $context
99                    );
100                }
101            } elseif (isset($d_defs[$attr_key])) {
102                // there is a global definition defined, validate according
103                // to the global definition
104                $result = $d_defs[$attr_key]->validate(
105                    $value,
106                    $config,
107                    $context
108                );
109            } else {
110                // system never heard of the attribute? DELETE!
111                $result = false;
112            }
113
114            // put the results into effect
115            if ($result === false || $result === null) {
116                // this is a generic error message that should replaced
117                // with more specific ones when possible
118                if ($e) {
119                    $e->send(E_ERROR, 'AttrValidator: Attribute removed');
120                }
121
122                // remove the attribute
123                unset($attr[$attr_key]);
124            } elseif (is_string($result)) {
125                // generally, if a substitution is happening, there
126                // was some sort of implicit correction going on. We'll
127                // delegate it to the attribute classes to say exactly what.
128
129                // simple substitution
130                $attr[$attr_key] = $result;
131            } else {
132                // nothing happens
133            }
134
135            // we'd also want slightly more complicated substitution
136            // involving an array as the return value,
137            // although we're not sure how colliding attributes would
138            // resolve (certain ones would be completely overriden,
139            // others would prepend themselves).
140        }
141
142        $context->destroy('CurrentAttr');
143
144        // post transforms
145
146        // global (error reporting untested)
147        foreach ($definition->info_attr_transform_post as $transform) {
148            $attr = $transform->transform($o = $attr, $config, $context);
149            if ($e) {
150                if ($attr != $o) {
151                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
152                }
153            }
154        }
155
156        // local (error reporting untested)
157        foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
158            $attr = $transform->transform($o = $attr, $config, $context);
159            if ($e) {
160                if ($attr != $o) {
161                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
162                }
163            }
164        }
165
166        $token->attr = $attr;
167
168        // destroy CurrentToken if we made it ourselves
169        if (!$current_token) {
170            $context->destroy('CurrentToken');
171        }
172
173    }
174
175
176}
177
178// vim: et sw=4 sts=4
179