Merge pull request 'Fix security issues' (#38) from security-fixes into main
All checks were successful
Build (and tag) Images / Build data_handler Image (push) Successful in 41s
Build (and tag) Images / Build web Image (push) Successful in 55s

Reviewed-on: #38
This commit is contained in:
stb 2026-11-15 13:42:03 +01:00
commit 60694442c2
7 changed files with 160 additions and 107 deletions

View file

@ -2,6 +2,18 @@
include "config.php"; include "config.php";
$seqNum = $_SERVER['argv'][1] ?? '';
if (!ctype_digit($seqNum)) {
echo "Invalid sequence number\n";
exit(1);
}
// Quote a value for the generated SQL file. Values come from OSM data and are untrusted.
function sqlString($value) {
global $mysqli;
return "'" . $mysqli->real_escape_string((string) $value) . "'";
}
$elementTypes = array(); $elementTypes = array();
$count = 0; $count = 0;
$countDelete = 0; $countDelete = 0;
@ -91,7 +103,7 @@ function endElement ($parser, $name) {
$mode = ''; $mode = '';
} else if ($name == 'node') { } else if ($name == 'node') {
if ($mode == 'delete') { if ($mode == 'delete') {
$id = $curNodeAttrs['id']; $id = (int) $curNodeAttrs['id'];
if (! $deleteTagStmt->execute()) { if (! $deleteTagStmt->execute()) {
echo "***** Error : Deleting tags $id : ". $deleteTagStmt->error . "\n"; echo "***** Error : Deleting tags $id : ". $deleteTagStmt->error . "\n";
@ -119,7 +131,7 @@ function endElement ($parser, $name) {
$id = $curNodeAttrs['id']; $id = (int) $curNodeAttrs['id'];
if ($mode == 'modify') { if ($mode == 'modify') {
if (! $deleteTagStmt->execute()) { if (! $deleteTagStmt->execute()) {
@ -140,36 +152,36 @@ function endElement ($parser, $name) {
$k = 'lat'; $k = 'lat';
$v = $curNodeAttrs['lat']; $v = $curNodeAttrs['lat'];
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
$k = 'lon'; $k = 'lon';
$v = $curNodeAttrs['lon']; $v = $curNodeAttrs['lon'];
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
$k = 'userid'; $k = 'userid';
$v = $curNodeAttrs['user']; $v = $curNodeAttrs['user'];
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
$k = 'version'; $k = 'version';
$v = $curNodeAttrs['version']; $v = $curNodeAttrs['version'];
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
$k = 'timestamp'; $k = 'timestamp';
$v = $curNodeAttrs['timestamp']; $v = $curNodeAttrs['timestamp'];
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
foreach($curNodeTags as $k => $v) { foreach($curNodeTags as $k => $v) {
$sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, '$k', '$v');\n"; $sql_statement = $sql_statement . "INSERT INTO tag (id, k, v) VALUES ($id, " . sqlString($k) . ", " . sqlString($v) . ");\n";
} }
} else if ($mode == 'modify') { } else if ($mode == 'modify') {
// delete former surveillance nodes that were modified to non-surveillance nodes // delete former surveillance nodes that were modified to non-surveillance nodes
$id = $curNodeAttrs['id']; $id = (int) $curNodeAttrs['id'];
if (! $deleteTagStmt->execute()) { if (! $deleteTagStmt->execute()) {
echo "***** Error : Deleting tags $id for non-surveillance node : ". $deleteTagStmt->error . "\n"; echo "***** Error : Deleting tags $id for non-surveillance node : ". $deleteTagStmt->error . "\n";
@ -208,11 +220,13 @@ while ($data = fread($fp, 4096)) {
} }
} }
$sql="UPDATE sync_state SET v='" . $_SERVER['argv'][1] ."' WHERE k='sequenceNumber';"; $syncStmt = $mysqli->prepare("UPDATE sync_state SET v=? WHERE k='sequenceNumber'");
$mysqli->query($sql); $syncStmt->bind_param('s', $seqNum);
$sql_statement = $sql_statement . "UPDATE sync_state SET v='" . $_SERVER['argv'][1] ."' WHERE k='sequenceNumber';\n"; $syncStmt->execute();
$syncStmt->close();
$sql_statement = $sql_statement . "UPDATE sync_state SET v='" . $seqNum ."' WHERE k='sequenceNumber';\n";
$sql_statement = $sql_statement . "COMMIT;"; $sql_statement = $sql_statement . "COMMIT;";
$sql_file = "/migrations/".$_SERVER['argv'][1].".sql"; $sql_file = "/migrations/".$seqNum.".sql";
file_put_contents($sql_file, $sql_statement); file_put_contents($sql_file, $sql_statement);
$mysqli->close(); $mysqli->close();

View file

@ -2,6 +2,12 @@
include "config.php"; include "config.php";
$seqNum = $_SERVER['argv'][1] ?? '';
if (!ctype_digit($seqNum)) {
echo "Invalid sequence number\n";
exit(1);
}
$elementTypes = array(); $elementTypes = array();
$count = 0; $count = 0;
$countDelete = 0; $countDelete = 0;
@ -281,8 +287,10 @@ while ($data = fread($fp, 4096)) {
} }
} }
$sql="UPDATE sync_state SET v='" . $_SERVER['argv'][1] ."' WHERE k='sequenceNumber';"; $syncStmt = $mysqli->prepare("UPDATE sync_state SET v=? WHERE k='sequenceNumber'");
$mysqli->query($sql); $syncStmt->bind_param('s', $seqNum);
$syncStmt->execute();
$syncStmt->close();
$mysqli->commit(); $mysqli->commit();
printDebug(); printDebug();

View file

@ -1,4 +1,17 @@
FROM docker.io/library/php:apache FROM docker.io/library/php:apache
RUN docker-php-ext-install mysqli bcmath RUN docker-php-ext-install mysqli bcmath
COPY ./www/* /var/www/html
# Use PHP's production defaults (no error output to clients) and hide the PHP version.
RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& printf 'expose_php = Off\n' > "$PHP_INI_DIR/conf.d/zz-hardening.ini"
# Disable directory listings and reduce the server signature.
RUN sed -i 's/^\(\s*Options\) Indexes FollowSymLinks/\1 FollowSymLinks/' /etc/apache2/apache2.conf \
&& sed -i -e 's/^ServerTokens .*/ServerTokens Prod/' -e 's/^ServerSignature .*/ServerSignature Off/' /etc/apache2/conf-available/security.conf
COPY ./www/* /var/www/html
# The web server must not be able to create or modify files in the document root.
RUN chown -R root:root /var/www/html \
&& chmod -R u=rwX,go=rX /var/www/html

View file

@ -4,6 +4,17 @@
define('MAX_POINTS_FOR_QUICKHULL', 3000); define('MAX_POINTS_FOR_QUICKHULL', 3000);
// Fields set by this script; OSM tags with these keys are not passed on.
define('RESERVED_KEYS', array('lat', 'lon', 'id', 'count', 'multi', 'poly'));
define('JSON_FLAGS', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
function exitWithError($message) {
header('Content-type: application/json; charset=utf-8');
echo json_encode(array('error' => $message), JSON_FLAGS);
exit;
}
class OsmPoint { class OsmPoint {
var $id; var $id;
var $lat; var $lat;
@ -179,63 +190,47 @@
|| !array_key_exists('zoom', $_GET) || !array_key_exists('zoom', $_GET)
|| !array_key_exists('width', $_GET) || !array_key_exists('width', $_GET)
|| !array_key_exists('height', $_GET)) { || !array_key_exists('height', $_GET)) {
header('Content-type: application/json'); exitWithError('bbox, zoom, width and height parameters are required. '
$result = '{"error":"bbox, zoom, width and height parameters are required. '
.(array_key_exists('bbox', $_GET) ? '' : 'bbox is empty. ') .(array_key_exists('bbox', $_GET) ? '' : 'bbox is empty. ')
.(array_key_exists('zoom', $_GET) ? '' : 'zoom is empty. ') .(array_key_exists('zoom', $_GET) ? '' : 'zoom is empty. ')
.(array_key_exists('width', $_GET) ? '' : 'width is empty. ') .(array_key_exists('width', $_GET) ? '' : 'width is empty. ')
.(array_key_exists('height', $_GET) ? '' : 'height is empty. ') .(array_key_exists('height', $_GET) ? '' : 'height is empty. '));
.'"}';
echo $result;
exit;
} }
/* Check zoom */ /* Check zoom */
$zoom = $_GET['zoom']; $zoom = $_GET['zoom'];
if ( !is_numeric($zoom) if ( !is_string($zoom)
|| !is_numeric($zoom)
|| intval($zoom) < 1 || intval($zoom) < 1
|| intval($zoom) > 19) { || intval($zoom) > 19) {
header('Content-type: application/json'); exitWithError('unexpected zoom value');
$result = '{"error":"unexpected zoom value : '
.htmlentities($zoom)
.'"}';
echo $result;
exit;
} }
/* Check bounds box --> bbox=boundsSW.lng,boundsSW.lat,boundsNE.lng,boundsNE.lat */ /* Check bounds box --> bbox=boundsSW.lng,boundsSW.lat,boundsNE.lng,boundsNE.lat */
if (!is_string($_GET['bbox'])) {
exitWithError('unexpected bbox value');
}
$bbox = explode(',', $_GET['bbox']); $bbox = explode(',', $_GET['bbox']);
if ( !is_numeric($bbox[0]) || $bbox[0] > 180 // 0 = SW.lng if ( count($bbox) != 4
|| !is_numeric($bbox[0]) || $bbox[0] > 180 // 0 = SW.lng
|| !is_numeric($bbox[2]) || $bbox[2] < -180 // 2 = NE.lng || !is_numeric($bbox[2]) || $bbox[2] < -180 // 2 = NE.lng
|| !is_numeric($bbox[1]) || $bbox[1] < -90 || $bbox[1] > 90 // 1 = SW.lat || !is_numeric($bbox[1]) || $bbox[1] < -90 || $bbox[1] > 90 // 1 = SW.lat
|| !is_numeric($bbox[3]) || $bbox[3] < -90 || $bbox[3] > 90) { // 3 = NE.lat || !is_numeric($bbox[3]) || $bbox[3] < -90 || $bbox[3] > 90) { // 3 = NE.lat
header('Content-type: application/json'); exitWithError('unexpected bbox longitude and latitude values : '
$result = '{"error":"unexpected bbox longitude and latitude values : ' .'lon ['.$bbox[0].', '.$bbox[2].'], '
.'lon ['.htmlentities($bbox[0]).', '.htmlentities($bbox[2]).'], ' .'lat ['.$bbox[1].', '.$bbox[3].']');
.'lat ['.htmlentities($bbox[1]).', '.htmlentities($bbox[3]).']'
.'"}';
echo $result;
exit;
} }
if ($bbox[0] >= $bbox[2]) { // 0 = SW.lng | 2 = NE.lng if ($bbox[0] >= $bbox[2]) { // 0 = SW.lng | 2 = NE.lng
header('Content-type: application/json'); exitWithError('min longitude greater than max longitude : '
$result = '{"error":"min longitude greater than max longitude : ' .'lon ['.$bbox[0].', '.$bbox[2].']');
.'lon ['.htmlentities($bbox[0]).', '.htmlentities($bbox[2]).']'
.'"}';
echo $result;
exit;
} }
if ($bbox[1] >= $bbox[3]) { // 1 = SW.lat | 3 = NE.lat if ($bbox[1] >= $bbox[3]) { // 1 = SW.lat | 3 = NE.lat
header('Content-type: application/json'); exitWithError('min latitude greater than max latitude : '
$result = '{"error":"min latitude greater than max latitude : ' .'lat ['.$bbox[1].', '.$bbox[3].']');
.'lat ['.htmlentities($bbox[1]).', '.htmlentities($bbox[3]).']'
.'"}';
echo $result;
exit;
} }
$lonMin = $bbox[0]; // 0 = SW.lng $lonMin = $bbox[0]; // 0 = SW.lng
@ -247,12 +242,7 @@
$pixelHeight = (int) $_GET['height']; $pixelHeight = (int) $_GET['height'];
if ($pixelWidth == 0 || $pixelHeight == 0) { if ($pixelWidth == 0 || $pixelHeight == 0) {
header('Content-type: application/json'); exitWithError('width or height is null : '.$pixelWidth.'x'.$pixelHeight);
$result = '{"error":"width or height is null : '
.htmlentities($pixelWidth).'x'.htmlentities($pixelHeight)
.'"}';
echo $result;
exit;
} }
$lonWidth = $lonMax - $lonMin; // NE.lng - SW.lng $lonWidth = $lonMax - $lonMin; // NE.lng - SW.lng
@ -268,10 +258,7 @@
/* Connect to database */ /* Connect to database */
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB); $mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
if($mysqli->connect_errno) { if($mysqli->connect_errno) {
header('Content-type: application/json'); exitWithError('error while connecting to db');
$result = '{"error":"error while connecting to db : ' . $mysqli->error . '"}';
echo $result;
exit;
} }
$rqtLonMin = $lonMin; // SW.lng $rqtLonMin = $lonMin; // SW.lng
@ -365,10 +352,7 @@
$stmt->close(); $stmt->close();
} else { } else {
$mysqli->close(); $mysqli->close();
header('Content-type: application/json'); exitWithError('error while querying db');
$result = '{"error":"Error while request : ' . $mysqli->error . '"}';
echo $result;
exit;
} }
/* Unify some clusters if nodes center are near */ /* Unify some clusters if nodes center are near */
@ -380,8 +364,7 @@
} }
} }
$result='['; $result = array();
$separator='';
/* Writing selected points */ /* Writing selected points */
$sql='SELECT k, v $sql='SELECT k, v
@ -393,53 +376,49 @@
$stmt->bind_param('d', $id); $stmt->bind_param('d', $id);
$stmt->bind_result($k, $v); $stmt->bind_result($k, $v);
/* Writing grouped items or not */ /* Writing grouped items or not. All values are passed on unescaped; the client
must escape them before inserting them into HTML. */
foreach($resArray as $val) { foreach($resArray as $val) {
if ($val['count'] > 0) { if ($val['count'] > 0) {
$result = $result.$separator $entry = array(
.'{"lat":"'.($val['latitude'] / $val['count']).'"' 'lat' => (string) ($val['latitude'] / $val['count']),
.',"lon":"'.($val['longitude'] / $val['count']).'"'; 'lon' => (string) ($val['longitude'] / $val['count']));
if ($val['count'] == 1) { if ($val['count'] == 1) {
$id = $val['points'][0]->id; $id = $val['points'][0]->id;
$result = $result.',"id":"'.$id.'"'; $entry['id'] = (string) $id;
$stmt->execute(); $stmt->execute();
while($stmt->fetch()) { while($stmt->fetch()) {
$result = $result.',"'.htmlentities($k).'":"'.htmlentities($v, ENT_COMPAT, 'UTF-8').'"'; if (!in_array($k, RESERVED_KEYS, true)) {
$entry[$k] = (string) $v;
}
} }
} else { } else {
$result = $result $entry['count'] = (string) $val['count'];
.',"count":"'.$val['count'].'"' $entry['multi'] = 'yes';
.',"multi":"yes"' $entry['poly'] = array();
.',"poly":[';
if ($pointsCount < MAX_POINTS_FOR_QUICKHULL) { if ($pointsCount < MAX_POINTS_FOR_QUICKHULL) {
$convexPoly = quickHull($val['points'], $val['count']); $convexPoly = quickHull($val['points'], $val['count']);
$sepPoly = '';
foreach($convexPoly as $point) { foreach($convexPoly as $point) {
$result = $result.$sepPoly.'{' $entry['poly'][] = array(
.'"lat":"'.$point->lat.'"' 'lat' => (string) $point->lat,
.',"id":"'.$point->id.'"' 'id' => (string) $point->id,
.',"lon":"'.$point->lon.'"}'; 'lon' => (string) $point->lon);
$sepPoly=',';
} }
} }
$result=$result.']';
} }
$result = $result.'}'; $result[] = $entry;
$separator=',';
} }
} }
$stmt->close(); $stmt->close();
$result = $result . ']';
$mysqli->close(); $mysqli->close();
header('Content-type: application/json; Charset : utf-8'); header('Content-type: application/json; charset=utf-8');
echo $result; echo json_encode($result, JSON_FLAGS);
?> ?>

View file

@ -129,7 +129,11 @@ function onClick(e) {
function onStateChanged() { function onStateChanged() {
if (ajaxRequest.readyState == 4) { // 0 = UNSENT, 1 = OPENED, 2 = HEADERS_RECEIVED, 3 = LOADING, 4 = DONE if (ajaxRequest.readyState == 4) { // 0 = UNSENT, 1 = OPENED, 2 = HEADERS_RECEIVED, 3 = LOADING, 4 = DONE
if (ajaxRequest.status == 200) { // 200 = OK, 404 = Page not found if (ajaxRequest.status == 200) { // 200 = OK, 404 = Page not found
plotList = eval('(' + ajaxRequest.responseText + ')'); try {
plotList = JSON.parse(ajaxRequest.responseText);
} catch(e) {
return;
}
removeMarkers(); removeMarkers();
for (i=0; i<plotList.length; i++) { for (i=0; i<plotList.length; i++) {
var plotLatLng; var plotLatLng;
@ -421,26 +425,39 @@ function drawCameraFocusDome(plotLatLng, focusRadius, focusColor) {
return(new L.Circle(plotLatLng, focusRadius, { weight:1, color:focusColor, fillOpacity:0.1 })); return(new L.Circle(plotLatLng, focusRadius, { weight:1, color:focusColor, fillOpacity:0.1 }));
} }
// Escape a value for use in HTML text and in double-quoted attributes.
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// Add camera popup to camera marker. // Add camera popup to camera marker.
// All values in plot come unescaped from OSM data and must be escaped.
function addCameraDetailsData(plotMarker, plot) { function addCameraDetailsData(plotMarker, plot) {
var id = escapeHtml(plot.id);
popupDataTable = '<table class="popup-content">' popupDataTable = '<table class="popup-content">'
+ '<tr><td>id</td><td><a href="https://www.openstreetmap.org/node/' + (plot.id) + '">' + (plot.id) + '</a></td></tr>' + '<tr><td>id</td><td><a href="https://www.openstreetmap.org/node/' + encodeURIComponent(plot.id) + '">' + id + '</a></td></tr>'
// + '<tr><td>user osm</td><td>' + (plot.userid) + '</td></tr>' // + '<tr><td>user osm</td><td>' + escapeHtml(plot.userid) + '</td></tr>'
+ '<tr><td>latitude</td><td>' + (plot.lat) + '</td></tr>' + '<tr><td>latitude</td><td>' + escapeHtml(plot.lat) + '</td></tr>'
+ '<tr><td>longitude</td><td>' + (plot.lon) + '</td></tr>'; + '<tr><td>longitude</td><td>' + escapeHtml(plot.lon) + '</td></tr>';
for (x in plot) { for (x in plot) {
if (plot[x] !== '' && x != 'multi' && x != 'id' && x != 'userid' && x != 'lat' && x != 'lon') { if (plot[x] !== '' && x != 'multi' && x != 'id' && x != 'userid' && x != 'lat' && x != 'lon') {
popupDataTable = popupDataTable + '<tr><td>' + x + '</td><td>'; popupDataTable = popupDataTable + '<tr><td>' + escapeHtml(x) + '</td><td>';
var descr = plot[x]; var descr = String(plot[x]);
if (descr.substr(0, 4) == 'http') { if (/^https?:\/\//i.test(descr)) {
var url = escapeHtml(descr);
var suffix = descr.slice(-3).toLowerCase(); var suffix = descr.slice(-3).toLowerCase();
if (suffix == 'jpg' || suffix == 'gif' || suffix == 'png') { if (suffix == 'jpg' || suffix == 'gif' || suffix == 'png') {
popupDataTable = popupDataTable + '<a href="' + descr + '"><img alt="Link" src="' + descr + '" width="200"/></a>'; popupDataTable = popupDataTable + '<a href="' + url + '"><img alt="Link" src="' + url + '" width="200"/></a>';
} else { } else {
popupDataTable = popupDataTable + '<a href="' + descr + '">Link</a>'; popupDataTable = popupDataTable + '<a href="' + url + '">Link</a>';
} }
} else { } else {
popupDataTable = popupDataTable + plot[x]; popupDataTable = popupDataTable + escapeHtml(descr);
} }
popupDataTable = popupDataTable + '</td></tr>'; popupDataTable = popupDataTable + '</td></tr>';
} }

View file

@ -37,15 +37,32 @@ function searchLocation(maxResults) {
searchLimit = 11; searchLimit = 11;
} }
var searchScript = document.createElement('script'); var searchUrl = 'https://nominatim.openstreetmap.org/search?q=' + encodeURIComponent(searchString)
searchScript.src = 'https://nominatim.openstreetmap.org/search.php?q=' + searchString + '&format=json&json_callback=parseResult&limit=' + searchLimit; + '&format=json&limit=' + encodeURIComponent(searchLimit);
document.body.appendChild(searchScript); fetch(searchUrl)
.then(function(response) { return response.json(); })
.then(parseResult)
.catch(function() { parseResult([]); });
} }
} }
// Escape a value for use in HTML text and in double-quoted attributes.
function escapeSearchHtml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function parseResult(response) { function parseResult(response) {
var searchResultDivs = ''; var searchResultDivs = '';
if (!Array.isArray(response)) {
response = [];
}
if (response.length == searchLimit) { if (response.length == searchLimit) {
var lastResultIndex = searchLimit - 1; var lastResultIndex = searchLimit - 1;
var nextSearchLimit = searchLimit + 10; var nextSearchLimit = searchLimit + 10;
@ -67,10 +84,15 @@ function parseResult(response) {
// Show the current 10 search results. // Show the current 10 search results.
for (var i=searchLimit-11; i<lastResultIndex; i++) { for (var i=searchLimit-11; i<lastResultIndex; i++) {
searchResult = response[i]; searchResult = response[i];
var lat = Number(searchResult.lat);
var lon = Number(searchResult.lon);
if (!isFinite(lat) || !isFinite(lon)) {
continue;
}
searchResultDivs += searchResultDivs +=
'<div class="search-result" onClick="gotoLocation(' + searchResult.lat + ', ' + searchResult.lon + ');return false;">' + '<div class="search-result" onClick="gotoLocation(' + lat + ', ' + lon + ');return false;">' +
'<div class="text-medium">' + searchResult.class + ' / ' + searchResult.type + '</div>' + '<div class="text-medium">' + escapeSearchHtml(searchResult.class) + ' / ' + escapeSearchHtml(searchResult.type) + '</div>' +
'<div>' + searchResult.display_name + '</div>' + '<div>' + escapeSearchHtml(searchResult.display_name) + '</div>' +
'</div>'; '</div>';
} }

View file

@ -84,8 +84,8 @@
} else { } else {
$i18n = $i18nCountries; $i18n = $i18nCountries;
$i18nDefault = $i18nCountriesDefault; $i18nDefault = $i18nCountriesDefault;
// Check if $key consists of 2 capital letters. // Check if $key consists of 2 capital letters or is '--' (unknown country).
$key = preg_match('/\b[A-Z]{2}\b/', $valsArray[$i]) === 1 || '--' ? $valsArray[$i] : 'dontfindme'; $key = (preg_match('/^[A-Z]{2}$/', $valsArray[$i]) === 1 || $valsArray[$i] === '--') ? $valsArray[$i] : 'dontfindme';
} }
if (!isValidValue($i18n, $i18nDefault, $key)) { if (!isValidValue($i18n, $i18nDefault, $key)) {
// ignore 'vals' and 'cols' // ignore 'vals' and 'cols'