UriTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. <?php
  2. namespace GuzzleHttp\Tests\Psr7;
  3. use GuzzleHttp\Psr7\Uri;
  4. /**
  5. * @covers GuzzleHttp\Psr7\Uri
  6. */
  7. class UriTest extends BaseTest
  8. {
  9. public function testParsesProvidedUri()
  10. {
  11. $uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#test');
  12. $this->assertSame('https', $uri->getScheme());
  13. $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
  14. $this->assertSame('user:pass', $uri->getUserInfo());
  15. $this->assertSame('example.com', $uri->getHost());
  16. $this->assertSame(8080, $uri->getPort());
  17. $this->assertSame('/path/123', $uri->getPath());
  18. $this->assertSame('q=abc', $uri->getQuery());
  19. $this->assertSame('test', $uri->getFragment());
  20. $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
  21. }
  22. public function testCanTransformAndRetrievePartsIndividually()
  23. {
  24. $uri = (new Uri())
  25. ->withScheme('https')
  26. ->withUserInfo('user', 'pass')
  27. ->withHost('example.com')
  28. ->withPort(8080)
  29. ->withPath('/path/123')
  30. ->withQuery('q=abc')
  31. ->withFragment('test');
  32. $this->assertSame('https', $uri->getScheme());
  33. $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
  34. $this->assertSame('user:pass', $uri->getUserInfo());
  35. $this->assertSame('example.com', $uri->getHost());
  36. $this->assertSame(8080, $uri->getPort());
  37. $this->assertSame('/path/123', $uri->getPath());
  38. $this->assertSame('q=abc', $uri->getQuery());
  39. $this->assertSame('test', $uri->getFragment());
  40. $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
  41. }
  42. /**
  43. * @dataProvider getValidUris
  44. */
  45. public function testValidUrisStayValid($input)
  46. {
  47. $uri = new Uri($input);
  48. $this->assertSame($input, (string) $uri);
  49. }
  50. /**
  51. * @dataProvider getValidUris
  52. */
  53. public function testFromParts($input)
  54. {
  55. $uri = Uri::fromParts(parse_url($input));
  56. $this->assertSame($input, (string) $uri);
  57. }
  58. public function getValidUris()
  59. {
  60. return [
  61. ['urn:path-rootless'],
  62. ['urn:path:with:colon'],
  63. ['urn:/path-absolute'],
  64. ['urn:/'],
  65. // only scheme with empty path
  66. ['urn:'],
  67. // only path
  68. ['/'],
  69. ['relative/'],
  70. ['0'],
  71. // same document reference
  72. [''],
  73. // network path without scheme
  74. ['//example.org'],
  75. ['//example.org/'],
  76. ['//example.org?q#h'],
  77. // only query
  78. ['?q'],
  79. ['?q=abc&foo=bar'],
  80. // only fragment
  81. ['#fragment'],
  82. // dot segments are not removed automatically
  83. ['./foo/../bar'],
  84. ];
  85. }
  86. /**
  87. * @expectedException \InvalidArgumentException
  88. * @expectedExceptionMessage Unable to parse URI
  89. * @dataProvider getInvalidUris
  90. */
  91. public function testInvalidUrisThrowException($invalidUri)
  92. {
  93. new Uri($invalidUri);
  94. }
  95. public function getInvalidUris()
  96. {
  97. return [
  98. // parse_url() requires the host component which makes sense for http(s)
  99. // but not when the scheme is not known or different. So '//' or '///' is
  100. // currently invalid as well but should not according to RFC 3986.
  101. ['http://'],
  102. ['urn://host:with:colon'], // host cannot contain ":"
  103. ];
  104. }
  105. /**
  106. * @expectedException \InvalidArgumentException
  107. * @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535
  108. */
  109. public function testPortMustBeValid()
  110. {
  111. (new Uri())->withPort(100000);
  112. }
  113. /**
  114. * @expectedException \InvalidArgumentException
  115. * @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535
  116. */
  117. public function testWithPortCannotBeZero()
  118. {
  119. (new Uri())->withPort(0);
  120. }
  121. /**
  122. * @expectedException \InvalidArgumentException
  123. * @expectedExceptionMessage Unable to parse URI
  124. */
  125. public function testParseUriPortCannotBeZero()
  126. {
  127. new Uri('//example.com:0');
  128. }
  129. /**
  130. * @expectedException \InvalidArgumentException
  131. */
  132. public function testSchemeMustHaveCorrectType()
  133. {
  134. (new Uri())->withScheme([]);
  135. }
  136. /**
  137. * @expectedException \InvalidArgumentException
  138. */
  139. public function testHostMustHaveCorrectType()
  140. {
  141. (new Uri())->withHost([]);
  142. }
  143. /**
  144. * @expectedException \InvalidArgumentException
  145. */
  146. public function testPathMustHaveCorrectType()
  147. {
  148. (new Uri())->withPath([]);
  149. }
  150. /**
  151. * @expectedException \InvalidArgumentException
  152. */
  153. public function testQueryMustHaveCorrectType()
  154. {
  155. (new Uri())->withQuery([]);
  156. }
  157. /**
  158. * @expectedException \InvalidArgumentException
  159. */
  160. public function testFragmentMustHaveCorrectType()
  161. {
  162. (new Uri())->withFragment([]);
  163. }
  164. public function testCanParseFalseyUriParts()
  165. {
  166. $uri = new Uri('0://0:0@0/0?0#0');
  167. $this->assertSame('0', $uri->getScheme());
  168. $this->assertSame('0:0@0', $uri->getAuthority());
  169. $this->assertSame('0:0', $uri->getUserInfo());
  170. $this->assertSame('0', $uri->getHost());
  171. $this->assertSame('/0', $uri->getPath());
  172. $this->assertSame('0', $uri->getQuery());
  173. $this->assertSame('0', $uri->getFragment());
  174. $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
  175. }
  176. public function testCanConstructFalseyUriParts()
  177. {
  178. $uri = (new Uri())
  179. ->withScheme('0')
  180. ->withUserInfo('0', '0')
  181. ->withHost('0')
  182. ->withPath('/0')
  183. ->withQuery('0')
  184. ->withFragment('0');
  185. $this->assertSame('0', $uri->getScheme());
  186. $this->assertSame('0:0@0', $uri->getAuthority());
  187. $this->assertSame('0:0', $uri->getUserInfo());
  188. $this->assertSame('0', $uri->getHost());
  189. $this->assertSame('/0', $uri->getPath());
  190. $this->assertSame('0', $uri->getQuery());
  191. $this->assertSame('0', $uri->getFragment());
  192. $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
  193. }
  194. /**
  195. * @dataProvider getPortTestCases
  196. */
  197. public function testIsDefaultPort($scheme, $port, $isDefaultPort)
  198. {
  199. $uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock();
  200. $uri->expects($this->any())->method('getScheme')->will($this->returnValue($scheme));
  201. $uri->expects($this->any())->method('getPort')->will($this->returnValue($port));
  202. $this->assertSame($isDefaultPort, Uri::isDefaultPort($uri));
  203. }
  204. public function getPortTestCases()
  205. {
  206. return [
  207. ['http', null, true],
  208. ['http', 80, true],
  209. ['http', 8080, false],
  210. ['https', null, true],
  211. ['https', 443, true],
  212. ['https', 444, false],
  213. ['ftp', 21, true],
  214. ['gopher', 70, true],
  215. ['nntp', 119, true],
  216. ['news', 119, true],
  217. ['telnet', 23, true],
  218. ['tn3270', 23, true],
  219. ['imap', 143, true],
  220. ['pop', 110, true],
  221. ['ldap', 389, true],
  222. ];
  223. }
  224. public function testIsAbsolute()
  225. {
  226. $this->assertTrue(Uri::isAbsolute(new Uri('http://example.org')));
  227. $this->assertFalse(Uri::isAbsolute(new Uri('//example.org')));
  228. $this->assertFalse(Uri::isAbsolute(new Uri('/abs-path')));
  229. $this->assertFalse(Uri::isAbsolute(new Uri('rel-path')));
  230. }
  231. public function testIsNetworkPathReference()
  232. {
  233. $this->assertFalse(Uri::isNetworkPathReference(new Uri('http://example.org')));
  234. $this->assertTrue(Uri::isNetworkPathReference(new Uri('//example.org')));
  235. $this->assertFalse(Uri::isNetworkPathReference(new Uri('/abs-path')));
  236. $this->assertFalse(Uri::isNetworkPathReference(new Uri('rel-path')));
  237. }
  238. public function testIsAbsolutePathReference()
  239. {
  240. $this->assertFalse(Uri::isAbsolutePathReference(new Uri('http://example.org')));
  241. $this->assertFalse(Uri::isAbsolutePathReference(new Uri('//example.org')));
  242. $this->assertTrue(Uri::isAbsolutePathReference(new Uri('/abs-path')));
  243. $this->assertTrue(Uri::isAbsolutePathReference(new Uri('/')));
  244. $this->assertFalse(Uri::isAbsolutePathReference(new Uri('rel-path')));
  245. }
  246. public function testIsRelativePathReference()
  247. {
  248. $this->assertFalse(Uri::isRelativePathReference(new Uri('http://example.org')));
  249. $this->assertFalse(Uri::isRelativePathReference(new Uri('//example.org')));
  250. $this->assertFalse(Uri::isRelativePathReference(new Uri('/abs-path')));
  251. $this->assertTrue(Uri::isRelativePathReference(new Uri('rel-path')));
  252. $this->assertTrue(Uri::isRelativePathReference(new Uri('')));
  253. }
  254. public function testIsSameDocumentReference()
  255. {
  256. $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org')));
  257. $this->assertFalse(Uri::isSameDocumentReference(new Uri('//example.org')));
  258. $this->assertFalse(Uri::isSameDocumentReference(new Uri('/abs-path')));
  259. $this->assertFalse(Uri::isSameDocumentReference(new Uri('rel-path')));
  260. $this->assertFalse(Uri::isSameDocumentReference(new Uri('?query')));
  261. $this->assertTrue(Uri::isSameDocumentReference(new Uri('')));
  262. $this->assertTrue(Uri::isSameDocumentReference(new Uri('#fragment')));
  263. $baseUri = new Uri('http://example.org/path?foo=bar');
  264. $this->assertTrue(Uri::isSameDocumentReference(new Uri('#fragment'), $baseUri));
  265. $this->assertTrue(Uri::isSameDocumentReference(new Uri('?foo=bar#fragment'), $baseUri));
  266. $this->assertTrue(Uri::isSameDocumentReference(new Uri('/path?foo=bar#fragment'), $baseUri));
  267. $this->assertTrue(Uri::isSameDocumentReference(new Uri('path?foo=bar#fragment'), $baseUri));
  268. $this->assertTrue(Uri::isSameDocumentReference(new Uri('//example.org/path?foo=bar#fragment'), $baseUri));
  269. $this->assertTrue(Uri::isSameDocumentReference(new Uri('http://example.org/path?foo=bar#fragment'), $baseUri));
  270. $this->assertFalse(Uri::isSameDocumentReference(new Uri('https://example.org/path?foo=bar'), $baseUri));
  271. $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.com/path?foo=bar'), $baseUri));
  272. $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org/'), $baseUri));
  273. $this->assertFalse(Uri::isSameDocumentReference(new Uri('http://example.org'), $baseUri));
  274. $this->assertFalse(Uri::isSameDocumentReference(new Uri('urn:/path'), new Uri('urn://example.com/path')));
  275. }
  276. public function testAddAndRemoveQueryValues()
  277. {
  278. $uri = new Uri();
  279. $uri = Uri::withQueryValue($uri, 'a', 'b');
  280. $uri = Uri::withQueryValue($uri, 'c', 'd');
  281. $uri = Uri::withQueryValue($uri, 'e', null);
  282. $this->assertSame('a=b&c=d&e', $uri->getQuery());
  283. $uri = Uri::withoutQueryValue($uri, 'c');
  284. $this->assertSame('a=b&e', $uri->getQuery());
  285. $uri = Uri::withoutQueryValue($uri, 'e');
  286. $this->assertSame('a=b', $uri->getQuery());
  287. $uri = Uri::withoutQueryValue($uri, 'a');
  288. $this->assertSame('', $uri->getQuery());
  289. }
  290. public function testWithQueryValues()
  291. {
  292. $uri = new Uri();
  293. $uri = Uri::withQueryValues($uri, [
  294. 'key1' => 'value1',
  295. 'key2' => 'value2'
  296. ]);
  297. $this->assertSame('key1=value1&key2=value2', $uri->getQuery());
  298. }
  299. public function testWithQueryValuesReplacesSameKeys()
  300. {
  301. $uri = new Uri();
  302. $uri = Uri::withQueryValues($uri, [
  303. 'key1' => 'value1',
  304. 'key2' => 'value2'
  305. ]);
  306. $uri = Uri::withQueryValues($uri, [
  307. 'key2' => 'newvalue'
  308. ]);
  309. $this->assertSame('key1=value1&key2=newvalue', $uri->getQuery());
  310. }
  311. public function testWithQueryValueReplacesSameKeys()
  312. {
  313. $uri = new Uri();
  314. $uri = Uri::withQueryValue($uri, 'a', 'b');
  315. $uri = Uri::withQueryValue($uri, 'c', 'd');
  316. $uri = Uri::withQueryValue($uri, 'a', 'e');
  317. $this->assertSame('c=d&a=e', $uri->getQuery());
  318. }
  319. public function testWithoutQueryValueRemovesAllSameKeys()
  320. {
  321. $uri = (new Uri())->withQuery('a=b&c=d&a=e');
  322. $uri = Uri::withoutQueryValue($uri, 'a');
  323. $this->assertSame('c=d', $uri->getQuery());
  324. }
  325. public function testRemoveNonExistingQueryValue()
  326. {
  327. $uri = new Uri();
  328. $uri = Uri::withQueryValue($uri, 'a', 'b');
  329. $uri = Uri::withoutQueryValue($uri, 'c');
  330. $this->assertSame('a=b', $uri->getQuery());
  331. }
  332. public function testWithQueryValueHandlesEncoding()
  333. {
  334. $uri = new Uri();
  335. $uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein');
  336. $this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded');
  337. $uri = new Uri();
  338. $uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein');
  339. $this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
  340. }
  341. public function testWithoutQueryValueHandlesEncoding()
  342. {
  343. // It also tests that the case of the percent-encoding does not matter,
  344. // i.e. both lowercase "%3d" and uppercase "%5E" can be removed.
  345. $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
  346. $uri = Uri::withoutQueryValue($uri, 'E=mc^2');
  347. $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form');
  348. $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
  349. $uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2');
  350. $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form');
  351. }
  352. public function testSchemeIsNormalizedToLowercase()
  353. {
  354. $uri = new Uri('HTTP://example.com');
  355. $this->assertSame('http', $uri->getScheme());
  356. $this->assertSame('http://example.com', (string) $uri);
  357. $uri = (new Uri('//example.com'))->withScheme('HTTP');
  358. $this->assertSame('http', $uri->getScheme());
  359. $this->assertSame('http://example.com', (string) $uri);
  360. }
  361. public function testHostIsNormalizedToLowercase()
  362. {
  363. $uri = new Uri('//eXaMpLe.CoM');
  364. $this->assertSame('example.com', $uri->getHost());
  365. $this->assertSame('//example.com', (string) $uri);
  366. $uri = (new Uri())->withHost('eXaMpLe.CoM');
  367. $this->assertSame('example.com', $uri->getHost());
  368. $this->assertSame('//example.com', (string) $uri);
  369. }
  370. public function testPortIsNullIfStandardPortForScheme()
  371. {
  372. // HTTPS standard port
  373. $uri = new Uri('https://example.com:443');
  374. $this->assertNull($uri->getPort());
  375. $this->assertSame('example.com', $uri->getAuthority());
  376. $uri = (new Uri('https://example.com'))->withPort(443);
  377. $this->assertNull($uri->getPort());
  378. $this->assertSame('example.com', $uri->getAuthority());
  379. // HTTP standard port
  380. $uri = new Uri('http://example.com:80');
  381. $this->assertNull($uri->getPort());
  382. $this->assertSame('example.com', $uri->getAuthority());
  383. $uri = (new Uri('http://example.com'))->withPort(80);
  384. $this->assertNull($uri->getPort());
  385. $this->assertSame('example.com', $uri->getAuthority());
  386. }
  387. public function testPortIsReturnedIfSchemeUnknown()
  388. {
  389. $uri = (new Uri('//example.com'))->withPort(80);
  390. $this->assertSame(80, $uri->getPort());
  391. $this->assertSame('example.com:80', $uri->getAuthority());
  392. }
  393. public function testStandardPortIsNullIfSchemeChanges()
  394. {
  395. $uri = new Uri('http://example.com:443');
  396. $this->assertSame('http', $uri->getScheme());
  397. $this->assertSame(443, $uri->getPort());
  398. $uri = $uri->withScheme('https');
  399. $this->assertNull($uri->getPort());
  400. }
  401. public function testPortPassedAsStringIsCastedToInt()
  402. {
  403. $uri = (new Uri('//example.com'))->withPort('8080');
  404. $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer');
  405. $this->assertSame('example.com:8080', $uri->getAuthority());
  406. }
  407. public function testPortCanBeRemoved()
  408. {
  409. $uri = (new Uri('http://example.com:8080'))->withPort(null);
  410. $this->assertNull($uri->getPort());
  411. $this->assertSame('http://example.com', (string) $uri);
  412. }
  413. /**
  414. * In RFC 8986 the host is optional and the authority can only
  415. * consist of the user info and port.
  416. */
  417. public function testAuthorityWithUserInfoOrPortButWithoutHost()
  418. {
  419. $uri = (new Uri())->withUserInfo('user', 'pass');
  420. $this->assertSame('user:pass', $uri->getUserInfo());
  421. $this->assertSame('user:pass@', $uri->getAuthority());
  422. $uri = $uri->withPort(8080);
  423. $this->assertSame(8080, $uri->getPort());
  424. $this->assertSame('user:pass@:8080', $uri->getAuthority());
  425. $this->assertSame('//user:pass@:8080', (string) $uri);
  426. $uri = $uri->withUserInfo('');
  427. $this->assertSame(':8080', $uri->getAuthority());
  428. }
  429. public function testHostInHttpUriDefaultsToLocalhost()
  430. {
  431. $uri = (new Uri())->withScheme('http');
  432. $this->assertSame('localhost', $uri->getHost());
  433. $this->assertSame('localhost', $uri->getAuthority());
  434. $this->assertSame('http://localhost', (string) $uri);
  435. }
  436. public function testHostInHttpsUriDefaultsToLocalhost()
  437. {
  438. $uri = (new Uri())->withScheme('https');
  439. $this->assertSame('localhost', $uri->getHost());
  440. $this->assertSame('localhost', $uri->getAuthority());
  441. $this->assertSame('https://localhost', (string) $uri);
  442. }
  443. public function testFileSchemeWithEmptyHostReconstruction()
  444. {
  445. $uri = new Uri('file:///tmp/filename.ext');
  446. $this->assertSame('', $uri->getHost());
  447. $this->assertSame('', $uri->getAuthority());
  448. $this->assertSame('file:///tmp/filename.ext', (string) $uri);
  449. }
  450. public function uriComponentsEncodingProvider()
  451. {
  452. $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@';
  453. return [
  454. // Percent encode spaces
  455. ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
  456. // Percent encode multibyte
  457. ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'],
  458. // Don't encode something that's already encoded
  459. ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
  460. // Percent encode invalid percent encodings
  461. ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'],
  462. // Don't encode path segments
  463. ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'],
  464. // Don't encode unreserved chars or sub-delimiters
  465. ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"],
  466. // Encoded unreserved chars are not decoded
  467. ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'],
  468. ];
  469. }
  470. /**
  471. * @dataProvider uriComponentsEncodingProvider
  472. */
  473. public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output)
  474. {
  475. $uri = new Uri($input);
  476. $this->assertSame($path, $uri->getPath());
  477. $this->assertSame($query, $uri->getQuery());
  478. $this->assertSame($fragment, $uri->getFragment());
  479. $this->assertSame($output, (string) $uri);
  480. }
  481. public function testWithPathEncodesProperly()
  482. {
  483. $uri = (new Uri())->withPath('/baz?#€/b%61r');
  484. // Query and fragment delimiters and multibyte chars are encoded.
  485. $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath());
  486. $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri);
  487. }
  488. public function testWithQueryEncodesProperly()
  489. {
  490. $uri = (new Uri())->withQuery('?=#&€=/&b%61r');
  491. // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to
  492. // construct such an URI. Also the "?" and "/" does not need to be encoded in the query.
  493. $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery());
  494. $this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri);
  495. }
  496. public function testWithFragmentEncodesProperly()
  497. {
  498. $uri = (new Uri())->withFragment('#€?/b%61r');
  499. // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to
  500. // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment.
  501. $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment());
  502. $this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri);
  503. }
  504. public function testAllowsForRelativeUri()
  505. {
  506. $uri = (new Uri)->withPath('foo');
  507. $this->assertSame('foo', $uri->getPath());
  508. $this->assertSame('foo', (string) $uri);
  509. }
  510. public function testRelativePathAndAuhorityIsAutomagicallyFixed()
  511. {
  512. // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
  513. $uri = (new Uri)->withPath('foo')->withHost('example.com');
  514. $this->assertSame('/foo', $uri->getPath());
  515. $this->assertSame('//example.com/foo', (string) $uri);
  516. }
  517. /**
  518. * @expectedException \InvalidArgumentException
  519. * @expectedExceptionMessage The path of a URI without an authority must not start with two slashes "//"
  520. */
  521. public function testPathStartingWithTwoSlashesAndNoAuthorityIsInvalid()
  522. {
  523. // URI "//foo" would be interpreted as network reference and thus change the original path to the host
  524. (new Uri)->withPath('//foo');
  525. }
  526. public function testPathStartingWithTwoSlashes()
  527. {
  528. $uri = new Uri('http://example.org//path-not-host.com');
  529. $this->assertSame('//path-not-host.com', $uri->getPath());
  530. $uri = $uri->withScheme('');
  531. $this->assertSame('//example.org//path-not-host.com', (string) $uri); // This is still valid
  532. $this->expectException('\InvalidArgumentException');
  533. $uri->withHost(''); // Now it becomes invalid
  534. }
  535. /**
  536. * @expectedException \InvalidArgumentException
  537. * @expectedExceptionMessage A relative URI must not have a path beginning with a segment containing a colon
  538. */
  539. public function testRelativeUriWithPathBeginngWithColonSegmentIsInvalid()
  540. {
  541. (new Uri)->withPath('mailto:foo');
  542. }
  543. public function testRelativeUriWithPathHavingColonSegment()
  544. {
  545. $uri = (new Uri('urn:/mailto:foo'))->withScheme('');
  546. $this->assertSame('/mailto:foo', $uri->getPath());
  547. $this->expectException('\InvalidArgumentException');
  548. (new Uri('urn:mailto:foo'))->withScheme('');
  549. }
  550. public function testDefaultReturnValuesOfGetters()
  551. {
  552. $uri = new Uri();
  553. $this->assertSame('', $uri->getScheme());
  554. $this->assertSame('', $uri->getAuthority());
  555. $this->assertSame('', $uri->getUserInfo());
  556. $this->assertSame('', $uri->getHost());
  557. $this->assertNull($uri->getPort());
  558. $this->assertSame('', $uri->getPath());
  559. $this->assertSame('', $uri->getQuery());
  560. $this->assertSame('', $uri->getFragment());
  561. }
  562. public function testImmutability()
  563. {
  564. $uri = new Uri();
  565. $this->assertNotSame($uri, $uri->withScheme('https'));
  566. $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass'));
  567. $this->assertNotSame($uri, $uri->withHost('example.com'));
  568. $this->assertNotSame($uri, $uri->withPort(8080));
  569. $this->assertNotSame($uri, $uri->withPath('/path/123'));
  570. $this->assertNotSame($uri, $uri->withQuery('q=abc'));
  571. $this->assertNotSame($uri, $uri->withFragment('test'));
  572. }
  573. public function testExtendingClassesInstantiates()
  574. {
  575. // The non-standard port triggers a cascade of private methods which
  576. // should not use late static binding to access private static members.
  577. // If they do, this will fatal.
  578. $this->assertInstanceOf(
  579. 'GuzzleHttp\Tests\Psr7\ExtendedUriTest',
  580. new ExtendedUriTest('http://h:9/')
  581. );
  582. }
  583. public function testSpecialCharsOfUserInfo()
  584. {
  585. // The `userInfo` must always be URL-encoded.
  586. $uri = (new Uri)->withUserInfo('foo@bar.com', 'pass#word');
  587. $this->assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo());
  588. // The `userInfo` can already be URL-encoded: it should not be encoded twice.
  589. $uri = (new Uri)->withUserInfo('foo%40bar.com', 'pass%23word');
  590. $this->assertSame('foo%40bar.com:pass%23word', $uri->getUserInfo());
  591. }
  592. }
  593. class ExtendedUriTest extends Uri
  594. {
  595. }