Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 65d19bd

Browse filesBrowse files
committed
bug #45998 [HttpClient] Fix sending content-length when streaming the body (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [HttpClient] Fix sending content-length when streaming the body | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #45965 | License | MIT | Doc PR | - Follows #45906 and previous PRs on the topic. This PR partly reverts previous changes but keeps tests added to cover how we manage content-related headers when redirecting. Relying on curl doesn't work in all cases, so we need to manage them on our own. Commits ------- ee57696 [HttpClient] Fix sending content-length when streaming the body
2 parents 2607b66 + ee57696 commit 65d19bd
Copy full SHA for 65d19bd

File tree

5 files changed

+30
-19
lines changed
Filter options

5 files changed

+30
-19
lines changed

‎src/Symfony/Component/HttpClient/CurlHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+14-11Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,7 @@ public function request(string $method, string $url, array $options = []): Respo
202202
$options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided
203203
}
204204

205-
$hasContentLength = isset($options['normalized_headers']['content-length'][0]);
206-
207-
foreach ($options['headers'] as $i => $header) {
208-
if ($hasContentLength && 0 === stripos($header, 'Content-Length:')) {
209-
// Let curl handle Content-Length headers
210-
unset($options['headers'][$i]);
211-
continue;
212-
}
205+
foreach ($options['headers'] as $header) {
213206
if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) {
214207
// curl requires a special syntax to send empty headers
215208
$curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2);
@@ -236,7 +229,7 @@ public function request(string $method, string $url, array $options = []): Respo
236229
};
237230
}
238231

239-
if ($hasContentLength) {
232+
if (isset($options['normalized_headers']['content-length'][0])) {
240233
$curlopts[\CURLOPT_INFILESIZE] = substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: '));
241234
} elseif (!isset($options['normalized_headers']['transfer-encoding'])) {
242235
$curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding: chunked'; // Enable chunked request bodies
@@ -249,7 +242,7 @@ public function request(string $method, string $url, array $options = []): Respo
249242
$curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded';
250243
}
251244
}
252-
} elseif ('' !== $body || 'POST' === $method || $hasContentLength) {
245+
} elseif ('' !== $body || 'POST' === $method) {
253246
$curlopts[\CURLOPT_POSTFIELDS] = $body;
254247
}
255248

@@ -406,16 +399,26 @@ private static function createRedirectResolver(array $options, string $host): \C
406399
}
407400
}
408401

409-
return static function ($ch, string $location) use ($redirectHeaders) {
402+
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders) {
410403
try {
411404
$location = self::parseUrl($location);
412405
} catch (InvalidArgumentException $e) {
413406
return null;
414407
}
415408

409+
if ($noContent && $redirectHeaders) {
410+
$filterContentHeaders = static function ($h) {
411+
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
412+
};
413+
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
414+
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
415+
}
416+
416417
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
417418
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
418419
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
420+
} elseif ($noContent && $redirectHeaders) {
421+
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
419422
}
420423

421424
$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));

‎src/Symfony/Component/HttpClient/HttpClientTrait.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/HttpClientTrait.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
9292
&& (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16)
9393
&& ('' !== $h || '' !== $options['body'])
9494
) {
95-
if (isset($options['normalized_headers']['transfer-encoding'])) {
95+
if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) {
9696
unset($options['normalized_headers']['transfer-encoding']);
9797
$options['body'] = self::dechunk($options['body']);
9898
}

‎src/Symfony/Component/HttpClient/NativeHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/NativeHttpClient.php
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function request(string $method, string $url, array $options = []): Respo
8585

8686
$options['body'] = self::getBodyAsString($options['body']);
8787

88-
if (isset($options['normalized_headers']['transfer-encoding'])) {
88+
if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) {
8989
unset($options['normalized_headers']['transfer-encoding']);
9090
$options['headers'] = array_merge(...array_values($options['normalized_headers']));
9191
$options['body'] = self::dechunk($options['body']);
@@ -397,7 +397,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar
397397
}
398398
}
399399

400-
return static function (NativeClientState $multi, ?string $location, $context) use ($redirectHeaders, $proxy, $noProxy, &$info, $maxRedirects, $onProgress): ?string {
400+
return static function (NativeClientState $multi, ?string $location, $context) use (&$redirectHeaders, $proxy, $noProxy, &$info, $maxRedirects, $onProgress): ?string {
401401
if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) {
402402
$info['redirect_url'] = null;
403403

@@ -431,7 +431,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar
431431
$info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET';
432432
$options['content'] = '';
433433
$filterContentHeaders = static function ($h) {
434-
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:');
434+
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
435435
};
436436
$options['header'] = array_filter($options['header'], $filterContentHeaders);
437437
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);

‎src/Symfony/Component/HttpClient/Response/CurlResponse.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/CurlResponse.php
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
361361
if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
362362
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
363363
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
364-
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
365364
curl_setopt($ch, \CURLOPT_POSTFIELDS, '');
366-
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
367365
}
368366
}
369367

@@ -382,7 +380,12 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
382380
$info['redirect_url'] = null;
383381

384382
if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
385-
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location)) {
383+
if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) {
384+
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
385+
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
386+
}
387+
388+
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
386389
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
387390
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
388391
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);

‎src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,16 @@ public function test304()
332332
$this->assertSame('', $response->getContent(false));
333333
}
334334

335-
public function testRedirects()
335+
/**
336+
* @testWith [[]]
337+
* [["Content-Length: 7"]]
338+
*/
339+
public function testRedirects(array $headers = [])
336340
{
337341
$client = $this->getHttpClient(__FUNCTION__);
338342
$response = $client->request('POST', 'http://localhost:8057/301', [
339343
'auth_basic' => 'foo:bar',
344+
'headers' => $headers,
340345
'body' => function () {
341346
yield 'foo=bar';
342347
},

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.