]> git.mxchange.org Git - friendica.git/blob - library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
Update HTMLPurifier to v4.7.0
[friendica.git] / library / 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                 // something funky happened to the default scheme object
89                 trigger_error(
90                     'Default scheme object "' . $def->defaultScheme . '" was not readable',
91                     E_USER_WARNING
92                 );
93                 return false;
94             }
95         }
96         return $scheme_obj;
97     }
98
99     /**
100      * Generic validation method applicable for all schemes. May modify
101      * this URI in order to get it into a compliant form.
102      * @param HTMLPurifier_Config $config
103      * @param HTMLPurifier_Context $context
104      * @return bool True if validation/filtering succeeds, false if failure
105      */
106     public function validate($config, $context)
107     {
108         // ABNF definitions from RFC 3986
109         $chars_sub_delims = '!$&\'()*+,;=';
110         $chars_gen_delims = ':/?#[]@';
111         $chars_pchar = $chars_sub_delims . ':@';
112
113         // validate host
114         if (!is_null($this->host)) {
115             $host_def = new HTMLPurifier_AttrDef_URI_Host();
116             $this->host = $host_def->validate($this->host, $config, $context);
117             if ($this->host === false) {
118                 $this->host = null;
119             }
120         }
121
122         // validate scheme
123         // NOTE: It's not appropriate to check whether or not this
124         // scheme is in our registry, since a URIFilter may convert a
125         // URI that we don't allow into one we do.  So instead, we just
126         // check if the scheme can be dropped because there is no host
127         // and it is our default scheme.
128         if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
129             // support for relative paths is pretty abysmal when the
130             // scheme is present, so axe it when possible
131             $def = $config->getDefinition('URI');
132             if ($def->defaultScheme === $this->scheme) {
133                 $this->scheme = null;
134             }
135         }
136
137         // validate username
138         if (!is_null($this->userinfo)) {
139             $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
140             $this->userinfo = $encoder->encode($this->userinfo);
141         }
142
143         // validate port
144         if (!is_null($this->port)) {
145             if ($this->port < 1 || $this->port > 65535) {
146                 $this->port = null;
147             }
148         }
149
150         // validate path
151         $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
152         if (!is_null($this->host)) { // this catches $this->host === ''
153             // path-abempty (hier and relative)
154             // http://www.example.com/my/path
155             // //www.example.com/my/path (looks odd, but works, and
156             //                            recognized by most browsers)
157             // (this set is valid or invalid on a scheme by scheme
158             // basis, so we'll deal with it later)
159             // file:///my/path
160             // ///my/path
161             $this->path = $segments_encoder->encode($this->path);
162         } elseif ($this->path !== '') {
163             if ($this->path[0] === '/') {
164                 // path-absolute (hier and relative)
165                 // http:/my/path
166                 // /my/path
167                 if (strlen($this->path) >= 2 && $this->path[1] === '/') {
168                     // This could happen if both the host gets stripped
169                     // out
170                     // http://my/path
171                     // //my/path
172                     $this->path = '';
173                 } else {
174                     $this->path = $segments_encoder->encode($this->path);
175                 }
176             } elseif (!is_null($this->scheme)) {
177                 // path-rootless (hier)
178                 // http:my/path
179                 // Short circuit evaluation means we don't need to check nz
180                 $this->path = $segments_encoder->encode($this->path);
181             } else {
182                 // path-noscheme (relative)
183                 // my/path
184                 // (once again, not checking nz)
185                 $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
186                 $c = strpos($this->path, '/');
187                 if ($c !== false) {
188                     $this->path =
189                         $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
190                         $segments_encoder->encode(substr($this->path, $c));
191                 } else {
192                     $this->path = $segment_nc_encoder->encode($this->path);
193                 }
194             }
195         } else {
196             // path-empty (hier and relative)
197             $this->path = ''; // just to be safe
198         }
199
200         // qf = query and fragment
201         $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
202
203         if (!is_null($this->query)) {
204             $this->query = $qf_encoder->encode($this->query);
205         }
206
207         if (!is_null($this->fragment)) {
208             $this->fragment = $qf_encoder->encode($this->fragment);
209         }
210         return true;
211     }
212
213     /**
214      * Convert URI back to string
215      * @return string URI appropriate for output
216      */
217     public function toString()
218     {
219         // reconstruct authority
220         $authority = null;
221         // there is a rendering difference between a null authority
222         // (http:foo-bar) and an empty string authority
223         // (http:///foo-bar).
224         if (!is_null($this->host)) {
225             $authority = '';
226             if (!is_null($this->userinfo)) {
227                 $authority .= $this->userinfo . '@';
228             }
229             $authority .= $this->host;
230             if (!is_null($this->port)) {
231                 $authority .= ':' . $this->port;
232             }
233         }
234
235         // Reconstruct the result
236         // One might wonder about parsing quirks from browsers after
237         // this reconstruction.  Unfortunately, parsing behavior depends
238         // on what *scheme* was employed (file:///foo is handled *very*
239         // differently than http:///foo), so unfortunately we have to
240         // defer to the schemes to do the right thing.
241         $result = '';
242         if (!is_null($this->scheme)) {
243             $result .= $this->scheme . ':';
244         }
245         if (!is_null($authority)) {
246             $result .= '//' . $authority;
247         }
248         $result .= $this->path;
249         if (!is_null($this->query)) {
250             $result .= '?' . $this->query;
251         }
252         if (!is_null($this->fragment)) {
253             $result .= '#' . $this->fragment;
254         }
255
256         return $result;
257     }
258
259     /**
260      * Returns true if this URL might be considered a 'local' URL given
261      * the current context.  This is true when the host is null, or
262      * when it matches the host supplied to the configuration.
263      *
264      * Note that this does not do any scheme checking, so it is mostly
265      * only appropriate for metadata that doesn't care about protocol
266      * security.  isBenign is probably what you actually want.
267      * @param HTMLPurifier_Config $config
268      * @param HTMLPurifier_Context $context
269      * @return bool
270      */
271     public function isLocal($config, $context)
272     {
273         if ($this->host === null) {
274             return true;
275         }
276         $uri_def = $config->getDefinition('URI');
277         if ($uri_def->host === $this->host) {
278             return true;
279         }
280         return false;
281     }
282
283     /**
284      * Returns true if this URL should be considered a 'benign' URL,
285      * that is:
286      *
287      *      - It is a local URL (isLocal), and
288      *      - It has a equal or better level of security
289      * @param HTMLPurifier_Config $config
290      * @param HTMLPurifier_Context $context
291      * @return bool
292      */
293     public function isBenign($config, $context)
294     {
295         if (!$this->isLocal($config, $context)) {
296             return false;
297         }
298
299         $scheme_obj = $this->getSchemeObj($config, $context);
300         if (!$scheme_obj) {
301             return false;
302         } // conservative approach
303
304         $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
305         if ($current_scheme_obj->secure) {
306             if (!$scheme_obj->secure) {
307                 return false;
308             }
309         }
310         return true;
311     }
312 }
313
314 // vim: et sw=4 sts=4