Skip to content

Commit

Permalink
[HttpClient] Fix sending content-length when streaming the body
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed Apr 11, 2022
1 parent c66fc3b commit 5432a7e
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 18 deletions.
25 changes: 14 additions & 11 deletions CurlHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,7 @@ public function request(string $method, string $url, array $options = []): Respo
$options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided
}

$hasContentLength = isset($options['normalized_headers']['content-length'][0]);

foreach ($options['headers'] as $i => $header) {
if ($hasContentLength && 0 === stripos($header, 'Content-Length:')) {
// Let curl handle Content-Length headers
unset($options['headers'][$i]);
continue;
}
foreach ($options['headers'] as $header) {
if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) {
// curl requires a special syntax to send empty headers
$curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2);
Expand All @@ -236,7 +229,7 @@ public function request(string $method, string $url, array $options = []): Respo
};
}

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

Expand Down Expand Up @@ -406,16 +399,26 @@ private static function createRedirectResolver(array $options, string $host): \C
}
}

return static function ($ch, string $location) use ($redirectHeaders) {
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders) {
try {
$location = self::parseUrl($location);
} catch (InvalidArgumentException $e) {
return null;
}

if ($noContent && $redirectHeaders) {
$filterContentHeaders = static function ($h) {
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
};
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
}

if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
} elseif ($noContent && $redirectHeaders) {
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
}

$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
Expand Down
2 changes: 1 addition & 1 deletion HttpClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
&& (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16)
&& ('' !== $h || '' !== $options['body'])
) {
if (isset($options['normalized_headers']['transfer-encoding'])) {
if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) {
unset($options['normalized_headers']['transfer-encoding']);
$options['body'] = self::dechunk($options['body']);
}
Expand Down
6 changes: 3 additions & 3 deletions NativeHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function request(string $method, string $url, array $options = []): Respo

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

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

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

Expand Down Expand Up @@ -431,7 +431,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar
$info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET';
$options['content'] = '';
$filterContentHeaders = static function ($h) {
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:');
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
};
$options['header'] = array_filter($options['header'], $filterContentHeaders);
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
Expand Down
9 changes: 6 additions & 3 deletions Response/CurlResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
curl_setopt($ch, \CURLOPT_POSTFIELDS, '');
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
}
}

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

if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location)) {
if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) {
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
}

if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
Expand Down

0 comments on commit 5432a7e

Please sign in to comment.