1<?php
2
3/**
4 * Implements data: URI for base64 encoded images supported by GD.
5 */
6class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
7{
8    /**
9     * @type bool
10     */
11    public $browsable = true;
12
13    /**
14     * @type array
15     */
16    public $allowed_types = array(
17        // you better write validation code for other types if you
18        // decide to allow them
19        'image/jpeg' => true,
20        'image/gif' => true,
21        'image/png' => true,
22    );
23    // this is actually irrelevant since we only write out the path
24    // component
25    /**
26     * @type bool
27     */
28    public $may_omit_host = true;
29
30    /**
31     * @param HTMLPurifier_URI $uri
32     * @param HTMLPurifier_Config $config
33     * @param HTMLPurifier_Context $context
34     * @return bool
35     */
36    public function doValidate(&$uri, $config, $context)
37    {
38        $result = explode(',', $uri->path, 2);
39        $is_base64 = false;
40        $charset = null;
41        $content_type = null;
42        if (count($result) == 2) {
43            list($metadata, $data) = $result;
44            // do some legwork on the metadata
45            $metas = explode(';', $metadata);
46            while (!empty($metas)) {
47                $cur = array_shift($metas);
48                if ($cur == 'base64') {
49                    $is_base64 = true;
50                    break;
51                }
52                if (substr($cur, 0, 8) == 'charset=') {
53                    // doesn't match if there are arbitrary spaces, but
54                    // whatever dude
55                    if ($charset !== null) {
56                        continue;
57                    } // garbage
58                    $charset = substr($cur, 8); // not used
59                } else {
60                    if ($content_type !== null) {
61                        continue;
62                    } // garbage
63                    $content_type = $cur;
64                }
65            }
66        } else {
67            $data = $result[0];
68        }
69        if ($content_type !== null && empty($this->allowed_types[$content_type])) {
70            return false;
71        }
72        if ($charset !== null) {
73            // error; we don't allow plaintext stuff
74            $charset = null;
75        }
76        $data = rawurldecode($data);
77        if ($is_base64) {
78            $raw_data = base64_decode($data);
79        } else {
80            $raw_data = $data;
81        }
82        if ( strlen($raw_data) < 12 ) {
83            // error; exif_imagetype throws exception with small files,
84            // and this likely indicates a corrupt URI/failed parse anyway
85            return false;
86        }
87        // XXX probably want to refactor this into a general mechanism
88        // for filtering arbitrary content types
89        if (function_exists('sys_get_temp_dir')) {
90            $file = tempnam(sys_get_temp_dir(), "");
91        } else {
92            $file = tempnam("/tmp", "");
93        }
94        file_put_contents($file, $raw_data);
95        if (function_exists('exif_imagetype')) {
96            $image_code = exif_imagetype($file);
97            unlink($file);
98        } elseif (function_exists('getimagesize')) {
99            set_error_handler(array($this, 'muteErrorHandler'));
100            $info = getimagesize($file);
101            restore_error_handler();
102            unlink($file);
103            if ($info == false) {
104                return false;
105            }
106            $image_code = $info[2];
107        } else {
108            trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
109        }
110        $real_content_type = image_type_to_mime_type($image_code);
111        if ($real_content_type != $content_type) {
112            // we're nice guys; if the content type is something else we
113            // support, change it over
114            if (empty($this->allowed_types[$real_content_type])) {
115                return false;
116            }
117            $content_type = $real_content_type;
118        }
119        // ok, it's kosher, rewrite what we need
120        $uri->userinfo = null;
121        $uri->host = null;
122        $uri->port = null;
123        $uri->fragment = null;
124        $uri->query = null;
125        $uri->path = "$content_type;base64," . base64_encode($raw_data);
126        return true;
127    }
128
129    /**
130     * @param int $errno
131     * @param string $errstr
132     */
133    public function muteErrorHandler($errno, $errstr)
134    {
135    }
136}
137