1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Http;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
49: class Url extends Nette\Object
50: {
51:
52: public static $defaultPorts = array(
53: 'http' => 80,
54: 'https' => 443,
55: 'ftp' => 21,
56: 'news' => 119,
57: 'nntp' => 119,
58: );
59:
60:
61: private $scheme = '';
62:
63:
64: private $user = '';
65:
66:
67: private $pass = '';
68:
69:
70: private $host = '';
71:
72:
73: private $port = NULL;
74:
75:
76: private $path = '';
77:
78:
79: private $query = '';
80:
81:
82: private $fragment = '';
83:
84:
85: 86: 87: 88:
89: public function __construct($url = NULL)
90: {
91: if (is_string($url)) {
92: $parts = @parse_url($url);
93: if ($parts === FALSE) {
94: throw new Nette\InvalidArgumentException("Malformed or unsupported URI '$url'.");
95: }
96:
97: foreach ($parts as $key => $val) {
98: $this->$key = $val;
99: }
100:
101: if (!$this->port && isset(self::$defaultPorts[$this->scheme])) {
102: $this->port = self::$defaultPorts[$this->scheme];
103: }
104:
105: if ($this->path === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
106: $this->path = '/';
107: }
108:
109: } elseif ($url instanceof self) {
110: foreach ($this as $key => $val) {
111: $this->$key = $url->$key;
112: }
113: }
114: }
115:
116:
117: 118: 119: 120: 121:
122: public function setScheme($value)
123: {
124: $this->scheme = (string) $value;
125: return $this;
126: }
127:
128:
129: 130: 131: 132:
133: public function getScheme()
134: {
135: return $this->scheme;
136: }
137:
138:
139: 140: 141: 142: 143:
144: public function setUser($value)
145: {
146: $this->user = (string) $value;
147: return $this;
148: }
149:
150:
151: 152: 153: 154:
155: public function getUser()
156: {
157: return $this->user;
158: }
159:
160:
161: 162: 163: 164: 165:
166: public function setPassword($value)
167: {
168: $this->pass = (string) $value;
169: return $this;
170: }
171:
172:
173: 174: 175: 176:
177: public function getPassword()
178: {
179: return $this->pass;
180: }
181:
182:
183: 184: 185: 186: 187:
188: public function setHost($value)
189: {
190: $this->host = (string) $value;
191: return $this;
192: }
193:
194:
195: 196: 197: 198:
199: public function getHost()
200: {
201: return $this->host;
202: }
203:
204:
205: 206: 207: 208: 209:
210: public function setPort($value)
211: {
212: $this->port = (int) $value;
213: return $this;
214: }
215:
216:
217: 218: 219: 220:
221: public function getPort()
222: {
223: return $this->port;
224: }
225:
226:
227: 228: 229: 230: 231:
232: public function setPath($value)
233: {
234: $this->path = (string) $value;
235: return $this;
236: }
237:
238:
239: 240: 241: 242:
243: public function getPath()
244: {
245: return $this->path;
246: }
247:
248:
249: 250: 251: 252: 253:
254: public function setQuery($value)
255: {
256: $this->query = (string) (is_array($value) ? http_build_query($value, '', '&') : $value);
257: return $this;
258: }
259:
260:
261: 262: 263: 264: 265:
266: public function appendQuery($value)
267: {
268: $value = (string) (is_array($value) ? http_build_query($value, '', '&') : $value);
269: $this->query .= ($this->query === '' || $value === '') ? $value : '&' . $value;
270: return $this;
271: }
272:
273:
274: 275: 276: 277:
278: public function getQuery()
279: {
280: return $this->query;
281: }
282:
283:
284: 285: 286: 287: 288:
289: public function getQueryParameter($name, $default = NULL)
290: {
291: parse_str($this->query, $params);
292: return isset($params[$name]) ? $params[$name] : $default;
293: }
294:
295:
296: 297: 298: 299: 300:
301: public function setQueryParameter($name, $value)
302: {
303: parse_str($this->query, $params);
304: if ($value === NULL) {
305: unset($params[$name]);
306: } else {
307: $params[$name] = $value;
308: }
309: $this->setQuery($params);
310: return $this;
311: }
312:
313:
314: 315: 316: 317: 318:
319: public function setFragment($value)
320: {
321: $this->fragment = (string) $value;
322: return $this;
323: }
324:
325:
326: 327: 328: 329:
330: public function getFragment()
331: {
332: return $this->fragment;
333: }
334:
335:
336: 337: 338: 339:
340: public function getAbsoluteUrl()
341: {
342: return $this->getHostUrl() . $this->path
343: . ($this->query === '' ? '' : '?' . $this->query)
344: . ($this->fragment === '' ? '' : '#' . $this->fragment);
345: }
346:
347:
348: 349: 350: 351:
352: public function getAuthority()
353: {
354: $authority = $this->host;
355: if ($this->port && (!isset(self::$defaultPorts[$this->scheme]) || $this->port !== self::$defaultPorts[$this->scheme])) {
356: $authority .= ':' . $this->port;
357: }
358:
359: if ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https') {
360: $authority = $this->user . ($this->pass === '' ? '' : ':' . $this->pass) . '@' . $authority;
361: }
362:
363: return $authority;
364: }
365:
366:
367: 368: 369: 370:
371: public function getHostUrl()
372: {
373: return ($this->scheme ? $this->scheme . ':' : '') . '//' . $this->getAuthority();
374: }
375:
376:
377: 378: 379: 380:
381: public function getBasePath()
382: {
383: $pos = strrpos($this->path, '/');
384: return $pos === FALSE ? '' : substr($this->path, 0, $pos + 1);
385: }
386:
387:
388: 389: 390: 391:
392: public function getBaseUrl()
393: {
394: return $this->getHostUrl() . $this->getBasePath();
395: }
396:
397:
398: 399: 400: 401:
402: public function getRelativeUrl()
403: {
404: return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
405: }
406:
407:
408: 409: 410: 411: 412:
413: public function isEqual($url)
414: {
415:
416: $part = self::unescape(strtok($url, '?#'), '%/');
417: if (strncmp($part, '//', 2) === 0) {
418: if ($part !== '//' . $this->getAuthority() . $this->path) {
419: return FALSE;
420: }
421:
422: } elseif (strncmp($part, '/', 1) === 0) {
423: if ($part !== $this->path) {
424: return FALSE;
425: }
426:
427: } else {
428: if ($part !== $this->getHostUrl() . $this->path) {
429: return FALSE;
430: }
431: }
432:
433:
434: $part = preg_split('#[&;]#', self::unescape(strtr((string) strtok('?#'), '+', ' '), '%&;=+'));
435: sort($part);
436: $query = preg_split('#[&;]#', $this->query);
437: sort($query);
438: return $part === $query;
439: }
440:
441:
442: 443: 444: 445:
446: public function canonicalize()
447: {
448: $this->path = $this->path === '' ? '/' : self::unescape($this->path, '%/');
449: $this->host = strtolower(rawurldecode($this->host));
450: $this->query = self::unescape(strtr($this->query, '+', ' '), '%&;=+');
451: return $this;
452: }
453:
454:
455: 456: 457:
458: public function __toString()
459: {
460: return $this->getAbsoluteUrl();
461: }
462:
463:
464: 465: 466: 467: 468: 469:
470: public static function unescape($s, $reserved = '%;/?:@&=+$,')
471: {
472:
473:
474:
475: preg_match_all('#(?<=%)[a-f0-9][a-f0-9]#i', $s, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
476: foreach (array_reverse($matches) as $match) {
477: $ch = chr(hexdec($match[0][0]));
478: if (strpos($reserved, $ch) === FALSE) {
479: $s = substr_replace($s, $ch, $match[0][1] - 1, 3);
480: }
481: }
482: return $s;
483: }
484:
485: }
486: