Repository: developmentseed/titiler

Issue:
When using QueryParams other than url, there may be a WMTS XML error
When I use wmts end point and if I only set param url, everything is fine.
http://192.168.148.44:6009/cog/WMTSCapabilities.xml?url=mytifurl

But if I set bidx, QGIS failed to parse capabilities.
http://192.168.148.44:6009/cog/WMTSCapabilities.xml?url=mytifurl&bidx=3&bidx=2&bidx=1
and the reason I guess is the problem of ResourceURL, below is the image in Edge when I request the url.
![1719389091210](https://github.com/developmentseed/titiler/assets/20750506/00025d9f-c44b-473e-8421-f2a6557ed515)
line 51 is ResourceURL, column 297 is ...&bidx

Does this mean & should be encoded?


Discussion:

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index fe24083..a36f3c0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,9 @@
 # Release Notes
 
+## 0.18.4 (2024-06-26)
+
+* fix Tiles URL encoding for WMTSCapabilities XML document
+
 ## 0.18.3 (2024-05-20)
 
 * fix `WMTSCapabilities.xml` response for ArcMap compatibility
diff --git a/src/titiler/core/titiler/core/templates/wmts.xml b/src/titiler/core/titiler/core/templates/wmts.xml
index 7944b0a..ae36a24 100644
--- a/src/titiler/core/titiler/core/templates/wmts.xml
+++ b/src/titiler/core/titiler/core/templates/wmts.xml
@@ -48,7 +48,7 @@
             <TileMatrixSetLink>
                 <TileMatrixSet>{{ tms.id }}</TileMatrixSet>
             </TileMatrixSetLink>
-            <ResourceURL format="{{ media_type }}" resourceType="tile" template="{{ tiles_endpoint }}" />
+            <ResourceURL format="{{ media_type }}" resourceType="tile" template="{{ tiles_endpoint | escape }}" />
         </Layer>
         <TileMatrixSet>
             <ows:Identifier>{{ tms.id }}</ows:Identifier>


Test patch:
diff --git a/src/titiler/core/tests/test_factories.py b/src/titiler/core/tests/test_factories.py
index 0414ae5..6333cb2 100644
--- a/src/titiler/core/tests/test_factories.py
+++ b/src/titiler/core/tests/test_factories.py
@@ -4,6 +4,7 @@ import json
 import os
 import pathlib
 import warnings
+import xml.etree.ElementTree as ET
 from dataclasses import dataclass
 from enum import Enum
 from io import BytesIO
@@ -268,6 +269,19 @@ def test_TilerFactory():
     meta = parse_img(response.content)
     assert meta["driver"] == "WMTS"
     assert meta["crs"] == "EPSG:3857"
+    root = ET.fromstring(response.content)
+    assert root
+
+    response = client.get(
+        f"/WebMercatorQuad/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&bdix=1&rescale=0,1000"
+    )
+    assert response.status_code == 200
+    assert response.headers["content-type"] == "application/xml"
+    meta = parse_img(response.content)
+    assert meta["driver"] == "WMTS"
+    assert meta["crs"] == "EPSG:3857"
+    root = ET.fromstring(response.content)
+    assert root
 
     response = client.get(
         f"/WorldCRS84Quad/WMTSCapabilities.xml?url={DATA_DIR}/cog.tif&minzoom=5&maxzoom=12"
@@ -277,6 +291,8 @@ def test_TilerFactory():
     meta = parse_img(response.content)
     assert meta["driver"] == "WMTS"
     assert str(meta["crs"]) == "OGC:CRS84"
+    root = ET.fromstring(response.content)
+    assert root
 
     response = client.get(f"/bounds?url={DATA_DIR}/cog.tif")
     assert response.status_code == 200