From 67925c72e027a1c2cdad55bf10bfef28bc938ddb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 6 Apr 2026 17:25:14 +0000
Subject: [PATCH 1/2] Initial plan
From 14f1a7c1aea292d994ce096b021eda9e688638c3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 6 Apr 2026 17:28:11 +0000
Subject: [PATCH 2/2] Add sanitizeInput(), improve normalizeStreet(), update
app.js and index.html
Agent-Logs-Url: https://github.com/NickHamby/PotholeDodgerV2/sessions/be13c8ab-ca4e-4894-a848-cc4155488651
Co-authored-by: NickHamby <271342652+NickHamby@users.noreply.github.com>
---
web/index.html | 1 +
web/js/app.js | 6 +++---
web/js/hazards.js | 35 +++++++++++++++++++++++++++--------
web/js/sanitize.js | 28 ++++++++++++++++++++++++++++
4 files changed, 59 insertions(+), 11 deletions(-)
create mode 100644 web/js/sanitize.js
diff --git a/web/index.html b/web/index.html
index 8f6bd86..024aade 100644
--- a/web/index.html
+++ b/web/index.html
@@ -31,6 +31,7 @@
Pothole Dodger
+
diff --git a/web/js/app.js b/web/js/app.js
index 47b336b..6f9af54 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -13,7 +13,7 @@ function attachAutocomplete(inputEl) {
inputEl.addEventListener('input', function () {
clearTimeout(debounceTimer);
- const query = inputEl.value.trim();
+ const query = sanitizeInput(inputEl.value);
if (query.length < 3) {
hideList();
@@ -72,8 +72,8 @@ document.addEventListener('DOMContentLoaded', function () {
}
async function run() {
- const origin = document.getElementById('origin').value.trim();
- const destination = document.getElementById('destination').value.trim();
+ const origin = sanitizeInput(document.getElementById('origin').value);
+ const destination = sanitizeInput(document.getElementById('destination').value);
if (!origin || !destination) {
setStatus('Please enter both an origin and destination.');
diff --git a/web/js/hazards.js b/web/js/hazards.js
index 48cbe51..d499544 100644
--- a/web/js/hazards.js
+++ b/web/js/hazards.js
@@ -1,6 +1,6 @@
// hazards.js — loads hazard data and filters it to streets matching the route
-const ABBR_MAP = [
+const STREET_TYPE_ABBRS = [
[/\bSt\b/g, 'Street'],
[/\bAve\b/g, 'Avenue'],
[/\bBlvd\b/g, 'Boulevard'],
@@ -9,21 +9,40 @@ const ABBR_MAP = [
[/\bPkwy\b/g, 'Parkway'],
[/\bLn\b/g, 'Lane'],
[/\bCt\b/g, 'Court'],
- [/\bW\b/g, 'West'],
- [/\bE\b/g, 'East'],
- [/\bN\b/g, 'North'],
- [/\bS\b/g, 'South'],
];
+const DIRECTIONAL_EXPAND = {
+ W: 'West',
+ E: 'East',
+ N: 'North',
+ S: 'South',
+};
+
function normalizeStreet(str) {
let s = str;
- for (const [pattern, replacement] of ABBR_MAP) {
+
+ // 1. Strip trailing ZIP code (5 digits, optionally preceded by comma and/or space)
+ s = s.replace(/[,\s]+\d{5}\s*$/, '');
+
+ // 2. Expand street type abbreviations
+ for (const [pattern, replacement] of STREET_TYPE_ABBRS) {
s = s.replace(pattern, replacement);
}
- // Strip leading house numbers (including fractional like "8 1/2") but preserve
- // street numbers that are part of the name (e.g. "1st Avenue")
+
+ // 3. Expand directionals at start of string (prefix directional)
+ s = s.replace(/^(W|E|N|S)\b\s*/, (_, d) => DIRECTIONAL_EXPAND[d] + ' ');
+
+ // 4. Expand directionals at end of string (suffix directional)
+ s = s.replace(/\s+(W|E|N|S)$/, (_, d) => ' ' + DIRECTIONAL_EXPAND[d]);
+
+ // 5. Strip leading house numbers (including fractional like "8 1/2")
s = s.replace(/^\d+(\s+\d+\/\d+)?\s+/, '');
+
+ // 6. Strip remaining punctuation
s = s.replace(/[^a-zA-Z0-9\s]/g, '');
+
+ // 7. Collapse whitespace and trim, lowercase
+ s = s.replace(/\s{2,}/g, ' ');
return s.trim().toLowerCase();
}
diff --git a/web/js/sanitize.js b/web/js/sanitize.js
new file mode 100644
index 0000000..7ab3395
--- /dev/null
+++ b/web/js/sanitize.js
@@ -0,0 +1,28 @@
+// sanitize.js — sanitizes raw user input before geocoding or internal matching
+
+/**
+ * Sanitizes a raw user-supplied address string for use as a Nominatim query.
+ *
+ * - Trims leading/trailing whitespace
+ * - Collapses multiple spaces into one
+ * - Strips parenthetical context (e.g. "(near the park)")
+ * - Removes characters that break queries: # " ' . ; : ! ? @ ^ * [ ] { } | \ ~ ` = + < > % & _
+ * - Preserves commas (Nominatim field separators), hyphens (address ranges), slashes (fractions)
+ * - Does NOT expand abbreviations (leave that to normalizeStreet in hazards.js)
+ * - Does NOT strip ZIP codes (Nominatim handles them; strip only inside normalizeStreet)
+ * - Must never be called on coordinate (lat/lng) values
+ *
+ * @param {string} str Raw input string
+ * @returns {string} Sanitized string safe for Nominatim queries
+ */
+function sanitizeInput(str) {
+ if (typeof str !== 'string') return '';
+ let s = str;
+ // Remove parenthetical content
+ s = s.replace(/\([^)]*\)/g, '');
+ // Keep: letters, digits, spaces, commas, hyphens, forward slashes
+ s = s.replace(/[^a-zA-Z0-9\s,\-\/]/g, '');
+ // Collapse multiple spaces
+ s = s.replace(/\s{2,}/g, ' ');
+ return s.trim();
+}