Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server OGC API: fix url path match #45450

Merged
merged 4 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions python/server/auto_generated/qgsserverapicontext.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ Returns the API root path
void setRequest( const QgsServerRequest *request );
%Docstring
Sets context request to ``request``
%End

QString handlerPath( ) const;
%Docstring
Returns the handler component of the URL path, i.e. the part of the path that comes
after the API path.

.. versionadded:: 3.22
%End

};
Expand Down
3 changes: 2 additions & 1 deletion python/server/auto_generated/qgsserverogcapi.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ Registers an OGC API ``handler``, ownership of the handler is transferred to the

static QUrl sanitizeUrl( const QUrl &url );
%Docstring
Returns a sanitized ``url`` with extra slashes removed
Returns a sanitized ``url`` with extra slashes removed and the path URL component that
always starts with a slash.
%End

static std::string relToString( const QgsServerOgcApi::Rel &rel );
Expand Down
14 changes: 14 additions & 0 deletions src/server/qgsserverapicontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,17 @@ void QgsServerApiContext::setRequest( const QgsServerRequest *request )
{
mRequest = request;
}

QString QgsServerApiContext::handlerPath() const
{
const QUrl url { request()->url() };
const QString urlBasePath { matchedPath() };
if ( ! urlBasePath.isEmpty() )
{
return url.path().mid( urlBasePath.length() );
}
else
{
return url.path();
}
}
8 changes: 8 additions & 0 deletions src/server/qgsserverapicontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ class SERVER_EXPORT QgsServerApiContext
*/
void setRequest( const QgsServerRequest *request );

/**
* Returns the handler component of the URL path, i.e. the part of the path that comes
* after the API path.
*
* \since QGIS 3.22
*/
QString handlerPath( ) const;

private:

QString mApiRootPath;
Expand Down
7 changes: 6 additions & 1 deletion src/server/qgsserverogcapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,18 @@ QUrl QgsServerOgcApi::sanitizeUrl( const QUrl &url )
{
u.setPath( u.path().replace( QLatin1String( "//" ), QChar( '/' ) ) );
}
// Make sure the path starts with '/'
if ( !u.path().startsWith( '/' ) )
{
u.setPath( u.path().prepend( '/' ) );
}
return u;
}

void QgsServerOgcApi::executeRequest( const QgsServerApiContext &context ) const
{
// Get url
const auto path { sanitizeUrl( context.request()->url() ).path() };
const auto path { sanitizeUrl( context.handlerPath( ) ).path() };
// Find matching handler
auto hasMatch { false };
for ( const auto &handler : mHandlers )
Expand Down
3 changes: 2 additions & 1 deletion src/server/qgsserverogcapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ class SERVER_EXPORT QgsServerOgcApi : public QgsServerApi
void registerHandler( QgsServerOgcApiHandler *handler SIP_TRANSFER );

/**
* Returns a sanitized \a url with extra slashes removed
* Returns a sanitized \a url with extra slashes removed and the path URL component that
* always starts with a slash.
*/
static QUrl sanitizeUrl( const QUrl &url );

Expand Down
5 changes: 3 additions & 2 deletions src/server/qgsserverogcapihandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context )
// value() calls the validators and throws an exception if validation fails
result[p.name()] = p.value( context );
}
const auto match { path().match( context.request()->url().toString() ) };
const auto sanitizedPath { QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path() };
const auto match { path().match( sanitizedPath ) };
if ( match.hasMatch() )
{
const auto constNamed { path().namedCaptureGroups() };
Expand Down Expand Up @@ -140,7 +141,7 @@ std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, co
{
QUrl url { context.request()->url() };
QString urlBasePath { context.matchedPath() };
const auto match { path().match( url.path() ) };
const auto match { path().match( QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path( ) ) };
if ( match.captured().count() > 0 )
{
url.setPath( urlBasePath + match.captured( 0 ) );
Expand Down
55 changes: 55 additions & 0 deletions tests/src/python/test_qgsserver_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from qgis.server import (
QgsBufferServerRequest,
QgsBufferServerResponse,
QgsServer,
QgsServerApi,
QgsServerApiBadRequestException,
QgsServerQueryStringParameter,
Expand Down Expand Up @@ -1749,6 +1750,36 @@ def templatePath(self, context):
return self.templatePathOverride


class Handler4(QgsServerOgcApiHandler):

def path(self):
return QtCore.QRegularExpression("/(?P<tilemapid>[^/]+)")

def operationId(self):
return "handler4"

def summary(self):
return "Fourth of its name"

def description(self):
return "The fourth handler ever"

def linkTitle(self):
return "Handler Four Link Title"

def linkType(self):
return QgsServerOgcApi.data

def handleRequest(self, context):
"""Simple mirror: returns the parameters"""

self.params = self.values(context)
self.write(self.params, context)

def parameters(self, context):
return []


class HandlerException(QgsServerOgcApiHandler):

def __init__(self):
Expand Down Expand Up @@ -2000,6 +2031,30 @@ def testOgcApiHandlerException(self):

del(project)

def test_path_capture(self):
"""Test issue GH #45439"""

api = QgsServerOgcApi(self.server.serverInterface(),
'/api4', 'apifour', 'a fourth api', '1.2')

h4 = Handler4()
api.registerHandler(h4)

request = QgsBufferServerRequest(
'http://localhost:19876/api4/france_parts.json?MAP=france_parts')
response = QgsBufferServerResponse()

server = QgsServer()
iface = server.serverInterface()
iface.serviceRegistry().registerApi(api)

server.handleRequest(request, response)

self.assertEqual(h4.params, {'tilemapid': 'france_parts.json'})

ctx = QgsServerApiContext(api.rootPath(), request, response, None, iface)
self.assertEqual(h4.href(ctx), 'http://localhost:19876/api4/france_parts?MAP=france_parts')


if __name__ == '__main__':
unittest.main()