PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/api/vendor/guzzlehttp/guzzle/src/Url.php

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