Skip to content

Commit 5432a7e

Browse files
[HttpClient] Fix sending content-length when streaming the body
1 parent c66fc3b commit 5432a7e

File tree

4 files changed

+24
-18
lines changed

4 files changed

+24
-18
lines changed

CurlHttpClient.php

Lines 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));

HttpClientTrait.php

Lines 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
}

NativeHttpClient.php

Lines 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);

Response/CurlResponse.php

Lines 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']);

0 commit comments

Comments
 (0)