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