RequestTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?php
  2. declare(strict_types=1);
  3. namespace GuzzleHttp\Tests\Psr7;
  4. use GuzzleHttp\Psr7;
  5. use GuzzleHttp\Psr7\Request;
  6. use GuzzleHttp\Psr7\Uri;
  7. use PHPUnit\Framework\TestCase;
  8. use Psr\Http\Message\StreamInterface;
  9. /**
  10. * @covers \GuzzleHttp\Psr7\MessageTrait
  11. * @covers \GuzzleHttp\Psr7\Request
  12. */
  13. class RequestTest extends TestCase
  14. {
  15. public function testRequestUriMayBeString(): void
  16. {
  17. $r = new Request('GET', '/');
  18. self::assertSame('/', (string) $r->getUri());
  19. }
  20. public function testRequestUriMayBeUri(): void
  21. {
  22. $uri = new Uri('/');
  23. $r = new Request('GET', $uri);
  24. self::assertSame($uri, $r->getUri());
  25. }
  26. public function testValidateRequestUri(): void
  27. {
  28. $this->expectException(\InvalidArgumentException::class);
  29. new Request('GET', '///');
  30. }
  31. public function testCanConstructWithBody(): void
  32. {
  33. $r = new Request('GET', '/', [], 'baz');
  34. self::assertInstanceOf(StreamInterface::class, $r->getBody());
  35. self::assertSame('baz', (string) $r->getBody());
  36. }
  37. public function testNullBody(): void
  38. {
  39. $r = new Request('GET', '/', [], null);
  40. self::assertInstanceOf(StreamInterface::class, $r->getBody());
  41. self::assertSame('', (string) $r->getBody());
  42. }
  43. public function testFalseyBody(): void
  44. {
  45. $r = new Request('GET', '/', [], '0');
  46. self::assertInstanceOf(StreamInterface::class, $r->getBody());
  47. self::assertSame('0', (string) $r->getBody());
  48. }
  49. public function testConstructorDoesNotReadStreamBody(): void
  50. {
  51. $streamIsRead = false;
  52. $body = Psr7\FnStream::decorate(Psr7\Utils::streamFor(''), [
  53. '__toString' => function () use (&$streamIsRead) {
  54. $streamIsRead = true;
  55. return '';
  56. },
  57. ]);
  58. $r = new Request('GET', '/', [], $body);
  59. self::assertFalse($streamIsRead);
  60. self::assertSame($body, $r->getBody());
  61. }
  62. public function testCapitalizesMethod(): void
  63. {
  64. $r = new Request('get', '/');
  65. self::assertSame('GET', $r->getMethod());
  66. }
  67. public function testCapitalizesWithMethod(): void
  68. {
  69. $r = new Request('GET', '/');
  70. self::assertSame('PUT', $r->withMethod('put')->getMethod());
  71. }
  72. public function testWithUri(): void
  73. {
  74. $r1 = new Request('GET', '/');
  75. $u1 = $r1->getUri();
  76. $u2 = new Uri('http://www.example.com');
  77. $r2 = $r1->withUri($u2);
  78. self::assertNotSame($r1, $r2);
  79. self::assertSame($u2, $r2->getUri());
  80. self::assertSame($u1, $r1->getUri());
  81. }
  82. /**
  83. * @dataProvider invalidMethodsProvider
  84. */
  85. public function testConstructWithInvalidMethods($method): void
  86. {
  87. $this->expectException(\TypeError::class);
  88. new Request($method, '/');
  89. }
  90. /**
  91. * @dataProvider invalidMethodsProvider
  92. */
  93. public function testWithInvalidMethods($method): void
  94. {
  95. $r = new Request('get', '/');
  96. $this->expectException(\InvalidArgumentException::class);
  97. $r->withMethod($method);
  98. }
  99. public function invalidMethodsProvider(): iterable
  100. {
  101. return [
  102. [null],
  103. [false],
  104. [['foo']],
  105. [new \stdClass()],
  106. ];
  107. }
  108. public function testSameInstanceWhenSameUri(): void
  109. {
  110. $r1 = new Request('GET', 'http://foo.com');
  111. $r2 = $r1->withUri($r1->getUri());
  112. self::assertSame($r1, $r2);
  113. }
  114. public function testWithRequestTarget(): void
  115. {
  116. $r1 = new Request('GET', '/');
  117. $r2 = $r1->withRequestTarget('*');
  118. self::assertSame('*', $r2->getRequestTarget());
  119. self::assertSame('/', $r1->getRequestTarget());
  120. }
  121. public function testRequestTargetDoesNotAllowSpaces(): void
  122. {
  123. $r1 = new Request('GET', '/');
  124. $this->expectException(\InvalidArgumentException::class);
  125. $r1->withRequestTarget('/foo bar');
  126. }
  127. public function testRequestTargetDefaultsToSlash(): void
  128. {
  129. $r1 = new Request('GET', '');
  130. self::assertSame('/', $r1->getRequestTarget());
  131. $r2 = new Request('GET', '*');
  132. self::assertSame('*', $r2->getRequestTarget());
  133. $r3 = new Request('GET', 'http://foo.com/bar baz/');
  134. self::assertSame('/bar%20baz/', $r3->getRequestTarget());
  135. }
  136. public function testBuildsRequestTarget(): void
  137. {
  138. $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
  139. self::assertSame('/baz?bar=bam', $r1->getRequestTarget());
  140. }
  141. public function testBuildsRequestTargetWithFalseyQuery(): void
  142. {
  143. $r1 = new Request('GET', 'http://foo.com/baz?0');
  144. self::assertSame('/baz?0', $r1->getRequestTarget());
  145. }
  146. public function testHostIsAddedFirst(): void
  147. {
  148. $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']);
  149. self::assertSame([
  150. 'Host' => ['foo.com'],
  151. 'Foo' => ['Bar'],
  152. ], $r->getHeaders());
  153. }
  154. public function testHeaderValueWithWhitespace(): void
  155. {
  156. $r = new Request('GET', 'https://example.com/', [
  157. 'User-Agent' => 'Linux f0f489981e90 5.10.104-linuxkit 1 SMP Wed Mar 9 19:05:23 UTC 2022 x86_64',
  158. ]);
  159. self::assertSame([
  160. 'Host' => ['example.com'],
  161. 'User-Agent' => ['Linux f0f489981e90 5.10.104-linuxkit 1 SMP Wed Mar 9 19:05:23 UTC 2022 x86_64'],
  162. ], $r->getHeaders());
  163. }
  164. public function testCanGetHeaderAsCsv(): void
  165. {
  166. $r = new Request('GET', 'http://foo.com/baz?bar=bam', [
  167. 'Foo' => ['a', 'b', 'c'],
  168. ]);
  169. self::assertSame('a, b, c', $r->getHeaderLine('Foo'));
  170. self::assertSame('', $r->getHeaderLine('Bar'));
  171. }
  172. /**
  173. * @dataProvider provideHeadersContainingNotAllowedChars
  174. */
  175. public function testContainsNotAllowedCharsOnHeaderField($header): void
  176. {
  177. $this->expectExceptionMessage(
  178. sprintf(
  179. '"%s" is not valid header name',
  180. $header
  181. )
  182. );
  183. $r = new Request(
  184. 'GET',
  185. 'http://foo.com/baz?bar=bam',
  186. [
  187. $header => 'value',
  188. ]
  189. );
  190. }
  191. public function provideHeadersContainingNotAllowedChars(): iterable
  192. {
  193. return [[' key '], ['key '], [' key'], ['key/'], ['key('], ['key\\'], [' ']];
  194. }
  195. /**
  196. * @dataProvider provideHeadersContainsAllowedChar
  197. */
  198. public function testContainsAllowedCharsOnHeaderField($header): void
  199. {
  200. $r = new Request(
  201. 'GET',
  202. 'http://foo.com/baz?bar=bam',
  203. [
  204. $header => 'value',
  205. ]
  206. );
  207. self::assertArrayHasKey($header, $r->getHeaders());
  208. }
  209. public function provideHeadersContainsAllowedChar(): iterable
  210. {
  211. return [
  212. ['key'],
  213. ['key#'],
  214. ['key$'],
  215. ['key%'],
  216. ['key&'],
  217. ['key*'],
  218. ['key+'],
  219. ['key.'],
  220. ['key^'],
  221. ['key_'],
  222. ['key|'],
  223. ['key~'],
  224. ['key!'],
  225. ['key-'],
  226. ["key'"],
  227. ['key`'],
  228. ];
  229. }
  230. public function testHostIsNotOverwrittenWhenPreservingHost(): void
  231. {
  232. $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']);
  233. self::assertSame(['Host' => ['a.com']], $r->getHeaders());
  234. $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
  235. self::assertSame('a.com', $r2->getHeaderLine('Host'));
  236. }
  237. public function testWithUriSetsHostIfNotSet(): void
  238. {
  239. $r = (new Request('GET', 'http://foo.com/baz?bar=bam'))->withoutHeader('Host');
  240. self::assertSame([], $r->getHeaders());
  241. $r2 = $r->withUri(new Uri('http://www.baz.com/bar'), true);
  242. self::assertSame('www.baz.com', $r2->getHeaderLine('Host'));
  243. }
  244. public function testOverridesHostWithUri(): void
  245. {
  246. $r = new Request('GET', 'http://foo.com/baz?bar=bam');
  247. self::assertSame(['Host' => ['foo.com']], $r->getHeaders());
  248. $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
  249. self::assertSame('www.baz.com', $r2->getHeaderLine('Host'));
  250. }
  251. public function testAggregatesHeaders(): void
  252. {
  253. $r = new Request('GET', '', [
  254. 'ZOO' => 'zoobar',
  255. 'zoo' => ['foobar', 'zoobar'],
  256. ]);
  257. self::assertSame(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders());
  258. self::assertSame('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
  259. }
  260. public function testAddsPortToHeader(): void
  261. {
  262. $r = new Request('GET', 'http://foo.com:8124/bar');
  263. self::assertSame('foo.com:8124', $r->getHeaderLine('host'));
  264. }
  265. public function testAddsPortToHeaderAndReplacePreviousPort(): void
  266. {
  267. $r = new Request('GET', 'http://foo.com:8124/bar');
  268. $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
  269. self::assertSame('foo.com:8125', $r->getHeaderLine('host'));
  270. }
  271. /**
  272. * @dataProvider provideHeaderValuesContainingNotAllowedChars
  273. */
  274. public function testContainsNotAllowedCharsOnHeaderValue(string $value): void
  275. {
  276. $this->expectException(\InvalidArgumentException::class);
  277. $this->expectExceptionMessage(sprintf('"%s" is not valid header value', $value));
  278. $r = new Request(
  279. 'GET',
  280. 'http://foo.com/baz?bar=bam',
  281. [
  282. 'testing' => $value,
  283. ]
  284. );
  285. }
  286. public function provideHeaderValuesContainingNotAllowedChars(): iterable
  287. {
  288. // Explicit tests for newlines as the most common exploit vector.
  289. $tests = [
  290. ["new\nline"],
  291. ["new\r\nline"],
  292. ["new\rline"],
  293. // Line folding is technically allowed, but deprecated.
  294. // We don't support it.
  295. ["new\r\n line"],
  296. ["newline\n"],
  297. ["\nnewline"],
  298. ["newline\r\n"],
  299. ["\r\nnewline"],
  300. ];
  301. for ($i = 0; $i <= 0xFF; ++$i) {
  302. if (\chr($i) == "\t") {
  303. continue;
  304. }
  305. if (\chr($i) == ' ') {
  306. continue;
  307. }
  308. if ($i >= 0x21 && $i <= 0x7E) {
  309. continue;
  310. }
  311. if ($i >= 0x80) {
  312. continue;
  313. }
  314. $tests[] = ['foo'.\chr($i).'bar'];
  315. $tests[] = ['foo'.\chr($i)];
  316. }
  317. return $tests;
  318. }
  319. }