scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2  
3 namespace GuzzleHttp;
4  
5 /**
6 * Parses and generates URLs based on URL parts
7 */
8 class Url
9 {
10 private $scheme;
11 private $host;
12 private $port;
13 private $username;
14 private $password;
15 private $path = '';
16 private $fragment;
17 private static $defaultPorts = ['http' => 80, 'https' => 443, 'ftp' => 21];
18  
19 /** @var Query Query part of the URL */
20 private $query;
21  
22 /**
23 * Factory method to create a new URL from a URL string
24 *
25 * @param string $url Full URL used to create a Url object
26 *
27 * @return Url
28 * @throws \InvalidArgumentException
29 */
30 public static function fromString($url)
31 {
32 static $defaults = array('scheme' => null, 'host' => null,
33 'path' => null, 'port' => null, 'query' => null,
34 'user' => null, 'pass' => null, 'fragment' => null);
35  
36 if (false === ($parts = parse_url($url))) {
37 throw new \InvalidArgumentException('Unable to parse malformed '
38 . 'url: ' . $url);
39 }
40  
41 $parts += $defaults;
42  
43 // Convert the query string into a Query object
44 if ($parts['query'] || 0 !== strlen($parts['query'])) {
45 $parts['query'] = Query::fromString($parts['query']);
46 }
47  
48 return new static($parts['scheme'], $parts['host'], $parts['user'],
49 $parts['pass'], $parts['port'], $parts['path'], $parts['query'],
50 $parts['fragment']);
51 }
52  
53 /**
54 * Build a URL from parse_url parts. The generated URL will be a relative
55 * URL if a scheme or host are not provided.
56 *
57 * @param array $parts Array of parse_url parts
58 *
59 * @return string
60 */
61 public static function buildUrl(array $parts)
62 {
63 $url = $scheme = '';
64  
65 if (!empty($parts['scheme'])) {
66 $scheme = $parts['scheme'];
67 $url .= $scheme . ':';
68 }
69  
70 if (!empty($parts['host'])) {
71 $url .= '//';
72 if (isset($parts['user'])) {
73 $url .= $parts['user'];
74 if (isset($parts['pass'])) {
75 $url .= ':' . $parts['pass'];
76 }
77 $url .= '@';
78 }
79  
80 $url .= $parts['host'];
81  
82 // Only include the port if it is not the default port of the scheme
83 if (isset($parts['port']) &&
84 (!isset(self::$defaultPorts[$scheme]) ||
85 $parts['port'] != self::$defaultPorts[$scheme])
86 ) {
87 $url .= ':' . $parts['port'];
88 }
89 }
90  
91 // Add the path component if present
92 if (isset($parts['path']) && strlen($parts['path'])) {
93 // Always ensure that the path begins with '/' if set and something
94 // is before the path
95 if (!empty($parts['host']) && $parts['path'][0] != '/') {
96 $url .= '/';
97 }
98 $url .= $parts['path'];
99 }
100  
101 // Add the query string if present
102 if (isset($parts['query'])) {
103 $queryStr = (string) $parts['query'];
104 if ($queryStr || $queryStr === '0') {
105 $url .= '?' . $queryStr;
106 }
107 }
108  
109 // Ensure that # is only added to the url if fragment contains anything.
110 if (isset($parts['fragment'])) {
111 $url .= '#' . $parts['fragment'];
112 }
113  
114 return $url;
115 }
116  
117 /**
118 * Create a new URL from URL parts
119 *
120 * @param string $scheme Scheme of the URL
121 * @param string $host Host of the URL
122 * @param string $username Username of the URL
123 * @param string $password Password of the URL
124 * @param int $port Port of the URL
125 * @param string $path Path of the URL
126 * @param Query|array|string $query Query string of the URL
127 * @param string $fragment Fragment of the URL
128 */
129 public function __construct(
130 $scheme,
131 $host,
132 $username = null,
133 $password = null,
134 $port = null,
135 $path = null,
136 Query $query = null,
137 $fragment = null
138 ) {
139 $this->scheme = $scheme;
140 $this->host = $host;
141 $this->port = $port;
142 $this->username = $username;
143 $this->password = $password;
144 $this->fragment = $fragment;
145 if (!$query) {
146 $this->query = new Query();
147 } else {
148 $this->setQuery($query);
149 }
150 $this->setPath($path);
151 }
152  
153 /**
154 * Clone the URL
155 */
156 public function __clone()
157 {
158 $this->query = clone $this->query;
159 }
160  
161 /**
162 * Returns the URL as a URL string
163 *
164 * @return string
165 */
166 public function __toString()
167 {
168 return static::buildUrl($this->getParts());
169 }
170  
171 /**
172 * Get the parts of the URL as an array
173 *
174 * @return array
175 */
176 public function getParts()
177 {
178 return array(
179 'scheme' => $this->scheme,
180 'user' => $this->username,
181 'pass' => $this->password,
182 'host' => $this->host,
183 'port' => $this->port,
184 'path' => $this->path,
185 'query' => $this->query,
186 'fragment' => $this->fragment,
187 );
188 }
189  
190 /**
191 * Set the host of the request.
192 *
193 * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com)
194 *
195 * @return Url
196 */
197 public function setHost($host)
198 {
199 if (strpos($host, ':') === false) {
200 $this->host = $host;
201 } else {
202 list($host, $port) = explode(':', $host);
203 $this->host = $host;
204 $this->setPort($port);
205 }
206  
207 return $this;
208 }
209  
210 /**
211 * Get the host part of the URL
212 *
213 * @return string
214 */
215 public function getHost()
216 {
217 return $this->host;
218 }
219  
220 /**
221 * Set the scheme part of the URL (http, https, ftp, etc.)
222 *
223 * @param string $scheme Scheme to set
224 *
225 * @return Url
226 */
227 public function setScheme($scheme)
228 {
229 // Remove the default port if one is specified
230 if ($this->port && isset(self::$defaultPorts[$this->scheme]) &&
231 self::$defaultPorts[$this->scheme] == $this->port
232 ) {
233 $this->port = null;
234 }
235  
236 $this->scheme = $scheme;
237  
238 return $this;
239 }
240  
241 /**
242 * Get the scheme part of the URL
243 *
244 * @return string
245 */
246 public function getScheme()
247 {
248 return $this->scheme;
249 }
250  
251 /**
252 * Set the port part of the URL
253 *
254 * @param int $port Port to set
255 *
256 * @return Url
257 */
258 public function setPort($port)
259 {
260 $this->port = $port;
261  
262 return $this;
263 }
264  
265 /**
266 * Get the port part of the URl.
267 *
268 * If no port was set, this method will return the default port for the
269 * scheme of the URI.
270 *
271 * @return int|null
272 */
273 public function getPort()
274 {
275 if ($this->port) {
276 return $this->port;
277 } elseif (isset(self::$defaultPorts[$this->scheme])) {
278 return self::$defaultPorts[$this->scheme];
279 }
280  
281 return null;
282 }
283  
284 /**
285 * Set the path part of the URL
286 *
287 * @param string $path Path string to set
288 *
289 * @return Url
290 */
291 public function setPath($path)
292 {
293 static $search = [' ', '?'];
294 static $replace = ['%20', '%3F'];
295 $this->path = str_replace($search, $replace, $path);
296  
297 return $this;
298 }
299  
300 /**
301 * Removes dot segments from a URL
302 *
303 * @return Url
304 * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
305 */
306 public function removeDotSegments()
307 {
308 static $noopPaths = ['' => true, '/' => true, '*' => true];
309 static $ignoreSegments = ['.' => true, '..' => true];
310  
311 if (isset($noopPaths[$this->path])) {
312 return $this;
313 }
314  
315 $results = [];
316 $segments = $this->getPathSegments();
317 foreach ($segments as $segment) {
318 if ($segment == '..') {
319 array_pop($results);
320 } elseif (!isset($ignoreSegments[$segment])) {
321 $results[] = $segment;
322 }
323 }
324  
325 $newPath = implode('/', $results);
326  
327 // Add the leading slash if necessary
328 if (substr($this->path, 0, 1) === '/' &&
329 substr($newPath, 0, 1) !== '/'
330 ) {
331 $newPath = '/' . $newPath;
332 }
333  
334 // Add the trailing slash if necessary
335 if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
336 $newPath .= '/';
337 }
338  
339 $this->path = $newPath;
340  
341 return $this;
342 }
343  
344 /**
345 * Add a relative path to the currently set path.
346 *
347 * @param string $relativePath Relative path to add
348 *
349 * @return Url
350 */
351 public function addPath($relativePath)
352 {
353 if ($relativePath != '/' &&
354 is_string($relativePath) &&
355 strlen($relativePath) > 0
356 ) {
357 // Add a leading slash if needed
358 if ($relativePath[0] !== '/' &&
359 substr($this->path, -1, 1) !== '/'
360 ) {
361 $relativePath = '/' . $relativePath;
362 }
363  
364 $this->setPath($this->path . $relativePath);
365 }
366  
367 return $this;
368 }
369  
370 /**
371 * Get the path part of the URL
372 *
373 * @return string
374 */
375 public function getPath()
376 {
377 return $this->path;
378 }
379  
380 /**
381 * Get the path segments of the URL as an array
382 *
383 * @return array
384 */
385 public function getPathSegments()
386 {
387 return explode('/', $this->path);
388 }
389  
390 /**
391 * Set the password part of the URL
392 *
393 * @param string $password Password to set
394 *
395 * @return Url
396 */
397 public function setPassword($password)
398 {
399 $this->password = $password;
400  
401 return $this;
402 }
403  
404 /**
405 * Get the password part of the URL
406 *
407 * @return null|string
408 */
409 public function getPassword()
410 {
411 return $this->password;
412 }
413  
414 /**
415 * Set the username part of the URL
416 *
417 * @param string $username Username to set
418 *
419 * @return Url
420 */
421 public function setUsername($username)
422 {
423 $this->username = $username;
424  
425 return $this;
426 }
427  
428 /**
429 * Get the username part of the URl
430 *
431 * @return null|string
432 */
433 public function getUsername()
434 {
435 return $this->username;
436 }
437  
438 /**
439 * Get the query part of the URL as a Query object
440 *
441 * @return Query
442 */
443 public function getQuery()
444 {
445 return $this->query;
446 }
447  
448 /**
449 * Set the query part of the URL
450 *
451 * @param Query|string|array $query Query string value to set. Can
452 * be a string that will be parsed into a Query object, an array
453 * of key value pairs, or a Query object.
454 *
455 * @return Url
456 * @throws \InvalidArgumentException
457 */
458 public function setQuery($query)
459 {
460 if ($query instanceof Query) {
461 $this->query = $query;
462 } elseif (is_string($query)) {
463 $this->query = Query::fromString($query);
464 } elseif (is_array($query)) {
465 $this->query = new Query($query);
466 } else {
467 throw new \InvalidArgumentException('Query must be a '
468 . 'QueryInterface, array, or string');
469 }
470  
471 return $this;
472 }
473  
474 /**
475 * Get the fragment part of the URL
476 *
477 * @return null|string
478 */
479 public function getFragment()
480 {
481 return $this->fragment;
482 }
483  
484 /**
485 * Set the fragment part of the URL
486 *
487 * @param string $fragment Fragment to set
488 *
489 * @return Url
490 */
491 public function setFragment($fragment)
492 {
493 $this->fragment = $fragment;
494  
495 return $this;
496 }
497  
498 /**
499 * Check if this is an absolute URL
500 *
501 * @return bool
502 */
503 public function isAbsolute()
504 {
505 return $this->scheme && $this->host;
506 }
507  
508 /**
509 * Combine the URL with another URL and return a new URL instance.
510 *
511 * Follows the rules specific in RFC 3986 section 5.4.
512 *
513 * @param string $url Relative URL to combine with
514 *
515 * @return Url
516 * @throws \InvalidArgumentException
517 * @link http://tools.ietf.org/html/rfc3986#section-5.4
518 */
519 public function combine($url)
520 {
521 $url = static::fromString($url);
522  
523 // Use the more absolute URL as the base URL
524 if (!$this->isAbsolute() && $url->isAbsolute()) {
525 $url = $url->combine($this);
526 }
527  
528 $parts = $url->getParts();
529  
530 // Passing a URL with a scheme overrides everything
531 if ($parts['scheme']) {
532 return new static(
533 $parts['scheme'],
534 $parts['host'],
535 $parts['user'],
536 $parts['pass'],
537 $parts['port'],
538 $parts['path'],
539 clone $parts['query'],
540 $parts['fragment']
541 );
542 }
543  
544 // Setting a host overrides the entire rest of the URL
545 if ($parts['host']) {
546 return new static(
547 $this->scheme,
548 $parts['host'],
549 $parts['user'],
550 $parts['pass'],
551 $parts['port'],
552 $parts['path'],
553 clone $parts['query'],
554 $parts['fragment']
555 );
556 }
557  
558 if (!$parts['path'] && $parts['path'] !== '0') {
559 // The relative URL has no path, so check if it is just a query
560 $path = $this->path ?: '';
561 $query = count($parts['query']) ? $parts['query'] : $this->query;
562 } else {
563 $query = $parts['query'];
564 if ($parts['path'][0] == '/' || !$this->path) {
565 // Overwrite the existing path if the rel path starts with "/"
566 $path = $parts['path'];
567 } else {
568 // If the relative URL does not have a path or the base URL
569 // path does not end in a "/" then overwrite the existing path
570 // up to the last "/"
571 $path = substr($this->path, 0, strrpos($this->path, '/') + 1) . $parts['path'];
572 }
573 }
574  
575 $result = new self(
576 $this->scheme,
577 $this->host,
578 $this->username,
579 $this->password,
580 $this->port,
581 $path,
582 clone $query,
583 $parts['fragment']
584 );
585  
586 if ($path) {
587 $result->removeDotSegments();
588 }
589  
590 return $result;
591 }
592 }