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