Skip to content

Commit

Permalink
Protect RemoteLRS from exceptions in sendRequest
Browse files Browse the repository at this point in the history
* This will cause exceptions from fopen and the one we cause for invalid
  status codes to not cause fatal error but instead return an LRS
  response with 'success' set to false and with a message in 'content',
  and in the fopen case allow detection that the request never actually
  made it to the LRS as 'response' itself will be empty
* Add error handling for fopen to provide a more meaningful message when
  PHP is setup without track_errors being on (IOW capture the E_WARNING
  that is generated)
  • Loading branch information
brianjmiller committed Mar 30, 2016
1 parent 3f8e1e0 commit d634afb
Showing 1 changed file with 53 additions and 30 deletions.
83 changes: 53 additions & 30 deletions src/RemoteLRS.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,42 +117,65 @@ protected function sendRequest($method, $resource) {
}
}

$context = stream_context_create(array( 'http' => $http ));
$fp = fopen($url, 'rb', false, $context);
if (! $fp) {
throw new \Exception("Request failed: $php_errormsg");
}

$metadata = stream_get_meta_data($fp);
$content = stream_get_contents($fp);

$response = $this->_parseMetadata($metadata, $options);
$success = false;

//
// keep a copy of the raw content, the methods expecting
// an LRS response may handle the content, for instance
// querying statements takes the returned value and converts
// it to Statement objects (really StatementsResult but who
// is counting), etc. but a user may want the original raw
// returned content untouched, do the same with the metadata
// because it feels like a good practice
// errors from fopen are reported to PHP as E_WARNING which prevents us
// from getting a reasonable message, so set an error handler here for
// the immediate call to turn it into an exception, and then restore
// normal handling
//
$response['_content'] = $content;
$response['_metadata'] = $metadata;
set_error_handler(
function ($errno, $errstr, $errfile, $errline, array $errcontext) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
);

//
// Content-Type won't be set in the case of a 204 (and potentially others)
//
if (isset($response['headers']['contentType']) && $response['headers']['contentType'] === "multipart/mixed") {
$content = $this->_parseMultipart($response['headers']['contentTypeBoundary'], $content);
}
try {
$context = stream_context_create(array( 'http' => $http ));
$fp = fopen($url, 'rb', false, $context);

$success = false;
if (($response['status'] >= 200 && $response['status'] < 300) || ($response['status'] === 404 && isset($options['ignore404']) && $options['ignore404'])) {
$success = true;
if (! $fp) {
$content = "Request failed: $php_errormsg";
}
}
catch (ErrorException $ex) {
$content = "Request failed: $ex";
}
elseif ($response['status'] >= 300 && $response['status'] < 400) {
throw new \Exception("Unsupported status code: " . $response['status'] . " (LRS should not redirect)");

restore_error_handler();

if ($fp) {
$metadata = stream_get_meta_data($fp);
$content = stream_get_contents($fp);

$response = $this->_parseMetadata($metadata, $options);

//
// keep a copy of the raw content, the methods expecting
// an LRS response may handle the content, for instance
// querying statements takes the returned value and converts
// it to Statement objects (really StatementsResult but who
// is counting), etc. but a user may want the original raw
// returned content untouched, do the same with the metadata
// because it feels like a good practice
//
$response['_content'] = $content;
$response['_metadata'] = $metadata;

//
// Content-Type won't be set in the case of a 204 (and potentially others)
//
if (isset($response['headers']['contentType']) && $response['headers']['contentType'] === "multipart/mixed") {
$content = $this->_parseMultipart($response['headers']['contentTypeBoundary'], $content);
}

if (($response['status'] >= 200 && $response['status'] < 300) || ($response['status'] === 404 && isset($options['ignore404']) && $options['ignore404'])) {
$success = true;
}
elseif ($response['status'] >= 300 && $response['status'] < 400) {
$content = "Unsupported status code: " . $response['status'] . " (LRS should not redirect)";
}
}

return new LRSResponse($success, $content, $response);
Expand Down

0 comments on commit d634afb

Please sign in to comment.