From 6ef1d9b87ffc3198eb3ca54c69cbe7334d86d2bc Mon Sep 17 00:00:00 2001 From: Josh Vincent Date: Tue, 6 Jan 2026 13:24:17 -0700 Subject: [PATCH 1/3] Escapes XML in GPX route data Ensures route names and descriptions are properly escaped when generating GPX files, preventing potential XML parsing issues. Sanitizes the filename to be downloaded. --- src/features/route/RoutePopup.jsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/features/route/RoutePopup.jsx b/src/features/route/RoutePopup.jsx index 09cc7b068..dddf25e22 100644 --- a/src/features/route/RoutePopup.jsx +++ b/src/features/route/RoutePopup.jsx @@ -388,6 +388,18 @@ export function RoutePopup({ end, inline = false, ...props }) { } function DownloadRouteGPX({ route }) { + const escapeXml = (value = '') => + String(value).replace(/[<>&'"]/g, (c) => ({ + '<': '<', + '>': '>', + '&': '&', + "'": ''', + '"': '"', + }[c])) + + const sanitizeFilename = (name = '') => + String(name).replace(/[\/:*?"<>|]/g, '').slice(0, 200) || 'route' + const GPXContent = React.useMemo(() => { if (!route.waypoints.length) { return null @@ -396,8 +408,8 @@ function DownloadRouteGPX({ route }) { return ` - ${route.name} - ${route.description} + ${escapeXml(route.name)} + ${escapeXml(route.description)} ${route.waypoints .map( (waypoint) => @@ -417,7 +429,7 @@ function DownloadRouteGPX({ route }) { href={`data:application/gpx;charset=utf-8,${encodeURIComponent( GPXContent, )}`} - download={`${route.name}.gpx`} + download={`${sanitizeFilename(route.name)}.gpx`} size="small" style={{ color: 'inherit' }} > From 26670e6a830239211f4e2972f61a41532845995f Mon Sep 17 00:00:00 2001 From: Josh Vincent Date: Tue, 6 Jan 2026 15:07:56 -0700 Subject: [PATCH 2/3] Prettier --- src/features/route/RoutePopup.jsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/features/route/RoutePopup.jsx b/src/features/route/RoutePopup.jsx index dddf25e22..f750619a5 100644 --- a/src/features/route/RoutePopup.jsx +++ b/src/features/route/RoutePopup.jsx @@ -389,16 +389,22 @@ export function RoutePopup({ end, inline = false, ...props }) { function DownloadRouteGPX({ route }) { const escapeXml = (value = '') => - String(value).replace(/[<>&'"]/g, (c) => ({ - '<': '<', - '>': '>', - '&': '&', - "'": ''', - '"': '"', - }[c])) + String(value).replace( + /[<>&'"]/g, + (c) => + ({ + '<': '<', + '>': '>', + '&': '&', + "'": ''', + '"': '"', + })[c], + ) const sanitizeFilename = (name = '') => - String(name).replace(/[\/:*?"<>|]/g, '').slice(0, 200) || 'route' + String(name) + .replace(/[\/:*?"<>|]/g, '') + .slice(0, 200) || 'route' const GPXContent = React.useMemo(() => { if (!route.waypoints.length) { From 976b2ee1d6ecdaffc61dd6b7f4e4cee8458763d0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 8 Jan 2026 15:42:52 -0800 Subject: [PATCH 3/3] fix: filename sanitizer to also strip backslashes for Windows safety --- src/features/route/RoutePopup.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/route/RoutePopup.jsx b/src/features/route/RoutePopup.jsx index f750619a5..6f9c4e934 100644 --- a/src/features/route/RoutePopup.jsx +++ b/src/features/route/RoutePopup.jsx @@ -403,7 +403,7 @@ function DownloadRouteGPX({ route }) { const sanitizeFilename = (name = '') => String(name) - .replace(/[\/:*?"<>|]/g, '') + .replace(/[\\/:*?"<>|]/g, '') .slice(0, 200) || 'route' const GPXContent = React.useMemo(() => {