Compare commits

...

20 Commits

Author SHA1 Message Date
Benoit D.-M. - oslandia
0b8e960237
Merge 739c6ec98efa5f7e785e0cb0e4d9e17805967bf1 into 74549aad26c3358101e88477d9dfa1caae013d72 2025-07-01 11:18:00 +00:00
bdm-oslandia
739c6ec98e test(qgsdatasourceuri): add uri in uri test 2025-07-01 11:49:05 +02:00
bdm-oslandia
2b40e3a7a4 fix(httpheaders): add missing urlencode to headers when updating QUrlQuery 2025-07-01 11:49:05 +02:00
bdm-oslandia
a3b523d368 fix(QgsProjectStorageRegistry): project uri can be urlencoded and starts with XXX%3A 2025-07-01 11:49:05 +02:00
Max Tobias Weber
4de79c55ba fix double URL-encoding issue with curly braces in z,x,y placeholders of VectorTile Sources in QgsDataSurceUri 2025-07-01 11:49:05 +02:00
Max Tobias Weber
3d392af31d fix(test): add/adjust tests for url encoding
add decoding step before adding the "url" value to QgsDataSourceUri in TestQgsIdentify::identifyVectorTile(), as QgsDataSourceUri now reliably returns the values that were provided.

adjust tests to QgsDataSourceUri full value URL-encoding

fix test tests/src/core/testqgshttpheaders.cpp

fix test tests/src/core/testqgssensorthingsconnection.cpp

fix test tests/src/core/testqgstiledsceneconnection.cpp

fix test tests/src/core/testqgsvectortileconnection.cpp

fix test tests/src/python/test_qgsvectortile.py

fix test tests/src/server/wms/test_qgsserver_wms_parameters.cpp

fix tests tests/src/core/testqgshttpheaders.cpp

fix most tests in tests/src/core/testqgsvectortilelayer.cpp
2025-07-01 11:49:05 +02:00
Max Tobias Weber
fa7f98a666 make sure QgsDataSourceUri values are FullyUrlDecoded when parsing QgsDataSourceUri strings in QgsDataSourceUri::setEncodedUri 2025-07-01 11:49:05 +02:00
Max Tobias Weber
fd7c2bfec4 move QUrl::toPercentEncoding and QUrl::fromPercentEncoding steps from QgsDataSourceUri param setter and getter functions to URI serialization QgsDataSourceUri::encodedUri() 2025-07-01 11:49:05 +02:00
Max Tobias Weber
ade7904a42 fix faulty assertion of URI parameter values not being fully URL-encoded in tests/src/providers/testqgswmsprovider.cpp 2025-07-01 11:49:05 +02:00
Max Tobias Weber
6ee3e72827 fix faulty assertion in tests/src/core/testqgsdatasourceuri.cpp
URl-encoded in URL-parameter values characters will be URL-decoded on retrieval.
2025-07-01 11:49:04 +02:00
Max Tobias Weber
f43bad1baa ensure values in QgsDataSourceUri are URL-encoded when assigned and properly decoded when retrieved, preventing any potential loss of information. 2025-07-01 11:49:04 +02:00
Juergen E. Fischer
74549aad26 Reapply "Allow free naming of project properties (#60855)"
This reverts commit fb11239112adfc321b3bbacbb20da888a7a37c23.
2025-07-01 09:08:44 +00:00
Juergen E. Fischer
eac401c009 if a plugin supports qt6 it should also support QGIS4 (fixes #62359) 2025-07-01 09:07:26 +00:00
Loïc Bartoletti
1f0166d35e
Merge pull request #62365 from benoitdm-oslandia/fix/cgibin_dir
fix: update default cgi-bin installation paths according to OS
2025-07-01 07:26:25 +02:00
Loïc Bartoletti
ada589bb1d
Merge pull request #62052 from martin-s42/visitPointsByRegularDistance-segfault-fix
fix segfault due to calling qgsDoubleNear with tollerance that can ne…
2025-07-01 06:59:03 +02:00
bdm-oslandia
b1c8ef3265 fixup! fix: update default cgi-bin installation paths according to OS 2025-06-27 09:35:48 +02:00
bdm-oslandia
f4cf09d4b0 fixup! fix: update default cgi-bin installation paths according to OS 2025-06-25 08:29:52 +02:00
bdm-oslandia
9db58e3726 fixup! fix: update default cgi-bin installation paths according to OS 2025-06-24 17:37:57 +02:00
bdm-oslandia
551aa20f20 fix: update default cgi-bin installation paths according to OS 2025-06-23 10:44:49 +02:00
Martin Siegert
a156c43f7b
fix segfault due to calling qgsDoubleNear with tollerance that can never been reached
see issue #60772: function interpolatePoint in qgsabstractprofilesurfacegenerator.cpp:157 can return a nullpointer causing a segfault because qgsDoubleNear in visitPointsByRegularDistance in qgslinestring.cpp is called with a tolerance that is too small so that qgsDoubleNear always returns false. Solution: scale epsilon by the size of the numbers to be compared.
2025-05-29 13:37:03 -07:00
23 changed files with 245 additions and 186 deletions

View File

@ -979,7 +979,31 @@ if (WITH_CORE)
else()
# UNIX
set (DEFAULT_BIN_SUBDIR bin)
set (DEFAULT_CGIBIN_SUBDIR bin)
# From https://www.cyberciti.biz/faq/how-do-i-find-the-url-for-my-cgi-bin/
execute_process(COMMAND lsb_release -a OUTPUT_VARIABLE LSB_RELEASE_A)
if(EXISTS "/etc/fedora-release")
# in /var/www/cgi-bin
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD")
# in /usr/local/www/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
elseif (${CMAKE_HOST_SYSTEM_NAME} MATCHES "BSD")
# in /usr/local/libexec/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR libexec/cgi-bin)
elseif ("${LSB_RELEASE_A}" MATCHES "Ubuntu" OR "${LSB_RELEASE_A}" MATCHES "Debian" OR "${LSB_RELEASE_A}" MATCHES "Mint")
# in /usr/lib/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR lib/cgi-bin)
else()
# others: Red Hat/CentOS/Rocky/Alma Linux
# in /var/www/cgi-bin/
set (DEFAULT_CGIBIN_SUBDIR www/cgi-bin)
endif()
set (DEFAULT_LIB_SUBDIR lib${LIB_SUFFIX})
set (DEFAULT_DATA_SUBDIR share/qgis)
set (DEFAULT_LIBEXEC_SUBDIR lib${LIB_SUFFIX}/qgis)

View File

@ -659,7 +659,10 @@ class Repositories(QObject):
.strip()
)
if not qgisMaximumVersion:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
if qgisMinimumVersion[0] == "3" and supports_qt6:
qgisMaximumVersion = "4.99"
else:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
# if compatible, add the plugin to the list
if not pluginNodes.item(i).firstChildElement(
"disabled"
@ -845,7 +848,10 @@ class Plugins(QObject):
qgisMinimumVersion = "0"
qgisMaximumVersion = pluginMetadata("qgisMaximumVersion").strip()
if not qgisMaximumVersion:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
if qgisMinimumVersion[0] == "3" and supports_qt6:
qgisMaximumVersion = "4.99"
else:
qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
# if compatible, add the plugin to the list
if not isCompatible(
pyQgisVersion(), qgisMinimumVersion, qgisMaximumVersion

View File

@ -727,19 +727,25 @@ bool QgsPluginRegistry::checkPythonPlugin( const QString &packageName )
bool QgsPluginRegistry::isPythonPluginCompatible( const QString &packageName ) const
{
#ifdef WITH_BINDINGS
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
bool supportsQgis4 = true;
const QString supportsQt6 = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "supportsQt6" ) ).trimmed();
if ( supportsQt6.compare( QLatin1String( "YES" ), Qt::CaseInsensitive ) != 0 && supportsQt6.compare( QLatin1String( "TRUE" ), Qt::CaseInsensitive ) != 0 )
{
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
if ( !getenv( "QGIS_DISABLE_SUPPORTS_QT6_CHECK" ) )
{
return false;
}
}
#endif
supportsQgis4 = false;
}
const QString minVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMinimumVersion" ) );
// try to read qgisMaximumVersion. Note checkQgisVersion can cope with "__error__" value.
const QString maxVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMaximumVersion" ) );
QString maxVersion = mPythonUtils->getPluginMetadata( packageName, QStringLiteral( "qgisMaximumVersion" ) );
if ( maxVersion == QLatin1String( "__error__" ) && minVersion.startsWith( QLatin1String( "3." ) ) && supportsQgis4 )
{
maxVersion = QLatin1String( "4.99.0" );
}
return minVersion != QLatin1String( "__error__" ) && checkQgisVersion( minVersion, maxVersion );
#else
Q_UNUSED( packageName )

View File

@ -1532,6 +1532,7 @@ void QgsLineString::visitPointsByRegularDistance( const double distance, const s
double pZ = std::numeric_limits<double>::quiet_NaN();
double pM = std::numeric_limits<double>::quiet_NaN();
double nextPointDistance = distance;
const double eps = 4 * nextPointDistance * std::numeric_limits<double>::epsilon ();
for ( int i = 1; i < totalPoints; ++i )
{
double thisX = *x++;
@ -1540,7 +1541,7 @@ void QgsLineString::visitPointsByRegularDistance( const double distance, const s
double thisM = m ? *m++ : 0.0;
const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength, eps ) )
{
// point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );

View File

@ -73,7 +73,7 @@ bool QgsHttpHeaders::updateUrlQuery( QUrlQuery &uri ) const
{
for ( auto ite = mHeaders.constBegin(); ite != mHeaders.constEnd(); ++ite )
{
uri.addQueryItem( QgsHttpHeaders::PARAM_PREFIX + ite.key().toUtf8(), ite.value().toString().toUtf8() );
uri.addQueryItem( QgsHttpHeaders::PARAM_PREFIX + ite.key().toUtf8(), QUrl::toPercentEncoding( ite.value().toString() ) );
}
return true;
}

View File

@ -116,21 +116,6 @@ QStringList makeKeyTokens_( const QString &scope, const QString &key )
// be sure to include the canonical root node
keyTokens.push_front( QStringLiteral( "properties" ) );
//check validy of keys since an invalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
for ( int i = 0; i < keyTokens.size(); ++i )
{
const QString keyToken = keyTokens.at( i );
//invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
//note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
const thread_local QRegularExpression sInvalidRegexp = QRegularExpression( QStringLiteral( "([^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\-\\.0-9\\x{B7}\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]|^[^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}])" ) );
if ( keyToken.contains( sInvalidRegexp ) )
{
const QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
QgsMessageLog::logMessage( errorString, QString(), Qgis::MessageLevel::Critical );
}
}
return keyTokens;
}
@ -1322,20 +1307,20 @@ void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
* scope. "layers" is a list containing three string values.
*
* \code{.xml}
* <properties>
* <fsplugin>
* <foo type="int" >42</foo>
* <baz type="int" >1</baz>
* <layers type="QStringList" >
* <properties name="properties">
* <properties name="fsplugin">
* <properties name="foo" type="int" >42</properties>
* <properties name="baz" type="int" >1</properties>
* <properties name="layers" type="QStringList">
* <value>railroad</value>
* <value>airport</value>
* </layers>
* <xyqzzy type="int" >1</xyqzzy>
* <bar type="double" >123.456</bar>
* <feature_types type="QStringList" >
* </properties>
* <properties name="xyqzzy" type="int" >1</properties>
* <properties name="bar" type="double" >123.456</properties>
* <properties name="feature_types" type="QStringList">
* <value>type</value>
* </feature_types>
* </fsplugin>
* </properties>
* </properties>
* </properties>
* \endcode
*
@ -3992,10 +3977,25 @@ bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &pro
const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
if ( !propertiesElem.isNull() )
{
const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
if ( !absElem.isNull() )
QDomElement e = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) );
if ( e.isNull() )
{
useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
e = propertiesElem.firstChildElement( QStringLiteral( "properties" ) );
while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "Paths" ) )
e = e.nextSiblingElement( QStringLiteral( "properties" ) );
e = e.firstChildElement( QStringLiteral( "properties" ) );
while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "Absolute" ) )
e = e.nextSiblingElement( QStringLiteral( "properties" ) );
}
else
{
e = e.firstChildElement( QStringLiteral( "Absolute" ) );
}
if ( !e.isNull() )
{
useAbsolutePaths = e.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
}
}

View File

@ -233,15 +233,15 @@ bool QgsProjectPropertyValue::readXml( const QDomNode &keyNode )
// keyElement is created by parent QgsProjectPropertyKey
bool QgsProjectPropertyValue::writeXml( QString const &nodeName,
QDomElement &keyElement,
QDomDocument &document )
QDomElement &keyElement,
QDomDocument &document )
{
QDomElement valueElement = document.createElement( nodeName );
QDomElement valueElement = document.createElement( QStringLiteral( "properties" ) );
// remember the type so that we can rebuild it when the project is read in
valueElement.setAttribute( QStringLiteral( "name" ), nodeName );
valueElement.setAttribute( QStringLiteral( "type" ), mValue.typeName() );
// we handle string lists differently from other types in that we
// create a sequence of repeated elements to cover all the string list
// members; each value will be in a <value></value> tag.
@ -362,33 +362,41 @@ bool QgsProjectPropertyKey::readXml( const QDomNode &keyNode )
while ( i < subkeys.count() )
{
const QDomNode subkey = subkeys.item( i );
QString name;
if ( subkey.nodeName() == QStringLiteral( "properties" ) &&
subkey.hasAttributes() && // if we have attributes
subkey.isElement() && // and we're an element
subkey.toElement().hasAttribute( QStringLiteral( "name" ) ) ) // and we have a "name" attribute
name = subkey.toElement().attribute( QStringLiteral( "name" ) );
else
name = subkey.nodeName();
// if the current node is an element that has a "type" attribute,
// then we know it's a leaf node; i.e., a subkey _value_, and not
// a subkey
if ( subkeys.item( i ).hasAttributes() && // if we have attributes
subkeys.item( i ).isElement() && // and we're an element
subkeys.item( i ).toElement().hasAttribute( QStringLiteral( "type" ) ) ) // and we have a "type" attribute
if ( subkey.hasAttributes() && // if we have attributes
subkey.isElement() && // and we're an element
subkey.toElement().hasAttribute( QStringLiteral( "type" ) ) ) // and we have a "type" attribute
{
// then we're a key value
delete mProperties.take( subkeys.item( i ).nodeName() );
mProperties.insert( subkeys.item( i ).nodeName(), new QgsProjectPropertyValue );
//
delete mProperties.take( name );
mProperties.insert( name, new QgsProjectPropertyValue );
QDomNode subkey = subkeys.item( i );
if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
if ( !mProperties[name]->readXml( subkey ) )
{
QgsDebugError( QStringLiteral( "unable to parse key value %1" ).arg( subkeys.item( i ).nodeName() ) );
QgsDebugError( QStringLiteral( "unable to parse key value %1" ).arg( name ) );
}
}
else // otherwise it's a subkey, so just recurse on down the remaining keys
{
addKey( subkeys.item( i ).nodeName() );
addKey( name );
QDomNode subkey = subkeys.item( i );
if ( !mProperties[subkeys.item( i ).nodeName()]->readXml( subkey ) )
if ( !mProperties[name]->readXml( subkey ) )
{
QgsDebugError( QStringLiteral( "unable to parse subkey %1" ).arg( subkeys.item( i ).nodeName() ) );
QgsDebugError( QStringLiteral( "unable to parse subkey %1" ).arg( name ) );
}
}
@ -408,7 +416,8 @@ bool QgsProjectPropertyKey::writeXml( QString const &nodeName, QDomElement &elem
// If it's an _empty_ node (i.e., one with no properties) we need to emit
// an empty place holder; else create new Dom elements as necessary.
QDomElement keyElement = document.createElement( nodeName ); // Dom element for this property key
QDomElement keyElement = document.createElement( "properties" ); // Dom element for this property key
keyElement.toElement().setAttribute( QStringLiteral( "name" ), nodeName );
if ( ! mProperties.isEmpty() )
{

View File

@ -33,8 +33,7 @@ QgsProjectStorage *QgsProjectStorageRegistry::projectStorageFromUri( const QStri
for ( auto it = mBackends.constBegin(); it != mBackends.constEnd(); ++it )
{
QgsProjectStorage *storage = it.value();
const QString scheme = storage->type() + ':';
if ( uri.startsWith( scheme ) )
if ( uri.startsWith( storage->type() + ':' ) || uri.startsWith( storage->type() + "%3A" ) )
return storage;
}

View File

@ -701,17 +701,17 @@ QByteArray QgsDataSourceUri::encodedUri() const
QUrlQuery url;
for ( auto it = mParams.constBegin(); it != mParams.constEnd(); ++it )
{
url.addQueryItem( it.key(), it.value() );
url.addQueryItem( it.key(), QUrl::toPercentEncoding( it.value() ) );
}
if ( !mUsername.isEmpty() )
url.addQueryItem( QStringLiteral( "username" ), mUsername );
url.addQueryItem( QStringLiteral( "username" ), QUrl::toPercentEncoding( mUsername ) );
if ( !mPassword.isEmpty() )
url.addQueryItem( QStringLiteral( "password" ), mPassword );
url.addQueryItem( QStringLiteral( "password" ), QUrl::toPercentEncoding( mPassword ) );
if ( !mAuthConfigId.isEmpty() )
url.addQueryItem( QStringLiteral( "authcfg" ), mAuthConfigId );
url.addQueryItem( QStringLiteral( "authcfg" ), QUrl::toPercentEncoding( mAuthConfigId ) );
mHttpHeaders.updateUrlQuery( url );
@ -731,7 +731,7 @@ void QgsDataSourceUri::setEncodedUri( const QByteArray &uri )
mHttpHeaders.setFromUrlQuery( query );
const auto constQueryItems = query.queryItems();
const auto constQueryItems = query.queryItems( QUrl::ComponentFormattingOption::FullyDecoded );
for ( const QPair<QString, QString> &item : constQueryItems )
{
if ( !item.first.startsWith( QgsHttpHeaders::PARAM_PREFIX ) && item.first != QgsHttpHeaders::KEY_REFERER )
@ -928,7 +928,7 @@ QString QgsDataSourceUri::param( const QString &key ) const
else if ( key == QLatin1String( "authcfg" ) && !mAuthConfigId.isEmpty() )
return mAuthConfigId;
return mParams.value( key );
return mParams.value( key ).toUtf8();
}
QStringList QgsDataSourceUri::params( const QString &key ) const

View File

@ -147,7 +147,7 @@ QString QgsVectorTileProviderMetadata::absoluteToRelativeUri( const QString &uri
// relative path will become "file:./x.txt"
const QString relSrcUrl = context.pathResolver().writePath( sourceUrl.toLocalFile() );
dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( relSrcUrl ).toString() );
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( relSrcUrl ).toString( QUrl::DecodeReserved ) );
return dsUri.encodedUri();
}
}

View File

@ -316,7 +316,7 @@ QString QgsXyzVectorTileDataProviderMetadata::absoluteToRelativeUri( const QStri
// relative path will become "file:./x.txt"
const QString relSrcUrl = context.pathResolver().writePath( sourceUrl.toLocalFile() );
dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( relSrcUrl ).toString() );
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( relSrcUrl ).toString( QUrl::DecodeReserved ) );
return dsUri.encodedUri();
}
@ -335,7 +335,7 @@ QString QgsXyzVectorTileDataProviderMetadata::relativeToAbsoluteUri( const QStri
{
const QString absSrcUrl = context.pathResolver().readPath( sourceUrl.toLocalFile() );
dsUri.removeParam( QStringLiteral( "url" ) ); // needed because setParam() would insert second "url" key
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( absSrcUrl ).toString() );
dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( absSrcUrl ).toString( QUrl::DecodeReserved ) );
return dsUri.encodedUri();
}

View File

@ -933,7 +933,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() );

View File

@ -38,6 +38,7 @@ class TestQgsDataSourceUri : public QObject
void checkParameterKeys();
void checkRemovePassword();
void checkUnicodeUri();
void checkUriInUri();
};
void TestQgsDataSourceUri::checkparser_data()
@ -775,7 +776,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" ) );
@ -822,6 +823,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"

View File

@ -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;

View File

@ -187,11 +187,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'" );
@ -199,7 +202,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" );
}

View File

@ -60,7 +60,7 @@ void TestQgsSensorThingsConnection::encodeDecode()
data.password = QStringLiteral( "my_pw" );
data.httpHeaders.insert( QStringLiteral( "my_header" ), QStringLiteral( "value" ) );
QCOMPARE( QgsSensorThingsProviderConnection::encodedUri( data ), QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( QgsSensorThingsProviderConnection::encodedUri( data ), QStringLiteral( "url=http%3A%2F%2Ftesturl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( QgsSensorThingsProviderConnection::encodedLayerUri( data ), QStringLiteral( "user='my_user' password='my_pw' authcfg=my_auth url='http://testurl' http-header:my_header='value'" ) );
const QgsSensorThingsProviderConnection::Data data2 = QgsSensorThingsProviderConnection::decodedUri( QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
@ -93,7 +93,7 @@ void TestQgsSensorThingsConnection::testConnections()
// retrieve stored connection
conn = QgsSensorThingsProviderConnection( QStringLiteral( "my connection" ) );
QCOMPARE( conn.uri(), QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( conn.uri(), QStringLiteral( "url=http%3A%2F%2Ftesturl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
// add a second connection
QgsSensorThingsProviderConnection::Data data2;
@ -104,7 +104,7 @@ void TestQgsSensorThingsConnection::testConnections()
data2.httpHeaders.insert( QStringLiteral( "my_header" ), QStringLiteral( "value2" ) );
// construct connection using encoded uri
QgsSensorThingsProviderConnection conn2( QgsSensorThingsProviderConnection::encodedUri( data2 ), {} );
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" ) );
conn2.store( QStringLiteral( "second connection" ) );
// retrieve stored connections

View File

@ -61,8 +61,8 @@ void TestQgsTiledSceneConnection::encodeDecode()
data.password = QStringLiteral( "my_pw" );
data.httpHeaders.insert( QStringLiteral( "my_header" ), QStringLiteral( "value" ) );
QCOMPARE( QgsTiledSceneProviderConnection::encodedUri( data ), QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( QgsTiledSceneProviderConnection::encodedLayerUri( data ), QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( QgsTiledSceneProviderConnection::encodedUri( data ), QStringLiteral( "url=http%3A%2F%2Ftesturl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( QgsTiledSceneProviderConnection::encodedLayerUri( data ), QStringLiteral( "url=http%3A%2F%2Ftesturl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
const QgsTiledSceneProviderConnection::Data data2 = QgsTiledSceneProviderConnection::decodedUri( QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( data2.url, QStringLiteral( "http://testurl" ) );
@ -97,7 +97,7 @@ void TestQgsTiledSceneConnection::testConnections()
// retrieve stored connection
conn = QgsTiledSceneProviderConnection( QStringLiteral( "my connection" ) );
QCOMPARE( conn.uri(), QStringLiteral( "url=http://testurl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( conn.uri(), QStringLiteral( "url=http%3A%2F%2Ftesturl&username=my_user&password=my_pw&authcfg=my_auth&http-header:my_header=value" ) );
QCOMPARE( qgis::down_cast<QgsTiledSceneProviderConnection *>( &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<QgsTiledSceneProviderConnection *>( &conn2 )->providerKey(), QStringLiteral( "test_provider2" ) );
conn2.store( QStringLiteral( "second connection" ) );

View File

@ -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" ) );
}

View File

@ -260,11 +260,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<QgsProviderSublayerDetails> 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 ) );
@ -273,7 +274,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() );
@ -282,7 +283,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 ) );
@ -291,17 +292,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() );
@ -332,8 +335,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 );
auto layer = std::make_unique<QgsVectorTileLayer>( srcMbtiles );
QVERIFY( layer->isValid() );
@ -341,7 +345,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 );
@ -392,7 +396,7 @@ 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 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://www.example.com/%7Bz%7D/%7Bx%7D/%7By%7D.pbf";
auto layer = std::make_unique<QgsVectorTileLayer>( srcXyzLocal );
@ -400,7 +404,7 @@ void TestQgsVectorTileLayer::test_relativePathsXyz()
// 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
@ -436,7 +440,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 );
}
@ -458,22 +463,24 @@ void TestQgsVectorTileLayer::testVtpkProviderMetadata()
QVERIFY( vectorTileMetadata->querySublayers( QStringLiteral( "type=vtpk&url=%1/points.shp" ).arg( TEST_DATA_DIR ) ).isEmpty() );
// vtpk uris
QString localVtpkPath = QStringLiteral( "%1%2" ).arg( QUrl::toPercentEncoding( TEST_DATA_DIR ), QUrl::toPercentEncoding( QStringLiteral("/testvtpk.vtpk") ) );
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<QgsProviderSublayerDetails> 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 ).uri(), QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ) );
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( vectorTileMetadata->priorityForUri( QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ) ), 100 );
QCOMPARE( vectorTileMetadata->validLayerTypesForUri( QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ) ), {Qgis::LayerType::VectorTile} );
sublayers = vectorTileMetadata->querySublayers( QStringLiteral( "type=vtpk&url=%1" ).arg( localVtpkPath ) );
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 ).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
@ -500,7 +507,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 );
auto layer = std::make_unique<QgsVectorTileLayer>( srcVtpk );
QVERIFY( layer->isValid() );
@ -508,7 +517,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 );

View File

@ -469,8 +469,8 @@ 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";
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 );
}

View File

@ -65,84 +65,6 @@ class TestQgsProject(QgisTestCase):
QgisTestCase.__init__(self, methodName)
self.messageCaught = False
def test_makeKeyTokens_(self):
# see http://www.w3.org/TR/REC-xml/#d0e804 for a list of valid characters
invalidTokens = []
validTokens = []
# all test tokens will be generated by prepending or inserting characters to this token
validBase = "valid"
# some invalid characters, not allowed anywhere in a token
# note that '/' must not be added here because it is taken as a separator by makeKeyTokens_()
invalidChars = "+*,;<>|!$%()=?#\x01"
# generate the characters that are allowed at the start of a token (and at every other position)
validStartChars = ":_"
charRanges = [
(ord("a"), ord("z")),
(ord("A"), ord("Z")),
(0x00F8, 0x02FF),
(0x0370, 0x037D),
(0x037F, 0x1FFF),
(0x200C, 0x200D),
(0x2070, 0x218F),
(0x2C00, 0x2FEF),
(0x3001, 0xD7FF),
(0xF900, 0xFDCF),
(0xFDF0, 0xFFFD),
# (0x10000, 0xEFFFF), while actually valid, these are not yet accepted by makeKeyTokens_()
]
for r in charRanges:
for c in range(r[0], r[1]):
validStartChars += chr(c)
# generate the characters that are only allowed inside a token, not at the start
validInlineChars = "-.\xB7"
charRanges = [
(ord("0"), ord("9")),
(0x0300, 0x036F),
(0x203F, 0x2040),
]
for r in charRanges:
for c in range(r[0], r[1]):
validInlineChars += chr(c)
# test forbidden start characters
for c in invalidChars + validInlineChars:
invalidTokens.append(c + validBase)
# test forbidden inline characters
for c in invalidChars:
invalidTokens.append(validBase[:4] + c + validBase[4:])
# test each allowed start character
for c in validStartChars:
validTokens.append(c + validBase)
# test each allowed inline character
for c in validInlineChars:
validTokens.append(validBase[:4] + c + validBase[4:])
logger = QgsApplication.messageLog()
logger.messageReceived.connect(self.catchMessage)
prj = QgsProject.instance()
for token in validTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = f"valid token '{token}' not accepted"
assert not self.messageCaught, myMessage
for token in invalidTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = f"invalid token '{token}' accepted"
assert self.messageCaught, myMessage
logger.messageReceived.disconnect(self.catchMessage)
def catchMessage(self):
self.messageCaught = True

View File

@ -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):

View File

@ -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()