set exception handler by request format, not always HTML

This commit is contained in:
marc tobias
2018-09-16 17:16:42 +02:00
committed by Marc Tobias Metten
parent 2467e9996e
commit e4a51e460e
20 changed files with 247 additions and 80 deletions

32
lib/DatabaseError.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace Nominatim;
class DatabaseError extends \Exception
{
public function __construct($message, $code = 500, Exception $previous = null, $oSql)
{
parent::__construct($message, $code, $previous);
$this->oSql = $oSql;
}
public function __toString()
{
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function getSqlError()
{
return $this->oSql->getMessage();
}
public function getSqlDebugDump()
{
if (CONST_Debug) {
return var_export($this->oSql, true);
} else {
return $this->oSql->getUserInfo();
}
}
}

View File

@@ -2,6 +2,7 @@
require_once('init.php');
require_once('ParameterParser.php');
require_once('DatabaseError.php');
require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
/***************************************************************************
@@ -15,74 +16,51 @@ function chksql($oSql, $sMsg = 'Database request failed')
{
if (!PEAR::isError($oSql)) return $oSql;
header('HTTP/1.0 500 Internal Server Error');
header('Content-type: text/html; charset=utf-8');
throw new Nominatim\DatabaseError($sMsg, 500, null, $oSql);
}
$sSqlError = $oSql->getMessage();
echo <<<INTERNALFAIL
<html>
<head><title>Internal Server Error</title></head>
<body>
<h1>Internal Server Error</h1>
<p>Nominatim has encountered an internal error while accessing the database.
This may happen because the database is broken or because of a bug in
the software. If you think it is a bug, feel free to report
it over on <a href="https://github.com/openstreetmap/Nominatim/issues">
Github</a>. Please include the URL that caused the problem and the
complete error details below.</p>
<p><b>Message:</b> $sMsg</p>
<p><b>SQL Error:</b> $sSqlError</p>
<p><b>Details:</b> <pre>
INTERNALFAIL;
function userError($sMsg)
{
throw new Exception($sMsg, 400);
}
if (CONST_Debug) {
var_dump($oSql);
function exception_handler_html($exception)
{
http_response_code($exception->getCode());
header('Content-type: text/html; charset=UTF-8');
include(CONST_BasePath.'/lib/template/error-html.php');
}
function exception_handler_json($exception)
{
http_response_code($exception->getCode());
header('Content-type: application/json; charset=utf-8');
include(CONST_BasePath.'/lib/template/error-json.php');
}
function exception_handler_xml($exception)
{
http_response_code($exception->getCode());
header('Content-type: text/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
include(CONST_BasePath.'/lib/template/error-xml.php');
}
function set_exception_handler_by_format($sFormat = 'html')
{
if ($sFormat == 'html') {
set_exception_handler('exception_handler_html');
} elseif ($sFormat == 'xml') {
set_exception_handler('exception_handler_xml');
} else {
echo "<pre>\n".$oSql->getUserInfo().'</pre>';
set_exception_handler('exception_handler_json');
}
echo '</pre></p></body></html>';
exit;
}
function failInternalError($sError, $sSQL = false, $vDumpVar = false)
{
header('HTTP/1.0 500 Internal Server Error');
header('Content-type: text/html; charset=utf-8');
echo '<html><body><h1>Internal Server Error</h1>';
echo '<p>Nominatim has encountered an internal error while processing your request. This is most likely because of a bug in the software.</p>';
echo '<p><b>Details:</b> '.$sError,'</p>';
echo '<p>Feel free to file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
echo 'Please include the error message above and the URL you used.</p>';
if (CONST_Debug) {
echo '<hr><h2>Debugging Information</h2><br>';
if ($sSQL) {
echo '<h3>SQL query</h3><code>'.$sSQL.'</code>';
}
if ($vDumpVar) {
echo '<h3>Result</h3> <code>';
var_dump($vDumpVar);
echo '</code>';
}
}
echo "\n</body></html>\n";
exit;
}
function userError($sError)
{
header('HTTP/1.0 400 Bad Request');
header('Content-type: text/html; charset=utf-8');
echo '<html><body><h1>Bad Request</h1>';
echo '<p>Nominatim has encountered an error with your request.</p>';
echo '<p><b>Details:</b> '.$sError.'</p>';
echo '<p>If you feel this error is incorrect feel file an issue on <a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>. ';
echo 'Please include the error message above and the URL you used.</p>';
echo "\n</body></html>\n";
exit;
}
// set a default
set_exception_handler_by_format();
/***************************************************************************
@@ -96,6 +74,6 @@ if (CONST_NoAccessControl) {
header('Access-Control-Allow-Headers: '.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
}
}
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') exit;
if (CONST_Debug) header('Content-type: text/html; charset=utf-8');

View File

@@ -61,6 +61,13 @@ function byImportance($a, $b)
function javascript_renderData($xVal, $iOptions = 0)
{
$sCallback = isset($_GET['json_callback']) ? $_GET['json_callback'] : '';
if ($sCallback && !preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $sCallback)) {
// Unset, we call javascript_renderData again during exception handling
unset($_GET['json_callback']);
throw new Exception('Invalid json_callback value', 400);
}
$iOptions |= JSON_UNESCAPED_UNICODE;
if (isset($_GET['pretty']) && in_array(strtolower($_GET['pretty']), array('1', 'true'))) {
$iOptions |= JSON_PRETTY_PRINT;
@@ -68,16 +75,12 @@ function javascript_renderData($xVal, $iOptions = 0)
$jsonout = json_encode($xVal, $iOptions);
if (!isset($_GET['json_callback'])) {
if ($sCallback) {
header('Content-Type: application/javascript; charset=UTF-8');
echo $_GET['json_callback'].'('.$jsonout.')';
} else {
header('Content-Type: application/json; charset=UTF-8');
echo $jsonout;
} else {
if (preg_match('/^[$_\p{L}][$_\p{L}\p{Nd}.[\]]*$/u', $_GET['json_callback'])) {
header('Content-Type: application/javascript; charset=UTF-8');
echo $_GET['json_callback'].'('.$jsonout.')';
} else {
header('HTTP/1.0 400 Bad Request');
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
$title = 'Internal Server Error';
if ( $exception->getCode() == 400 ) {
$title = 'Bad Request';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
em { font-weight: bold; font-family: monospace; color: #e00404; background-color: #ffeaea; }
</style>
</head>
<body>
<h1><?php echo $title ?></h1>
<?php if (get_class($exception) == 'Nominatim\DatabaseError') { ?>
<p>Nominatim has encountered an internal error while accessing the database.
This may happen because the database is broken or because of a bug in
the software.</p>
<?php } else { ?>
<p>Nominatim has encountered an error with your request.</p>
<?php } ?>
<h3>Details</h3>
Uncaught exception <em><?php echo get_class($exception) ?></em>
with message <em><?php echo $exception->getMessage() ?></em>
<?php if (CONST_Debug) { ?>
<br>
thrown in <em><?php $exception->getFile() . '('. $exception->getLine() . ')' ?></em>.
<?php if (get_class($exception) == 'Nominatim\DatabaseError') { ?>
<h3>SQL Error</h3>
<em><?php echo $exception->getSqlError() ?></em>
<pre><?php echo $exception->getSqlDebugDump() ?></pre>
<?php } ?>
<h3>Stack trace</h3>
<pre><?php echo $exception->getTraceAsString() ?></pre>
<?php } ?>
<p>
If you feel this error is incorrect feel file an issue on
<a href="https://github.com/openstreetmap/Nominatim/issues">Github</a>.
Please include the error message above and the URL you used.
</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<?php
$error = array(
'code' => $exception->getCode(),
'message' => $exception->getMessage()
);
if (CONST_Debug) {
$error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
}
echo javascript_renderData(array('error' => $error));

View File

@@ -0,0 +1,7 @@
<error>
<code><?php echo $exception->getCode() ?></code>
<message><?php echo $exception->getMessage() ?></message>
<?php if (CONST_Debug) { ?>
<details><?php echo $exception->getFile() . '('. $exception->getLine() . ')' ?></details>
<?php } ?>
</error>

View File

@@ -0,0 +1,13 @@
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for errors in various response formats.
Scenario Outline: Force error by providing too many ids
When sending <format> lookup query for N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18,N19,N20,N21,N22,N23,N24,N25,N26,N27,N28,N29,N30,N31,N32,N33,N34,N35,N36,N37,N38,N39,N40,N41,N42,N43,N44,N45,N46,N47,N48,N49,N50,N51
Then a <format> user error is returned
Examples:
| format |
| xml |
| json |
| geojson |

View File

@@ -1,6 +1,6 @@
@APIDB
Feature: Places by osm_type and osm_id Tests
Simple tests for internal server errors and response format.
Simple tests for response format.
Scenario Outline: address lookup for existing node, way, relation
When sending <format> lookup query for N3284625766,W6065798,,R123924,X99,N0

View File

@@ -194,7 +194,7 @@ Feature: Simple Tests
When sending json search query "Tokyo"
| param | value |
|json_callback | <data> |
Then a HTTP 400 is returned
Then a json user error is returned
Examples:
| data |

View File

@@ -494,6 +494,18 @@ def step_impl(context, fmt):
context.execute_steps("Then a HTTP 200 is returned")
eq_(context.response.format, fmt)
@then(u'a (?P<fmt>\w+) user error is returned')
def check_page_error(context, fmt):
context.execute_steps("Then a HTTP 400 is returned")
eq_(context.response.format, fmt)
if fmt == 'html':
assert_is_not_none(re.search(r'<html( |>).+</html>', context.response.page, re.DOTALL))
elif fmt == 'xml':
assert_is_not_none(re.search(r'<error>.+</error>', context.response.page, re.DOTALL))
else:
assert_is_not_none(re.search(r'({"error":)', context.response.page, re.DOTALL))
@then(u'result header contains')
def check_header_attr(context):
for line in context.table:

View File

@@ -2,14 +2,10 @@
namespace Nominatim;
require_once(CONST_BasePath.'/lib/init-website.php');
require_once(CONST_BasePath.'/lib/AddressDetails.php');
function chksql($oSql, $sMsg = 'Database request failed')
{
return $oSql;
}
class AddressDetailsTest extends \PHPUnit\Framework\TestCase
{

View File

@@ -0,0 +1,44 @@
<?php
namespace Nominatim;
require_once(CONST_BasePath.'/lib/init-website.php');
require_once(CONST_BasePath.'/lib/DatabaseError.php');
class DatabaseErrorTest extends \PHPUnit\Framework\TestCase
{
public function testSqlMessage()
{
$oSqlStub = $this->getMockBuilder(\DB_Error::class)
->setMethods(array('getMessage'))
->getMock();
$oSqlStub->method('getMessage')
->willReturn('Unknown table.');
$oErr = new DatabaseError('Sql error', 123, null, $oSqlStub);
$this->assertEquals('Sql error', $oErr->getMessage());
$this->assertEquals(123, $oErr->getCode());
$this->assertEquals('Unknown table.', $oErr->getSqlError());
// causes a circular reference warning during dump
// $this->assertRegExp('/Mock_DB_Error/', $oErr->getSqlDebugDump());
}
public function testSqlObjectDump()
{
$oErr = new DatabaseError('Sql error', 123, null, array('one' => 'two'));
$this->assertRegExp('/two/', $oErr->getSqlDebugDump());
}
public function testChksqlThrows()
{
$this->expectException(DatabaseError::class);
$this->expectExceptionMessage('My custom error message');
$this->expectExceptionCode(500);
$oDB = new \DB_Error;
$this->assertEquals(false, chksql($oDB, 'My custom error message'));
}
}

View File

@@ -2,6 +2,9 @@
namespace Nominatim;
require_once(CONST_BasePath.'/lib/lib.php');
require_once(CONST_BasePath.'/lib/ClassTypes.php');
class LibTest extends \PHPUnit\Framework\TestCase
{

View File

@@ -2,6 +2,7 @@
namespace Nominatim;
require_once(CONST_BasePath.'/lib/db.php');
require_once(CONST_BasePath.'/lib/Status.php');

View File

@@ -2,8 +2,8 @@
namespace Nominatim;
require_once(CONST_BasePath.'/lib/db.php');
require_once(CONST_BasePath.'/lib/cmd.php');
// require_once(CONST_BasePath.'/lib/db.php');
// require_once(CONST_BasePath.'/lib/cmd.php');
require_once(CONST_BasePath.'/lib/TokenList.php');

View File

@@ -1,2 +1,4 @@
<?php
@define('CONST_BasePath', '../..');
@define('CONST_Debug', true);
@define('CONST_NoAccessControl', false);

View File

@@ -11,6 +11,7 @@ ini_set('memory_limit', '200M');
$oParams = new Nominatim\ParameterParser();
$sOutputFormat = $oParams->getSet('format', array('html', 'json'), 'html');
set_exception_handler_by_format($sOutputFormat);
$aLangPrefOrder = $oParams->getPreferredLanguages();
$sLanguagePrefArraySQL = 'ARRAY['.join(',', array_map('getDBQuoted', $aLangPrefOrder)).']';

View File

@@ -12,6 +12,7 @@ $oParams = new Nominatim\ParameterParser();
// Format for output
$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'geojson'), 'xml');
set_exception_handler_by_format($sOutputFormat);
// Preferred language
$aLangPrefOrder = $oParams->getPreferredLanguages();

View File

@@ -13,6 +13,7 @@ $oParams = new Nominatim\ParameterParser();
// Format for output
$sOutputFormat = $oParams->getSet('format', array('html', 'xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'xml');
set_exception_handler_by_format($sOutputFormat);
// Preferred language
$aLangPrefOrder = $oParams->getPreferredLanguages();

View File

@@ -27,6 +27,7 @@ if (CONST_Search_ReversePlanForAll
// Format for output
$sOutputFormat = $oParams->getSet('format', array('html', 'xml', 'json', 'jsonv2', 'geojson', 'geocodejson'), 'html');
set_exception_handler_by_format($sOutputFormat);
$sForcedGeometry = ($sOutputFormat == 'html') ? 'geojson' : null;
$oGeocode->loadParamArray($oParams, $sForcedGeometry);