Yaffs site version 1.1
[yaffs-website] / vendor / ezyang / htmlpurifier / library / HTMLPurifier / URI.php
1 <?php
2
3 /**
4  * HTML Purifier's internal representation of a URI.
5  * @note
6  *      Internal data-structures are completely escaped. If the data needs
7  *      to be used in a non-URI context (which is very unlikely), be sure
8  *      to decode it first. The URI may not necessarily be well-formed until
9  *      validate() is called.
10  */
11 class HTMLPurifier_URI
12 {
13     /**
14      * @type string
15      */
16     public $scheme;
17
18     /**
19      * @type string
20      */
21     public $userinfo;
22
23     /**
24      * @type string
25      */
26     public $host;
27
28     /**
29      * @type int
30      */
31     public $port;
32
33     /**
34      * @type string
35      */
36     public $path;
37
38     /**
39      * @type string
40      */
41     public $query;
42
43     /**
44      * @type string
45      */
46     public $fragment;
47
48     /**
49      * @param string $scheme
50      * @param string $userinfo
51      * @param string $host
52      * @param int $port
53      * @param string $path
54      * @param string $query
55      * @param string $fragment
56      * @note Automatically normalizes scheme and port
57      */
58     public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
59     {
60         $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
61         $this->userinfo = $userinfo;
62         $this->host = $host;
63         $this->port = is_null($port) ? $port : (int)$port;
64         $this->path = $path;
65         $this->query = $query;
66         $this->fragment = $fragment;
67     }
68
69     /**
70      * Retrieves a scheme object corresponding to the URI's scheme/default
71      * @param HTMLPurifier_Config $config
72      * @param HTMLPurifier_Context $context
73      * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
74      */
75     public function getSchemeObj($config, $context)
76     {
77         $registry = HTMLPurifier_URISchemeRegistry::instance();
78         if ($this->scheme !== null) {
79             $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
80             if (!$scheme_obj) {
81                 return false;
82             } // invalid scheme, clean it out
83         } else {
84             // no scheme: retrieve the default one
85             $def = $config->getDefinition('URI');
86             $scheme_obj = $def->getDefaultScheme($config, $context);
87             if (!$scheme_obj) {
88                 if ($def->defaultScheme !== null) {
89                     // something funky happened to the default scheme object
90                     trigger_error(
91                         'Default scheme object "' . $def->defaultScheme . '" was not readable',
92                         E_USER_WARNING
93                     );
94                 } // suppress error if it's null
95                 return false;
96             }
97         }
98         return $scheme_obj;
99     }
100
101     /**
102      * Generic validation method applicable for all schemes. May modify
103      * this URI in order to get it into a compliant form.
104      * @param HTMLPurifier_Config $config
105      * @param HTMLPurifier_Context $context
106      * @return bool True if validation/filtering succeeds, false if failure
107      */
108     public function validate($config, $context)
109     {
110         // ABNF definitions from RFC 3986
111         $chars_sub_delims = '!$&\'()*+,;=';
112         $chars_gen_delims = ':/?#[]@';
113         $chars_pchar = $chars_sub_delims . ':@';
114
115         // validate host
116         if (!is_null($this->host)) {
117             $host_def = new HTMLPurifier_AttrDef_URI_Host();
118             $this->host = $host_def->validate($this->host, $config, $context);
119             if ($this->host === false) {
120                 $this->host = null;
121             }
122         }
123
124         // validate scheme
125         // NOTE: It's not appropriate to check whether or not this
126         // scheme is in our registry, since a URIFilter may convert a
127         // URI that we don't allow into one we do.  So instead, we just
128         // check if the scheme can be dropped because there is no host
129         // and it is our default scheme.
130         if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
131             // support for relative paths is pretty abysmal when the
132             // scheme is present, so axe it when possible
133             $def = $config->getDefinition('URI');
134             if ($def->defaultScheme === $this->scheme) {
135                 $this->scheme = null;
136             }
137         }
138
139         // validate username
140         if (!is_null($this->userinfo)) {
141             $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
142             $this->userinfo = $encoder->encode($this->userinfo);
143         }
144
145         // validate port
146         if (!is_null($this->port)) {
147             if ($this->port < 1 || $this->port > 65535) {
148                 $this->port = null;
149             }
150         }
151
152         // validate path
153         $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
154         if (!is_null($this->host)) { // this catches $this->host === ''
155             // path-abempty (hier and relative)
156             // http://www.example.com/my/path
157             // //www.example.com/my/path (looks odd, but works, and
158             //                            recognized by most browsers)
159             // (this set is valid or invalid on a scheme by scheme
160             // basis, so we'll deal with it later)
161             // file:///my/path
162             // ///my/path
163             $this->path = $segments_encoder->encode($this->path);
164         } elseif ($this->path !== '') {
165             if ($this->path[0] === '/') {
166                 // path-absolute (hier and relative)
167                 // http:/my/path
168                 // /my/path
169                 if (strlen($this->path) >= 2 && $this->path[1] === '/') {
170                     // This could happen if both the host gets stripped
171                     // out
172                     // http://my/path
173                     // //my/path
174                     $this->path = '';
175                 } else {
176                     $this->path = $segments_encoder->encode($this->path);
177                 }
178             } elseif (!is_null($this->scheme)) {
179                 // path-rootless (hier)
180                 // http:my/path
181                 // Short circuit evaluation means we don't need to check nz
182                 $this->path = $segments_encoder->encode($this->path);
183             } else {
184                 // path-noscheme (relative)
185                 // my/path
186                 // (once again, not checking nz)
187                 $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
188                 $c = strpos($this->path, '/');
189                 if ($c !== false) {
190                     $this->path =
191                         $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
192                         $segments_encoder->encode(substr($this->path, $c));
193                 } else {
194                     $this->path = $segment_nc_encoder->encode($this->path);
195                 }
196             }
197         } else {
198             // path-empty (hier and relative)
199             $this->path = ''; // just to be safe
200         }
201
202         // qf = query and fragment
203         $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
204
205         if (!is_null($this->query)) {
206             $this->query = $qf_encoder->encode($this->query);
207         }
208
209         if (!is_null($this->fragment)) {
210             $this->fragment = $qf_encoder->encode($this->fragment);
211         }
212         return true;
213     }
214
215     /**
216      * Convert URI back to string
217      * @return string URI appropriate for output
218      */
219     public function toString()
220     {
221         // reconstruct authority
222         $authority = null;
223         // there is a rendering difference between a null authority
224         // (http:foo-bar) and an empty string authority
225         // (http:///foo-bar).
226         if (!is_null($this->host)) {
227             $authority = '';
228             if (!is_null($this->userinfo)) {
229                 $authority .= $this->userinfo . '@';
230             }
231             $authority .= $this->host;
232             if (!is_null($this->port)) {
233                 $authority .= ':' . $this->port;
234             }
235         }
236
237         // Reconstruct the result
238         // One might wonder about parsing quirks from browsers after
239         // this reconstruction.  Unfortunately, parsing behavior depends
240         // on what *scheme* was employed (file:///foo is handled *very*
241         // differently than http:///foo), so unfortunately we have to
242         // defer to the schemes to do the right thing.
243         $result = '';
244         if (!is_null($this->scheme)) {
245             $result .= $this->scheme . ':';
246         }
247         if (!is_null($authority)) {
248             $result .= '//' . $authority;
249         }
250         $result .= $this->path;
251         if (!is_null($this->query)) {
252             $result .= '?' . $this->query;
253         }
254         if (!is_null($this->fragment)) {
255             $result .= '#' . $this->fragment;
256         }
257
258         return $result;
259     }
260
261     /**
262      * Returns true if this URL might be considered a 'local' URL given
263      * the current context.  This is true when the host is null, or
264      * when it matches the host supplied to the configuration.
265      *
266      * Note that this does not do any scheme checking, so it is mostly
267      * only appropriate for metadata that doesn't care about protocol
268      * security.  isBenign is probably what you actually want.
269      * @param HTMLPurifier_Config $config
270      * @param HTMLPurifier_Context $context
271      * @return bool
272      */
273     public function isLocal($config, $context)
274     {
275         if ($this->host === null) {
276             return true;
277         }
278         $uri_def = $config->getDefinition('URI');
279         if ($uri_def->host === $this->host) {
280             return true;
281         }
282         return false;
283     }
284
285     /**
286      * Returns true if this URL should be considered a 'benign' URL,
287      * that is:
288      *
289      *      - It is a local URL (isLocal), and
290      *      - It has a equal or better level of security
291      * @param HTMLPurifier_Config $config
292      * @param HTMLPurifier_Context $context
293      * @return bool
294      */
295     public function isBenign($config, $context)
296     {
297         if (!$this->isLocal($config, $context)) {
298             return false;
299         }
300
301         $scheme_obj = $this->getSchemeObj($config, $context);
302         if (!$scheme_obj) {
303             return false;
304         } // conservative approach
305
306         $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
307         if ($current_scheme_obj->secure) {
308             if (!$scheme_obj->secure) {
309                 return false;
310             }
311         }
312         return true;
313     }
314 }
315
316 // vim: et sw=4 sts=4