forked from hans/Nominatim
set exception handler by request format, not always HTML
This commit is contained in:
committed by
Marc Tobias Metten
parent
2467e9996e
commit
e4a51e460e
32
lib/DatabaseError.php
Normal file
32
lib/DatabaseError.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
19
lib/lib.php
19
lib/lib.php
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
61
lib/template/error-html.php
Normal file
61
lib/template/error-html.php
Normal 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>
|
||||
11
lib/template/error-json.php
Normal file
11
lib/template/error-json.php
Normal 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));
|
||||
7
lib/template/error-xml.php
Normal file
7
lib/template/error-xml.php
Normal 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>
|
||||
13
test/bdd/api/errors/formats.feature
Normal file
13
test/bdd/api/errors/formats.feature
Normal 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 |
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
44
test/php/Nominatim/DatabaseErrorTest.php
Normal file
44
test/php/Nominatim/DatabaseErrorTest.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Nominatim;
|
||||
|
||||
require_once(CONST_BasePath.'/lib/db.php');
|
||||
require_once(CONST_BasePath.'/lib/Status.php');
|
||||
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
<?php
|
||||
@define('CONST_BasePath', '../..');
|
||||
@define('CONST_Debug', true);
|
||||
@define('CONST_NoAccessControl', false);
|
||||
|
||||
@@ -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)).']';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user