mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
BUG Do not rely on broken curl header size calculation.
Header parsing now takes into account situations like a proxy or redirections. Works around the curl issue. Also fixes the issue when a redirected request would cause a double amount of headers coming out of the parser - it would merrily process anything that's in key:value format even if it was two distinct headers.
This commit is contained in:
parent
eb3cd197ac
commit
7ddd5b57c3
@ -308,7 +308,8 @@ class RestfulService extends ViewableData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the response body and headers from a full curl response
|
* Build the response from raw data. The response could have multiple redirection
|
||||||
|
* and proxy connect headers, so we are only interested in the last header before the body.
|
||||||
*
|
*
|
||||||
* @param curl_handle $ch The curl handle for the request
|
* @param curl_handle $ch The curl handle for the request
|
||||||
* @param string $rawResponse The raw response text
|
* @param string $rawResponse The raw response text
|
||||||
@ -322,15 +323,21 @@ class RestfulService extends ViewableData {
|
|||||||
$curlError = curl_error($ch);
|
$curlError = curl_error($ch);
|
||||||
//normalise the status code
|
//normalise the status code
|
||||||
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500;
|
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500;
|
||||||
//calculate the length of the header and extract it
|
|
||||||
$headerLength = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
// Parse the headers and body from the response.
|
||||||
$rawHeaders = substr($rawResponse, 0, $headerLength);
|
// We cannot rely on CURLINFO_HEADER_SIZE here, it's miscalculated when connecting via
|
||||||
//extract the body
|
// a proxy (see http://sourceforge.net/p/curl/bugs/1204/). This is fixed in curl 7.30.0.
|
||||||
$body = substr($rawResponse, $headerLength);
|
$headerParts = array();
|
||||||
//parse the headers
|
$parts = explode("\r\n\r\n", $rawResponse);
|
||||||
$headers = $this->parseRawHeaders($rawHeaders);
|
while (isset($parts[0])) {
|
||||||
//return the response object
|
if (strpos($parts[0], 'HTTP/')===0) $headerParts[] = array_shift($parts);
|
||||||
return new RestfulService_Response($body, $statusCode, $headers);
|
else break; // We have reached the body.
|
||||||
|
}
|
||||||
|
$lastHeader = array_pop($headerParts);
|
||||||
|
$body = implode("\r\n\r\n", $parts);
|
||||||
|
|
||||||
|
$parsedHeader = $this->parseRawHeaders($lastHeader);
|
||||||
|
return new RestfulService_Response($body, $statusCode, $parsedHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,6 +196,120 @@ class RestfulServiceTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testExtractResponseRedirectionAndProxy() {
|
||||||
|
// This is an example of real raw response for a request via a proxy that gets redirected.
|
||||||
|
$rawResponse =
|
||||||
|
"HTTP/1.0 200 Connection established\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"HTTP/1.1 301 Moved Permanently\r\n" .
|
||||||
|
"Server: nginx\r\n" .
|
||||||
|
"Date: Fri, 20 Sep 2013 01:53:07 GMT\r\n" .
|
||||||
|
"Content-Type: text/html\r\n" .
|
||||||
|
"Content-Length: 178\r\n" .
|
||||||
|
"Connection: keep-alive\r\n" .
|
||||||
|
"Location: https://www.foobar.org.nz/\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"HTTP/1.0 200 Connection established\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"HTTP/1.1 200 OK\r\n" .
|
||||||
|
"Server: nginx\r\n" .
|
||||||
|
"Date: Fri, 20 Sep 2013 01:53:08 GMT\r\n" .
|
||||||
|
"Content-Type: text/html; charset=utf-8\r\n" .
|
||||||
|
"Transfer-Encoding: chunked\r\n" .
|
||||||
|
"Connection: keep-alive\r\n" .
|
||||||
|
"X-Frame-Options: SAMEORIGIN\r\n" .
|
||||||
|
"Cache-Control: no-cache, max-age=0, must-revalidate, no-transform\r\n" .
|
||||||
|
"Vary: Accept-Encoding\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"<!doctype html></html>";
|
||||||
|
|
||||||
|
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
||||||
|
$headerFunction->setAccessible(true);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
$response = $headerFunction->invoke(
|
||||||
|
new RestfulService(Director::absoluteBaseURL(),0),
|
||||||
|
$ch,
|
||||||
|
$rawResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($response->getBody(), '<!doctype html></html>', 'Body is correctly extracted.');
|
||||||
|
$this->assertEquals(
|
||||||
|
$response->getHeaders(),
|
||||||
|
array(
|
||||||
|
'Server' => "nginx",
|
||||||
|
'Date' => "Fri, 20 Sep 2013 01:53:08 GMT",
|
||||||
|
'Content-Type' => "text/html; charset=utf-8",
|
||||||
|
'Transfer-Encoding' => "chunked",
|
||||||
|
'Connection' => "keep-alive",
|
||||||
|
'X-Frame-Options' => "SAMEORIGIN",
|
||||||
|
'Cache-Control' => "no-cache, max-age=0, must-revalidate, no-transform",
|
||||||
|
'Vary' => "Accept-Encoding"
|
||||||
|
),
|
||||||
|
'Only last header is extracted and parsed.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExtractResponseNewlinesInBody() {
|
||||||
|
$rawResponse =
|
||||||
|
"HTTP/1.1 200 OK\r\n" .
|
||||||
|
"Server: nginx\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"<!doctype html>\r\n" .
|
||||||
|
"\r\n" .
|
||||||
|
"</html>";
|
||||||
|
|
||||||
|
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
||||||
|
$headerFunction->setAccessible(true);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
$response = $headerFunction->invoke(
|
||||||
|
new RestfulService(Director::absoluteBaseURL(),0),
|
||||||
|
$ch,
|
||||||
|
$rawResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($response->getBody(), "<!doctype html>\r\n\r\n</html>", 'Body is correctly extracted.');
|
||||||
|
$this->assertEquals($response->getHeaders(), array('Server' => "nginx"), 'Headers are correctly extracted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExtractResponseNoBody() {
|
||||||
|
// For example a response to HEAD request.
|
||||||
|
$rawResponse =
|
||||||
|
"HTTP/1.1 200 OK\r\n" .
|
||||||
|
"Server: nginx";
|
||||||
|
|
||||||
|
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
||||||
|
$headerFunction->setAccessible(true);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
$response = $headerFunction->invoke(
|
||||||
|
new RestfulService(Director::absoluteBaseURL(),0),
|
||||||
|
$ch,
|
||||||
|
$rawResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($response->getBody(), "", 'Body is correctly extracted.');
|
||||||
|
$this->assertEquals($response->getHeaders(), array('Server' => "nginx"), 'Headers are correctly extracted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExtractResponseNoHead() {
|
||||||
|
// Malformed response.
|
||||||
|
$rawResponse = "I am a malformed response";
|
||||||
|
|
||||||
|
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
||||||
|
$headerFunction->setAccessible(true);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
$response = $headerFunction->invoke(
|
||||||
|
new RestfulService(Director::absoluteBaseURL(),0),
|
||||||
|
$ch,
|
||||||
|
$rawResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($response->getBody(), "I am a malformed response", 'Body is correctly extracted.');
|
||||||
|
$this->assertEquals($response->getHeaders(), array(), 'Headers are correctly extracted.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestfulServiceTest_Controller extends Controller implements TestOnly {
|
class RestfulServiceTest_Controller extends Controller implements TestOnly {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user