--- /dev/null
+<?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();
+ }
+ }
+}
require_once('init.php');
require_once('ParameterParser.php');
+require_once('DatabaseError.php');
require_once(CONST_Debug ? 'DebugHtml.php' : 'DebugNone.php');
/***************************************************************************
{
if (!PEAR::isError($oSql)) return $oSql;
- header('HTTP/1.0 500 Internal Server Error');
- header('Content-type: text/html; charset=utf-8');
-
- $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;
-
- if (CONST_Debug) {
- var_dump($oSql);
- } else {
- echo "<pre>\n".$oSql->getUserInfo().'</pre>';
- }
+ throw new Nominatim\DatabaseError($sMsg, 500, null, $oSql);
+}
+
- echo '</pre></p></body></html>';
- exit;
+function userError($sMsg)
+{
+ throw new Exception($sMsg, 400);
}
-function failInternalError($sError, $sSQL = false, $vDumpVar = false)
+
+function exception_handler_html($exception)
{
- 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;
+ 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 userError($sError)
+function exception_handler_xml($exception)
{
- 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;
+ 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 {
+ set_exception_handler('exception_handler_json');
+ }
}
+// set a default
+set_exception_handler_by_format();
/***************************************************************************
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');
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;
$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');
- }
}
}
--- /dev/null
+<?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>
--- /dev/null
+<?php
+ $error = array(
+ 'code' => $exception->getCode(),
+ 'message' => $exception->getMessage()
+ );
+
+ if (CONST_Debug) {
+ $error['details'] = $exception->getFile() . '('. $exception->getLine() . ')';
+ }
+
+ echo javascript_renderData(array('error' => $error));
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+@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 |
@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
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 |
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:
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
{
--- /dev/null
+<?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'));
+ }
+}
namespace Nominatim;
+require_once(CONST_BasePath.'/lib/lib.php');
+require_once(CONST_BasePath.'/lib/ClassTypes.php');
+
class LibTest extends \PHPUnit\Framework\TestCase
{
namespace Nominatim;
+require_once(CONST_BasePath.'/lib/db.php');
require_once(CONST_BasePath.'/lib/Status.php');
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');
<?php
@define('CONST_BasePath', '../..');
+ @define('CONST_Debug', true);
+ @define('CONST_NoAccessControl', false);
$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)).']';
// Format for output
$sOutputFormat = $oParams->getSet('format', array('xml', 'json', 'geojson'), 'xml');
+set_exception_handler_by_format($sOutputFormat);
// Preferred language
$aLangPrefOrder = $oParams->getPreferredLanguages();
// 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();
// 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);