&item : constItems )
+ {
+diff --git a/tests/src/app/testqgsidentify.cpp b/tests/src/app/testqgsidentify.cpp
+index 856d5077c15..401e58747d8 100644
+--- a/tests/src/app/testqgsidentify.cpp
++++ b/tests/src/app/testqgsidentify.cpp
+@@ -932,7 +932,9 @@ void TestQgsIdentify::identifyVectorTile()
+ const QString vtPath = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/vector_tile/{z}-{x}-{y}.pbf" );
+ QgsDataSourceUri dsUri;
+ dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
+- dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( vtPath ).toString() );
++ // The values need to be passed to QgsDataSourceUri::setParam() in the same format they are expected to be retrieved.
++ // QUrl::fromPercentEncoding() is needed here because QUrl::fromLocalFile(vtPath).toString() returns the curly braces in an URL-encoded format.
++ dsUri.setParam( QStringLiteral( "url" ), QUrl::fromPercentEncoding( QUrl::fromLocalFile( vtPath ).toString().toUtf8() ) );
+ QgsVectorTileLayer *tempLayer = new QgsVectorTileLayer( dsUri.encodedUri(), QStringLiteral( "testlayer" ) );
+ QVERIFY( tempLayer->isValid() );
+
+diff --git a/tests/src/core/testqgsdatasourceuri.cpp b/tests/src/core/testqgsdatasourceuri.cpp
+index 409b059b488..436216ede80 100644
+--- a/tests/src/core/testqgsdatasourceuri.cpp
++++ b/tests/src/core/testqgsdatasourceuri.cpp
+@@ -37,6 +37,7 @@ class TestQgsDataSourceUri : public QObject
+ void checkParameterKeys();
+ void checkRemovePassword();
+ void checkUnicodeUri();
++ void checkUriInUri();
+ };
+
+ void TestQgsDataSourceUri::checkparser_data()
+@@ -564,7 +565,7 @@ void TestQgsDataSourceUri::checkAuthParams()
+ // issue GH #53654
+ QgsDataSourceUri uri5;
+ uri5.setEncodedUri( QStringLiteral( "zmax=14&zmin=0&styleUrl=http://localhost:8000/&f=application%2Fvnd.geoserver.mbstyle%2Bjson" ) );
+- QCOMPARE( uri5.param( QStringLiteral( "f" ) ), QStringLiteral( "application%2Fvnd.geoserver.mbstyle%2Bjson" ) );
++ QCOMPARE( uri5.param( QStringLiteral( "f" ) ), QStringLiteral( "application/vnd.geoserver.mbstyle+json" ) );
+
+ uri5.setEncodedUri( QStringLiteral( "zmax=14&zmin=0&styleUrl=http://localhost:8000/&f=application/vnd.geoserver.mbstyle+json" ) );
+ QCOMPARE( uri5.param( QStringLiteral( "f" ) ), QStringLiteral( "application/vnd.geoserver.mbstyle+json" ) );
+@@ -611,6 +612,83 @@ void TestQgsDataSourceUri::checkUnicodeUri()
+ QCOMPARE( uri.param( QStringLiteral( "url" ) ), QStringLiteral( "file:///directory/テスト.mbtiles" ) );
+ }
+
++void TestQgsDataSourceUri::checkUriInUri()
++{
++ QString dataUri = QStringLiteral( "dpiMode=7&url=%1&SERVICE=WMS&REQUEST=GetCapabilities&username=username&password=qgis%C3%A8%C3%A9" );
++
++ // If the 'url' field references a QGIS server then the 'MAP' parameter can contain an url to the project file.
++ // When the project is saved in a postgresql db, the connection url will also contains '&' and '='.
++ {
++ QgsDataSourceUri uri;
++ // here the project url is encoded but the whole serverUrl is not encoded.
++ // The OGC server will receive a call with this url: http://localhost:8000/ows/?MAP=postgresql://?service=qgis_test&dbname&schema=project&project=luxembourg&SERVICE=WMS&REQUEST=GetCapabilities
++ // from the OGC server POV the 'schema' and 'project' keys will be parsed as main query parameters for 'http://localhost:8000/ows/?'
++ // and not associated to the project file uri.
++ QString project = "postgresql://?service=qgis_test&dbname&schema=project&project=luxembourg";
++ QString projectEnc = QUrl::toPercentEncoding( project );
++ QString serverUrl = QString( "http://localhost:8000/ows/?MAP=%1" );
++ uri.setEncodedUri( dataUri.arg( serverUrl.arg( projectEnc ) ) );
++ QCOMPARE( uri.param( QStringLiteral( "username" ) ), QStringLiteral( "username" ) );
++ QCOMPARE( uri.username(), QStringLiteral( "username" ) );
++ QCOMPARE( uri.param( QStringLiteral( "password" ) ), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri.password(), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri.param( QStringLiteral( "SERVICE" ) ), QStringLiteral( "WMS" ) );
++ QCOMPARE( uri.param( QStringLiteral( "REQUEST" ) ), QStringLiteral( "GetCapabilities" ) );
++ // not enough encoded at the beginning ==> bad encoding at the end
++ QCOMPARE( uri.param( QStringLiteral( "url" ) ), serverUrl.arg( project ) );
++
++ QgsDataSourceUri uri2;
++ // here the project url is encoded and the whole serverUrl is also encoded.
++ // The OGC server will receive a call with this url: http://localhost:8000/ows/?MAP=postgresql%3A%2F%2F%3Fservice%3Dqgis_test%26dbname%26schema%3Dproject%26project%3Dluxembourg&SERVICE=WMS&REQUEST=GetCapabilities
++ // and will be able to decode all parameters
++ QString serverUrlEnc = QUrl::toPercentEncoding( serverUrl.arg( projectEnc ) );
++ uri2.setEncodedUri( dataUri.arg( serverUrlEnc ) );
++ QCOMPARE( uri2.param( QStringLiteral( "username" ) ), QStringLiteral( "username" ) );
++ QCOMPARE( uri2.username(), QStringLiteral( "username" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "password" ) ), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri2.password(), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "SERVICE" ) ), QStringLiteral( "WMS" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "REQUEST" ) ), QStringLiteral( "GetCapabilities" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "url" ) ), serverUrl.arg( projectEnc ) );
++ }
++
++ // same as above but with extra param at the end of the
++ {
++ QgsDataSourceUri uri;
++ // here the project url is encoded but the whole serverUrl is not encoded.
++ // The OGC server will receive a call with this url: https://titiler.xyz/cog/tiles/WebMercatorQuad/16/34060/23336@1x?url=https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif&bidx=1&rescale=1600%2C2100&colormap_name=gist_earth
++ // from the OGC server POV the 'rescale' and 'colormap_name' keys could be parsed as sub query parameters for 'https://data.geo.admin.ch/'
++ QString project = "https://data.geo.admin.ch/ch.swisstopo.swissalti3d/swissalti3d_2019_2573-1085/swissalti3d_2019_2573-1085_0.5_2056_5728.tif";
++ QString projectEnc = QUrl::toPercentEncoding( project );
++ QString extraParam = "&bidx=1&rescale=1600%2C2100&colormap_name=gist_earth";
++ QString serverUrl = QString( "https://titiler.xyz/cog/tiles/WebMercatorQuad/16/34060/23336@1x?url=%1" );
++
++ uri.setEncodedUri( dataUri.arg( serverUrl.arg( projectEnc ) + extraParam ) );
++ QCOMPARE( uri.param( QStringLiteral( "username" ) ), QStringLiteral( "username" ) );
++ QCOMPARE( uri.username(), QStringLiteral( "username" ) );
++ QCOMPARE( uri.param( QStringLiteral( "password" ) ), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri.password(), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri.param( QStringLiteral( "SERVICE" ) ), QStringLiteral( "WMS" ) );
++ QCOMPARE( uri.param( QStringLiteral( "REQUEST" ) ), QStringLiteral( "GetCapabilities" ) );
++ // not enough encoded at the beginning ==> bad encoding at the end
++ QCOMPARE( uri.param( QStringLiteral( "url" ) ), serverUrl.arg( project ) );
++
++ QgsDataSourceUri uri2;
++ // here the project url is encoded and the whole serverUrl is also encoded.
++ // The OGC server will receive a call with this url: https://titiler.xyz/cog/tiles/WebMercatorQuad/16/34060/23336@1x?url=https%3A%2F%2Fdata.geo.admin.ch%2Fch.swisstopo.swissalti3d%2Fswissalti3d_2019_2573-1085%2Fswissalti3d_2019_2573-1085_0.5_2056_5728.tif&bidx=1&rescale=1600%2C2100&colormap_name=gist_earth
++ // and will be able to decode all parameters
++ QString serverUrlEnc = QUrl::toPercentEncoding( serverUrl.arg( projectEnc ) + extraParam );
++ uri2.setEncodedUri( dataUri.arg( serverUrlEnc ) );
++ QCOMPARE( uri2.param( QStringLiteral( "username" ) ), QStringLiteral( "username" ) );
++ QCOMPARE( uri2.username(), QStringLiteral( "username" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "password" ) ), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri2.password(), QStringLiteral( "qgisèé" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "SERVICE" ) ), QStringLiteral( "WMS" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "REQUEST" ) ), QStringLiteral( "GetCapabilities" ) );
++ QCOMPARE( uri2.param( QStringLiteral( "url" ) ), serverUrl.arg( projectEnc ) + extraParam );
++ }
++}
++
+
+ QGSTEST_MAIN( TestQgsDataSourceUri )
+ #include "testqgsdatasourceuri.moc"
+diff --git a/tests/src/core/testqgsgdalcloudconnection.cpp b/tests/src/core/testqgsgdalcloudconnection.cpp
+index e43c4757ee7..0e69eb210ab 100644
+--- a/tests/src/core/testqgsgdalcloudconnection.cpp
++++ b/tests/src/core/testqgsgdalcloudconnection.cpp
+@@ -59,7 +59,7 @@ void TestQgsGdalCloudConnection::encodeDecode()
+ data.rootPath = QStringLiteral( "some/path" );
+ data.credentialOptions = QVariantMap { { "pw", QStringLiteral( "xxxx" ) }, { "key", QStringLiteral( "yyy" ) } };
+
+- QCOMPARE( QgsGdalCloudProviderConnection::encodedUri( data ), QStringLiteral( "container=my_container&credentialOptions=key%3Dyyy%7Cpw%3Dxxxx&handler=vsis3&rootPath=some/path" ) );
++ QCOMPARE( QgsGdalCloudProviderConnection::encodedUri( data ), QStringLiteral( "container=my_container&credentialOptions=key%3Dyyy%7Cpw%3Dxxxx&handler=vsis3&rootPath=some%2Fpath" ) );
+
+ const QgsGdalCloudProviderConnection::Data data2 = QgsGdalCloudProviderConnection::decodedUri( QStringLiteral( "container=my_container&credentialOptions=key%3Dyyy%7Cpw%3Dxxxx&handler=vsis3&rootPath=some/path" ) );
+ QCOMPARE( data2.vsiHandler, QStringLiteral( "vsis3" ) );
+@@ -94,7 +94,7 @@ void TestQgsGdalCloudConnection::testConnections()
+
+ // retrieve stored connection
+ conn = QgsGdalCloudProviderConnection( QStringLiteral( "my connection" ) );
+- QCOMPARE( conn.uri(), QStringLiteral( "container=my_container&credentialOptions=key%3Dyyy%7Cpw%3Dxxxx&handler=vsis3&rootPath=some/path" ) );
++ QCOMPARE( conn.uri(), QStringLiteral( "container=my_container&credentialOptions=key%3Dyyy%7Cpw%3Dxxxx&handler=vsis3&rootPath=some%2Fpath" ) );
+
+ // add a second connection
+ QgsGdalCloudProviderConnection::Data data2;
+diff --git a/tests/src/core/testqgshttpheaders.cpp b/tests/src/core/testqgshttpheaders.cpp
+index 9c2df3cc20e..78bc5f8be81 100644
+--- a/tests/src/core/testqgshttpheaders.cpp
++++ b/tests/src/core/testqgshttpheaders.cpp
+@@ -147,11 +147,14 @@ void TestQgsHttpheaders::createQgsOwsConnection()
+
+ QgsOwsConnection ows( "service", "name" );
+ QCOMPARE( ows.connectionInfo(), ",authcfg=,referer=http://test.com" );
+- QCOMPARE( ows.uri().encodedUri(), "url&http-header:other_http_header=value&http-header:referer=http://test.com" );
++ if ( ows.uri().encodedUri().startsWith( "url=" ) )
++ QCOMPARE( ows.uri().encodedUri(), "url=&http-header:other_http_header=value&http-header:referer=http%3A%2F%2Ftest.com" );
++ else
++ QCOMPARE( ows.uri().encodedUri(), "url&http-header:other_http_header=value&http-header:referer=http%3A%2F%2Ftest.com" );
+
+ QgsDataSourceUri uri( QString( "https://www.ogc.org/?p1=v1" ) );
+ QgsDataSourceUri uri2 = ows.addWmsWcsConnectionSettings( uri, "service", "name" );
+- QCOMPARE( uri2.encodedUri(), "https://www.ogc.org/?p1=v1&http-header:other_http_header=value&http-header:referer=http://test.com" );
++ QCOMPARE( uri2.encodedUri(), "https://www.ogc.org/?p1=v1&http-header:other_http_header=value&http-header:referer=http%3A%2F%2Ftest.com" );
+
+ // check space separated string
+ QCOMPARE( uri2.uri(), " https://www.ogc.org/?p1='v1' http-header:other_http_header='value' http-header:referer='http://test.com' referer='http://test.com'" );
+@@ -159,7 +162,7 @@ void TestQgsHttpheaders::createQgsOwsConnection()
+ QgsDataSourceUri uri3( uri2.uri() );
+ QCOMPARE( uri3.httpHeader( QgsHttpHeaders::KEY_REFERER ), "http://test.com" );
+ QCOMPARE( uri3.httpHeader( "other_http_header" ), "value" );
+- QCOMPARE( uri3.encodedUri(), "https://www.ogc.org/?p1=v1&referer=http://test.com&http-header:other_http_header=value&http-header:referer=http://test.com" );
++ QCOMPARE( uri3.encodedUri(), "https://www.ogc.org/?p1=v1&referer=http%3A%2F%2Ftest.com&http-header:other_http_header=value&http-header:referer=http%3A%2F%2Ftest.com" );
+ }
+
+
+diff --git a/tests/src/core/testqgsmaplayer.cpp b/tests/src/core/testqgsmaplayer.cpp
+index 47e6f2d5a6e..e9d527b186f 100644
+--- a/tests/src/core/testqgsmaplayer.cpp
++++ b/tests/src/core/testqgsmaplayer.cpp
+@@ -32,6 +32,7 @@
+ #include "qgsmaplayerstore.h"
+ #include "qgsproject.h"
+ #include "qgsxmlutils.h"
++#include "qgsvectortilelayer.h"
+
+ /**
+ * \ingroup UnitTests
+@@ -54,6 +55,8 @@ class TestQgsMapLayer : public QObject
+ void testId();
+ void formatName();
+
++ void generalHtmlMetadata();
++
+ void setBlendMode();
+
+ void isInScaleRange_data();
+@@ -150,6 +153,33 @@ void TestQgsMapLayer::testId()
+ QCOMPARE( spy3.count(), 1 );
+ }
+
++void TestQgsMapLayer::generalHtmlMetadata()
++{
++ {
++ QgsDataSourceUri ds;
++ ds.setParam( QStringLiteral( "type" ), "xyz" );
++ ds.setParam( QStringLiteral( "zmax" ), "1" );
++ ds.setParam( QStringLiteral( "url" ), "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png" );
++ std::unique_ptr vl( new QgsVectorTileLayer( ds.encodedUri(), QStringLiteral( "testLayer" ) ) );
++ QVERIFY( vl->dataProvider() );
++ QVERIFY( vl->dataProvider()->isValid() );
++ QCOMPARE( ds.param( QStringLiteral( "url" ) ), "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png" );
++ QVERIFY( vl->generalHtmlMetadata().contains( "URL| vl( new QgsVectorTileLayer( ds.encodedUri(), QStringLiteral( "testLayer" ) ) );
++ QVERIFY( vl->dataProvider() );
++ QVERIFY( vl->dataProvider()->isValid() );
++ QCOMPARE( ds.param( QStringLiteral( "url" ) ), QStringLiteral( "%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QVERIFY( vl->generalHtmlMetadata().contains( QStringLiteral( "Path | ( &conn )->providerKey(), QStringLiteral( "test_provider" ) );
+
+ // add a second connection
+@@ -110,7 +110,7 @@ void TestQgsTiledSceneConnection::testConnections()
+ data2.httpHeaders.insert( QStringLiteral( "my_header" ), QStringLiteral( "value2" ) );
+ // construct connection using encoded uri
+ QgsTiledSceneProviderConnection conn2( QgsTiledSceneProviderConnection::encodedUri( data2 ), QStringLiteral( "test_provider2" ), {} );
+- QCOMPARE( conn2.uri(), QStringLiteral( "url=http://testurl2&username=my_user2&password=my_pw2&authcfg=my_auth2&http-header:my_header=value2" ) );
++ QCOMPARE( conn2.uri(), QStringLiteral( "url=http%3A%2F%2Ftesturl2&username=my_user2&password=my_pw2&authcfg=my_auth2&http-header:my_header=value2" ) );
+ QCOMPARE( qgis::down_cast( &conn2 )->providerKey(), QStringLiteral( "test_provider2" ) );
+ conn2.store( QStringLiteral( "second connection" ) );
+
+diff --git a/tests/src/core/testqgsvectortileconnection.cpp b/tests/src/core/testqgsvectortileconnection.cpp
+index e539eb0be69..d73454fa428 100644
+--- a/tests/src/core/testqgsvectortileconnection.cpp
++++ b/tests/src/core/testqgsvectortileconnection.cpp
+@@ -62,13 +62,13 @@ void TestQgsVectorTileConnection::test_encodedUri()
+ conn.zMin = 0;
+ conn.zMax = 18;
+ QString uri = QgsVectorTileProviderConnection::encodedUri( conn );
+- QCOMPARE( uri, QStringLiteral( "type=xyz&url=https://api.maptiler.com/tiles/v3/%7Bz%7D/%7Bx%7D/%7By%7D.pbf?key%3Dabcdef12345&zmax=18&zmin=0" ) );
++ QCOMPARE( uri, QStringLiteral( "type=xyz&url=https%3A%2F%2Fapi.maptiler.com%2Ftiles%2Fv3%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf%3Fkey%3Dabcdef12345&zmax=18&zmin=0" ) );
+
+ conn.url = QStringLiteral( "file:///home/user/tiles.mbtiles" );
+ conn.zMin = 0;
+ conn.zMax = 18;
+ uri = QgsVectorTileProviderConnection::encodedUri( conn );
+- QCOMPARE( uri, QStringLiteral( "type=mbtiles&url=file:///home/user/tiles.mbtiles&zmax=18&zmin=0" ) );
++ QCOMPARE( uri, QStringLiteral( "type=mbtiles&url=file%3A%2F%2F%2Fhome%2Fuser%2Ftiles.mbtiles&zmax=18&zmin=0" ) );
+ }
+
+
+diff --git a/tests/src/core/testqgsvectortilelayer.cpp b/tests/src/core/testqgsvectortilelayer.cpp
+index 4a5f82f0b0d..99c0b503c30 100644
+--- a/tests/src/core/testqgsvectortilelayer.cpp
++++ b/tests/src/core/testqgsvectortilelayer.cpp
+@@ -256,11 +256,12 @@ void TestQgsVectorTileLayer::testMbtilesProviderMetadata()
+ QCOMPARE( vectorTileMetadata->validLayerTypesForUri( QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) ), { Qgis::LayerType::VectorTile } );
+
+ // query sublayers
++ QString localMbtilesPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral( "/vector_tile/mbtiles_vt.mbtiles" ) ) );
+ QList sublayers = vectorTileMetadata->querySublayers( QStringLiteral( "%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "mbtilesvectortiles" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1" ).arg( localMbtilesPath ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+ QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );
+ QVERIFY( !QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );
+@@ -269,7 +270,7 @@ void TestQgsVectorTileLayer::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "mbtilesvectortiles" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1" ).arg( localMbtilesPath ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+ QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );
+
+@@ -278,7 +279,7 @@ void TestQgsVectorTileLayer::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "mbtilesvectortiles" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1" ).arg( localMbtilesPath ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+ QVERIFY( QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );
+@@ -287,17 +288,19 @@ void TestQgsVectorTileLayer::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "mbtilesvectortiles" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1" ).arg( localMbtilesPath ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+
+ // fast scan mode means that any mbtile file will be reported, including those with only raster tiles
+ // (we are skipping a potentially expensive db open and format check)
++ QString localIsleOfManPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral( "/isle_of_man.mbtiles" ) ) );
++
+ sublayers = vectorTileMetadata->querySublayers( QStringLiteral( "%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "mbtilesvectortiles" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=mbtiles&url=%1" ).arg( localIsleOfManPath ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+
+@@ -328,8 +331,9 @@ void TestQgsVectorTileLayer::test_relativePathsMbTiles()
+ QgsReadWriteContext contextRel;
+ contextRel.setPathResolver( QgsPathResolver( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/project.qgs" ) ) );
+ const QgsReadWriteContext contextAbs;
++ QString localMbtilesPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral( "/vector_tile/mbtiles_vt.mbtiles" ) ) );
+
+- const QString srcMbtiles = QStringLiteral( "type=mbtiles&url=%1/vector_tile/mbtiles_vt.mbtiles" ).arg( TEST_DATA_DIR );
++ const QString srcMbtiles = QStringLiteral( "type=mbtiles&url=%1" ).arg( localMbtilesPath );
+
+ std::unique_ptr layer = std::make_unique( srcMbtiles );
+ QVERIFY( layer->isValid() );
+@@ -337,7 +341,7 @@ void TestQgsVectorTileLayer::test_relativePathsMbTiles()
+
+ // encode source: converting absolute paths to relative
+ const QString srcMbtilesRel = layer->encodedSource( srcMbtiles, contextRel );
+- QCOMPARE( srcMbtilesRel, QStringLiteral( "type=mbtiles&url=./vector_tile/mbtiles_vt.mbtiles" ) );
++ QCOMPARE( srcMbtilesRel, QStringLiteral( "type=mbtiles&url=.%2Fvector_tile%2Fmbtiles_vt.mbtiles" ) );
+
+ // encode source: keeping absolute paths
+ QCOMPARE( layer->encodedSource( srcMbtiles, contextAbs ), srcMbtiles );
+@@ -377,15 +381,15 @@ void TestQgsVectorTileLayer::test_relativePathsXyz()
+ contextRel.setPathResolver( QgsPathResolver( "/home/qgis/project.qgs" ) );
+ const QgsReadWriteContext contextAbs;
+
+- const QString srcXyzLocal = "type=xyz&url=file:///home/qgis/%7Bz%7D/%7Bx%7D/%7By%7D.pbf";
+- const QString srcXyzRemote = "type=xyz&url=http://www.example.com/%7Bz%7D/%7Bx%7D/%7By%7D.pbf";
++ const QString srcXyzLocal = "type=xyz&url=file%3A%2F%2F%2Fhome%2Fqgis%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf";
++ const QString srcXyzRemote = "type=xyz&url=http%3A%2F%2Fwww.example.com%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf";
+
+ std::unique_ptr layer = std::make_unique( srcXyzLocal );
+ QCOMPARE( layer->providerType(), QStringLiteral( "xyzvectortiles" ) );
+
+ // encode source: converting absolute paths to relative
+ const QString srcXyzLocalRel = layer->encodedSource( srcXyzLocal, contextRel );
+- QCOMPARE( srcXyzLocalRel, QStringLiteral( "type=xyz&url=file:./%7Bz%7D/%7Bx%7D/%7By%7D.pbf" ) );
++ QCOMPARE( srcXyzLocalRel, QStringLiteral( "type=xyz&url=file%3A.%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf" ) );
+ QCOMPARE( layer->encodedSource( srcXyzRemote, contextRel ), srcXyzRemote );
+
+ // encode source: keeping absolute paths
+@@ -421,7 +425,8 @@ void TestQgsVectorTileLayer::test_absoluteRelativeUriXyz()
+
+ QString absoluteUri = dsAbs.encodedUri();
+ QString relativeUri = dsRel.encodedUri();
+- QCOMPARE( vectorTileMetadata->absoluteToRelativeUri( absoluteUri, context ), relativeUri );
++ QString absToRelUri = vectorTileMetadata->absoluteToRelativeUri( absoluteUri, context );
++ QCOMPARE( absToRelUri, relativeUri );
+ QCOMPARE( vectorTileMetadata->relativeToAbsoluteUri( relativeUri, context ), absoluteUri );
+ }
+
+@@ -443,23 +448,23 @@ void TestQgsVectorTileLayer::testVtpkProviderMetadata()
+ QVERIFY( vectorTileMetadata->querySublayers( QStringLiteral( "type=vtpk&url=%1/points.shp" ).arg( TEST_DATA_DIR ) ).isEmpty() );
+
+ // vtpk uris
+- QCOMPARE( vectorTileMetadata->priorityForUri( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/testvtpk.vtpk" ) ), 100 );
+- QCOMPARE( vectorTileMetadata->validLayerTypesForUri( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/testvtpk.vtpk" ) ), { Qgis::LayerType::VectorTile } );
+- QList sublayers = vectorTileMetadata->querySublayers( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/testvtpk.vtpk" ) );
+- QCOMPARE( sublayers.size(), 1 );
+- QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "vtpkvectortiles" ) );
+- QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "testvtpk" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) );
+- QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
+-
+- QCOMPARE( vectorTileMetadata->priorityForUri( QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) ), 100 );
+- QCOMPARE( vectorTileMetadata->validLayerTypesForUri( QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) ), { Qgis::LayerType::VectorTile } );
+- sublayers = vectorTileMetadata->querySublayers( QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) );
+- QCOMPARE( sublayers.size(), 1 );
+- QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "vtpkvectortiles" ) );
+- QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "testvtpk" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) );
+- QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
++ QString localVtpkPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral( "/testvtpk.vtpk" ) ) );
++
++ for ( auto uriStr : {
++ QStringLiteral( "%1/%2" ).arg( TEST_DATA_DIR ).arg( "testvtpk.vtpk" ), //
++ QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ), //
++ QStringLiteral( "type=vtpk&url=%1" ).arg( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/testvtpk.vtpk" ) )
++ } )
++ {
++ QCOMPARE( vectorTileMetadata->priorityForUri( uriStr ), 100 );
++ QCOMPARE( vectorTileMetadata->validLayerTypesForUri( uriStr ), { Qgis::LayerType::VectorTile } );
++ QList sublayers = vectorTileMetadata->querySublayers( uriStr );
++ QCOMPARE( sublayers.size(), 1 );
++ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "vtpkvectortiles" ) );
++ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "testvtpk" ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ) );
++ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::VectorTile );
++ }
+
+ // test that vtpk provider is the preferred provider for vtpk files
+ QList candidates = QgsProviderRegistry::instance()->preferredProvidersForUri( QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR ) );
+@@ -485,7 +490,9 @@ void TestQgsVectorTileLayer::test_relativePathsVtpk()
+ contextRel.setPathResolver( QgsPathResolver( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/project.qgs" ) ) );
+ const QgsReadWriteContext contextAbs;
+
+- const QString srcVtpk = QStringLiteral( "type=vtpk&url=%1/testvtpk.vtpk" ).arg( TEST_DATA_DIR );
++ QString localVtpkPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral( "/testvtpk.vtpk" ) ) );
++
++ const QString srcVtpk = QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath );
+
+ std::unique_ptr layer = std::make_unique( srcVtpk );
+ QVERIFY( layer->isValid() );
+@@ -493,7 +500,7 @@ void TestQgsVectorTileLayer::test_relativePathsVtpk()
+
+ // encode source: converting absolute paths to relative
+ const QString srcVtpkRel = layer->encodedSource( srcVtpk, contextRel );
+- QCOMPARE( srcVtpkRel, QStringLiteral( "type=vtpk&url=./testvtpk.vtpk" ) );
++ QCOMPARE( srcVtpkRel, QStringLiteral( "type=vtpk&url=.%2Ftestvtpk.vtpk" ) );
+
+ // encode source: keeping absolute paths
+ QCOMPARE( layer->encodedSource( srcVtpk, contextAbs ), srcVtpk );
+diff --git a/tests/src/providers/testqgswmsprovider.cpp b/tests/src/providers/testqgswmsprovider.cpp
+index d736bfcc38f..3cbaf2578fd 100644
+--- a/tests/src/providers/testqgswmsprovider.cpp
++++ b/tests/src/providers/testqgswmsprovider.cpp
+@@ -321,7 +321,7 @@ void TestQgsWmsProvider::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), u"url=file%3A%2F%2F%1%2Fisle_of_man.mbtiles&type=mbtiles"_s.arg( QString( TEST_DATA_DIR ).replace( "/", "%2F" ) ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
+ QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );
+ QVERIFY( !QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );
+@@ -330,7 +330,7 @@ void TestQgsWmsProvider::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), u"url=file%3A%2F%2F%1%2Fisle_of_man.mbtiles&type=mbtiles"_s.arg( QString( TEST_DATA_DIR ).replace( "/", "%2F" ) ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
+ QVERIFY( !sublayers.at( 0 ).skippedContainerScan() );
+
+@@ -347,16 +347,16 @@ void TestQgsWmsProvider::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), u"url=file%3A%2F%2F%1%2Fisle_of_man.mbtiles&type=mbtiles"_s.arg( QString( TEST_DATA_DIR ).replace( "/", "%2F" ) ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+ QVERIFY( QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers ) );
+
+- sublayers = wmsMetadata->querySublayers( QStringLiteral( "type=mbtiles&url=%1/isle_of_man.mbtiles" ).arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
++ sublayers = wmsMetadata->querySublayers( u"type=mbtiles&url=file%3A%2F%2F%1%2Fisle_of_man.mbtiles"_s.arg( TEST_DATA_DIR ), Qgis::SublayerQueryFlag::FastScan );
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "isle_of_man" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/isle_of_man.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), u"url=file%3A%2F%2F%1%2Fisle_of_man.mbtiles&type=mbtiles"_s.arg( QString( TEST_DATA_DIR ).replace( "/", "%2F" ) ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+
+@@ -374,7 +374,7 @@ void TestQgsWmsProvider::testMbtilesProviderMetadata()
+ QCOMPARE( sublayers.size(), 1 );
+ QCOMPARE( sublayers.at( 0 ).providerKey(), QStringLiteral( "wms" ) );
+ QCOMPARE( sublayers.at( 0 ).name(), QStringLiteral( "mbtiles_vt" ) );
+- QCOMPARE( sublayers.at( 0 ).uri(), QStringLiteral( "url=file://%1/vector_tile/mbtiles_vt.mbtiles&type=mbtiles" ).arg( TEST_DATA_DIR ) );
++ QCOMPARE( sublayers.at( 0 ).uri(), u"url=file%3A%2F%2F%1%2Fvector_tile%2Fmbtiles_vt.mbtiles&type=mbtiles"_s.arg( QString( TEST_DATA_DIR ).replace( "/", "%2F" ) ) );
+ QCOMPARE( sublayers.at( 0 ).type(), Qgis::LayerType::Raster );
+ QVERIFY( sublayers.at( 0 ).skippedContainerScan() );
+
+@@ -435,22 +435,21 @@ void TestQgsWmsProvider::providerUriUpdates()
+ QCOMPARE( parts["testParam"], QVariant( "false" ) );
+
+ QString updatedUri = metadata->encodeUri( parts );
+- QString expectedUri = QStringLiteral( "crs=EPSG:4326&dpiMode=7&"
++ QString expectedUri = QStringLiteral( "crs=EPSG%3A4326&dpiMode=7&"
+ "layers=testlayer&styles&"
+ "testParam=false&"
+- "url=http://localhost:8380/mapserv" );
++ "url=http%3A%2F%2Flocalhost%3A8380%2Fmapserv" );
+ QCOMPARE( updatedUri, expectedUri );
+ }
+
+ void TestQgsWmsProvider::providerUriLocalFile()
+ {
+- QString uriString = QStringLiteral( "url=file:///my/local/tiles.mbtiles&type=mbtiles" );
+- QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "wms" ), uriString );
++ QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( u"wms"_s, u"url=file:///my/local/tiles.mbtiles&type=mbtiles"_s );
+ QVariantMap expectedParts { { QString( "type" ), QVariant( "mbtiles" ) }, { QString( "path" ), QVariant( "/my/local/tiles.mbtiles" ) }, { QString( "url" ), QVariant( "file:///my/local/tiles.mbtiles" ) } };
+ QCOMPARE( parts, expectedParts );
+
+ QString encodedUri = QgsProviderRegistry::instance()->encodeUri( QStringLiteral( "wms" ), parts );
+- QCOMPARE( encodedUri, uriString );
++ QCOMPARE( encodedUri, u"url=file%3A%2F%2F%2Fmy%2Flocal%2Ftiles.mbtiles&type=mbtiles"_s );
+
+ QgsProviderMetadata *wmsMetadata = QgsProviderRegistry::instance()->providerMetadata( "wms" );
+ QVERIFY( wmsMetadata );
+@@ -475,10 +474,27 @@ void TestQgsWmsProvider::absoluteRelativeUri()
+ QgsProviderMetadata *wmsMetadata = QgsProviderRegistry::instance()->providerMetadata( "wms" );
+ QVERIFY( wmsMetadata );
+
+- QString absoluteUri = "type=mbtiles&url=file://" + QStringLiteral( TEST_DATA_DIR ) + "/isle_of_man.mbtiles";
+- QString relativeUri = "type=mbtiles&url=file:./isle_of_man.mbtiles";
+- QCOMPARE( wmsMetadata->absoluteToRelativeUri( absoluteUri, context ), relativeUri );
+- QCOMPARE( wmsMetadata->relativeToAbsoluteUri( relativeUri, context ), absoluteUri );
++ // from no encoded absolute url to encoded relative url
++ {
++ QString absoluteUri = QString( "type=mbtiles&url=" ) + "file://" + QStringLiteral( TEST_DATA_DIR ) + "/isle_of_man.mbtiles";
++ QString relativeUri = "type=mbtiles&url=file%3A.%2Fisle_of_man.mbtiles";
++ QCOMPARE( wmsMetadata->absoluteToRelativeUri( absoluteUri, context ), relativeUri );
++ }
++
++ // from no encoded relative url to encoded absolute url
++ {
++ QString relativeUri = "type=mbtiles&url=file:./isle_of_man.mbtiles";
++ QString absoluteUri = "type=mbtiles&url=" + QString( QUrl::toPercentEncoding( "file://" + QStringLiteral( TEST_DATA_DIR ) + "/isle_of_man.mbtiles" ) );
++ QCOMPARE( wmsMetadata->relativeToAbsoluteUri( relativeUri, context ), absoluteUri );
++ }
++
++ // from encoded to encoded
++ {
++ QString absoluteUri = "type=mbtiles&url=" + QString( QUrl::toPercentEncoding( "file://" + QStringLiteral( TEST_DATA_DIR ) + "/isle_of_man.mbtiles" ) );
++ QString relativeUri = "type=mbtiles&url=file%3A.%2Fisle_of_man.mbtiles";
++ QCOMPARE( wmsMetadata->absoluteToRelativeUri( absoluteUri, context ), relativeUri );
++ QCOMPARE( wmsMetadata->relativeToAbsoluteUri( relativeUri, context ), absoluteUri );
++ }
+ }
+
+ void TestQgsWmsProvider::testXyzIsBasemap()
+diff --git a/tests/src/python/test_qgsmapboxglconverter.py b/tests/src/python/test_qgsmapboxglconverter.py
+index 8f4640eb89c..0031ee3c54d 100644
+--- a/tests/src/python/test_qgsmapboxglconverter.py
++++ b/tests/src/python/test_qgsmapboxglconverter.py
+@@ -2406,7 +2406,7 @@ class TestQgsMapBoxGlStyleConverter(QgisTestCase):
+ self.assertIsInstance(rl, QgsRasterLayer)
+ self.assertEqual(
+ rl.source(),
+- "tilePixelRation=1&type=xyz&url=https://yyyyyy/v1/tiles/texturereliefshade/EPSG:3857/%7Bz%7D/%7Bx%7D/%7By%7D.webp&zmax=20&zmin=3",
++ "tilePixelRation=1&type=xyz&url=https%3A%2F%2Fyyyyyy%2Fv1%2Ftiles%2Ftexturereliefshade%2FEPSG%3A3857%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.webp&zmax=20&zmin=3",
+ )
+ self.assertEqual(rl.providerType(), "wms")
+
+@@ -2418,7 +2418,7 @@ class TestQgsMapBoxGlStyleConverter(QgisTestCase):
+ self.assertEqual(raster_layer.name(), "Texture-Relief")
+ self.assertEqual(
+ raster_layer.source(),
+- "tilePixelRation=1&type=xyz&url=https://yyyyyy/v1/tiles/texturereliefshade/EPSG:3857/%7Bz%7D/%7Bx%7D/%7By%7D.webp&zmax=20&zmin=3",
++ "tilePixelRation=1&type=xyz&url=https%3A%2F%2Fyyyyyy%2Fv1%2Ftiles%2Ftexturereliefshade%2FEPSG%3A3857%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.webp&zmax=20&zmin=3",
+ )
+ self.assertEqual(
+ raster_layer.pipe()
+diff --git a/tests/src/python/test_qgsvectortile.py b/tests/src/python/test_qgsvectortile.py
+index a4866d1229b..4c42b630b58 100644
+--- a/tests/src/python/test_qgsvectortile.py
++++ b/tests/src/python/test_qgsvectortile.py
+@@ -105,7 +105,7 @@ class TestVectorTile(QgisTestCase):
+
+ parts["path"] = "/my/new/file.mbtiles"
+ uri = md.encodeUri(parts)
+- self.assertEqual(uri, "type=mbtiles&url=/my/new/file.mbtiles")
++ self.assertEqual(uri, "type=mbtiles&url=%2Fmy%2Fnew%2Ffile.mbtiles")
+
+ uri = (
+ "type=xyz&url=https://fake.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmin=0&zmax=2"
+@@ -125,7 +125,7 @@ class TestVectorTile(QgisTestCase):
+ uri = md.encodeUri(parts)
+ self.assertEqual(
+ uri,
+- "type=xyz&url=https://fake.new.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&zmin=0",
++ "type=xyz&url=https%3A%2F%2Ffake.new.server%2F%7Bx%7D%2F%7By%7D%2F%7Bz%7D.png&zmax=2&zmin=0",
+ )
+
+ uri = "type=xyz&serviceType=arcgis&url=https://fake.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&http-header:referer=https://qgis.org/&styleUrl=https://qgis.org/"
+@@ -147,7 +147,7 @@ class TestVectorTile(QgisTestCase):
+ uri = md.encodeUri(parts)
+ self.assertEqual(
+ uri,
+- "serviceType=arcgis&styleUrl=https://qgis.org/&type=xyz&url=https://fake.new.server/%7Bx%7D/%7By%7D/%7Bz%7D.png&zmax=2&http-header:referer=https://qgis.org/",
++ "serviceType=arcgis&styleUrl=https%3A%2F%2Fqgis.org%2F&type=xyz&url=https%3A%2F%2Ffake.new.server%2F%7Bx%7D%2F%7By%7D%2F%7Bz%7D.png&zmax=2&http-header:referer=https%3A%2F%2Fqgis.org%2F",
+ )
+
+ def testZoomRange(self):
+diff --git a/tests/src/server/wms/test_qgsserver_wms_parameters.cpp b/tests/src/server/wms/test_qgsserver_wms_parameters.cpp
+index 792325c642b..5aa2ab3bd9f 100644
+--- a/tests/src/server/wms/test_qgsserver_wms_parameters.cpp
++++ b/tests/src/server/wms/test_qgsserver_wms_parameters.cpp
+@@ -64,14 +64,14 @@ void TestQgsServerWmsParameters::external_layers()
+
+ QgsWms::QgsWmsParametersLayer layer_params = layers_params[0];
+ QCOMPARE( layer_params.mNickname, QString( "external_layer_1" ) );
+- QCOMPARE( layer_params.mExternalUri, QString( "layers=layer_1_name&url=http://url_1" ) );
++ QCOMPARE( layer_params.mExternalUri, QString( "layers=layer_1_name&url=http%3A%2F%2Furl_1" ) );
+
+ layer_params = layers_params[1];
+ QCOMPARE( layer_params.mNickname, QString( "layer" ) );
+
+ layer_params = layers_params[2];
+ QCOMPARE( layer_params.mNickname, QString( "external_layer_2" ) );
+- QCOMPARE( layer_params.mExternalUri, QString( "layers=layer_2_name&opacities=100&url=http://url_2" ) );
++ QCOMPARE( layer_params.mExternalUri, QString( "layers=layer_2_name&opacities=100&url=http%3A%2F%2Furl_2" ) );
+
+ //test if opacities are also applied to external layers
+ QCOMPARE( layers_params[0].mOpacity, 255 );
+@@ -94,7 +94,7 @@ void TestQgsServerWmsParameters::external_layers()
+
+ QgsWms::QgsWmsParametersLayer layer_params2 = layers_params2[0];
+ QCOMPARE( layer_params2.mNickname, QString( "external_layer_1" ) );
+- QCOMPARE( layer_params2.mExternalUri, QString( "layers=layer_1_name&url=http://url_1" ) );
++ QCOMPARE( layer_params2.mExternalUri, QString( "layers=layer_1_name&url=http%3A%2F%2Furl_1" ) );
+ }
+
+ void TestQgsServerWmsParameters::percent_encoding()
|