1<?php
2
3class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
4{
5    /**
6     * @type string
7     */
8    public $name = 'Munge';
9
10    /**
11     * @type bool
12     */
13    public $post = true;
14
15    /**
16     * @type string
17     */
18    private $target;
19
20    /**
21     * @type HTMLPurifier_URIParser
22     */
23    private $parser;
24
25    /**
26     * @type bool
27     */
28    private $doEmbed;
29
30    /**
31     * @type string
32     */
33    private $secretKey;
34
35    /**
36     * @type array
37     */
38    protected $replace = array();
39
40    /**
41     * @param HTMLPurifier_Config $config
42     * @return bool
43     */
44    public function prepare($config)
45    {
46        $this->target = $config->get('URI.' . $this->name);
47        $this->parser = new HTMLPurifier_URIParser();
48        $this->doEmbed = $config->get('URI.MungeResources');
49        $this->secretKey = $config->get('URI.MungeSecretKey');
50        if ($this->secretKey && !function_exists('hash_hmac')) {
51            throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
52        }
53        return true;
54    }
55
56    /**
57     * @param HTMLPurifier_URI $uri
58     * @param HTMLPurifier_Config $config
59     * @param HTMLPurifier_Context $context
60     * @return bool
61     */
62    public function filter(&$uri, $config, $context)
63    {
64        if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
65            return true;
66        }
67
68        $scheme_obj = $uri->getSchemeObj($config, $context);
69        if (!$scheme_obj) {
70            return true;
71        } // ignore unknown schemes, maybe another postfilter did it
72        if (!$scheme_obj->browsable) {
73            return true;
74        } // ignore non-browseable schemes, since we can't munge those in a reasonable way
75        if ($uri->isBenign($config, $context)) {
76            return true;
77        } // don't redirect if a benign URL
78
79        $this->makeReplace($uri, $config, $context);
80        $this->replace = array_map('rawurlencode', $this->replace);
81
82        $new_uri = strtr($this->target, $this->replace);
83        $new_uri = $this->parser->parse($new_uri);
84        // don't redirect if the target host is the same as the
85        // starting host
86        if ($uri->host === $new_uri->host) {
87            return true;
88        }
89        $uri = $new_uri; // overwrite
90        return true;
91    }
92
93    /**
94     * @param HTMLPurifier_URI $uri
95     * @param HTMLPurifier_Config $config
96     * @param HTMLPurifier_Context $context
97     */
98    protected function makeReplace($uri, $config, $context)
99    {
100        $string = $uri->toString();
101        // always available
102        $this->replace['%s'] = $string;
103        $this->replace['%r'] = $context->get('EmbeddedURI', true);
104        $token = $context->get('CurrentToken', true);
105        $this->replace['%n'] = $token ? $token->name : null;
106        $this->replace['%m'] = $context->get('CurrentAttr', true);
107        $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
108        // not always available
109        if ($this->secretKey) {
110            $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
111        }
112    }
113}
114
115// vim: et sw=4 sts=4
116