Skip to content

Commit

Permalink
Merge pull request #46825 from qgis/backport-46629-to-release-3_22
Browse files Browse the repository at this point in the history
[Backport release-3_22] Fix headers
  • Loading branch information
sbrunner committed Jan 18, 2022
2 parents 4f11be9 + 2b17552 commit e32083e
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 37 deletions.
6 changes: 6 additions & 0 deletions python/server/auto_generated/qgsserverrequest.sip.in
Expand Up @@ -57,6 +57,12 @@ class QgsServerRequest
X_QGIS_WCS_SERVICE_URL,
// The QGIS WMTS service URL
X_QGIS_WMTS_SERVICE_URL,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
ACCEPT,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
USER_AGENT,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
AUTHORIZATION,
};

QgsServerRequest();
Expand Down
36 changes: 19 additions & 17 deletions src/server/qgsfcgiserverrequest.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsfcgiserverrequest.h"
#include "qgsserverlogger.h"
#include "qgsmessagelog.h"
#include "qgsstringutils.h"
#include <fcgi_stdio.h>
#include <QDebug>

Expand Down Expand Up @@ -106,11 +107,17 @@ QgsFcgiServerRequest::QgsFcgiServerRequest()
setUrl( url );
setMethod( method );

// Get accept header for content-type negotiation
const char *accept = getenv( "HTTP_ACCEPT" );
if ( accept )
// Fill the headers dictionary
for ( const auto &headerKey : qgsEnumMap<QgsServerRequest::RequestHeader>().values() )
{
setHeader( QStringLiteral( "Accept" ), accept );
const QString headerName = QgsStringUtils::capitalize(
QString( headerKey ).replace( QLatin1Char( '_' ), QLatin1Char( ' ' ) ), QgsStringUtils::Capitalization::TitleCase
).replace( QLatin1Char( ' ' ), QLatin1Char( '-' ) );
const char *result = getenv( QStringLiteral( "HTTP_%1" ).arg( headerKey ).toStdString().c_str() );
if ( result && strlen( result ) > 0 )
{
setHeader( headerName, result );
}
}

// Output debug infos
Expand Down Expand Up @@ -223,41 +230,36 @@ void QgsFcgiServerRequest::printRequestInfos( const QUrl &url )
QStringLiteral( "CONTENT_TYPE" ),
QStringLiteral( "REQUEST_METHOD" ),
QStringLiteral( "AUTH_TYPE" ),
QStringLiteral( "HTTP_ACCEPT" ),
QStringLiteral( "HTTP_USER_AGENT" ),
QStringLiteral( "HTTP_PROXY" ),
QStringLiteral( "NO_PROXY" ),
QStringLiteral( "HTTP_AUTHORIZATION" ),
QStringLiteral( "QGIS_PROJECT_FILE" ),
QStringLiteral( "QGIS_SERVER_IGNORE_BAD_LAYERS" ),
QStringLiteral( "QGIS_SERVER_SERVICE_URL" ),
QStringLiteral( "QGIS_SERVER_WMS_SERVICE_URL" ),
QStringLiteral( "QGIS_SERVER_WFS_SERVICE_URL" ),
QStringLiteral( "QGIS_SERVER_WMTS_SERVICE_URL" ),
QStringLiteral( "QGIS_SERVER_WCS_SERVICE_URL" ),
QStringLiteral( "HTTP_X_QGIS_SERVICE_URL" ),
QStringLiteral( "HTTP_X_QGIS_WMS_SERVICE_URL" ),
QStringLiteral( "HTTP_X_QGIS_WFS_SERVICE_URL" ),
QStringLiteral( "HTTP_X_QGIS_WCS_SERVICE_URL" ),
QStringLiteral( "HTTP_X_QGIS_WMTS_SERVICE_URL" ),
QStringLiteral( "HTTP_FORWARDED" ),
QStringLiteral( "HTTP_X_FORWARDED_HOST" ),
QStringLiteral( "HTTP_X_FORWARDED_PROTO" ),
QStringLiteral( "HTTP_HOST" ),
QStringLiteral( "SERVER_PROTOCOL" )
};

QgsMessageLog::logMessage( QStringLiteral( "Request URL: %2" ).arg( url.url() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );

QgsMessageLog::logMessage( QStringLiteral( "Environment:" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
QgsMessageLog::logMessage( QStringLiteral( "------------------------------------------------" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );

for ( const auto &envVar : envVars )
{
if ( getenv( envVar.toStdString().c_str() ) )
{
QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( envVar ).arg( QString( getenv( envVar.toStdString().c_str() ) ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
}
}

qDebug() << "Headers:";
qDebug() << "------------------------------------------------";
for ( const auto &headerName : headers().keys() )
{
qDebug() << headerName << ": " << headers().value( headerName );
}
}

QString QgsFcgiServerRequest::header( const QString &name ) const
Expand Down
2 changes: 1 addition & 1 deletion src/server/qgsserverprojectutils.cpp
Expand Up @@ -355,7 +355,7 @@ QString QgsServerProjectUtils::serviceUrl( const QString &service, const QgsServ
QString proto;
QString host;

QString forwarded = request.header( QgsServerRequest::FORWARDED );
QString forwarded = request.header( QgsServerRequest::FORWARDED );
if ( ! forwarded.isEmpty() )
{
forwarded = forwarded.split( QLatin1Char( ',' ) )[0];
Expand Down
22 changes: 7 additions & 15 deletions src/server/qgsserverrequest.cpp
Expand Up @@ -18,6 +18,7 @@
***************************************************************************/

#include "qgsserverrequest.h"
#include "qgsstringutils.h"
#include <QUrlQuery>


Expand All @@ -32,19 +33,7 @@ QgsServerRequest::QgsServerRequest( const QUrl &url, Method method, const Header
, mBaseUrl( url )
, mMethod( method )
, mHeaders( headers )
, mRequestHeaderConv()
{
mRequestHeaderConv.insert( HOST, QStringLiteral( "Host" ) );
mRequestHeaderConv.insert( FORWARDED, QStringLiteral( "Forwarded" ) );
mRequestHeaderConv.insert( X_FORWARDED_FOR, QStringLiteral( "X-Forwarded-For" ) );
mRequestHeaderConv.insert( X_FORWARDED_HOST, QStringLiteral( "X-Forwarded-Host" ) );
mRequestHeaderConv.insert( X_FORWARDED_PROTO, QStringLiteral( "X-Forwarded-Proto" ) );
mRequestHeaderConv.insert( X_QGIS_SERVICE_URL, QStringLiteral( "X-Qgis-Service-Url" ) );
mRequestHeaderConv.insert( X_QGIS_WMS_SERVICE_URL, QStringLiteral( "X-Qgis-Wms-Service-Url" ) );
mRequestHeaderConv.insert( X_QGIS_WFS_SERVICE_URL, QStringLiteral( "X-Qgis-Wfs-Service-Url" ) );
mRequestHeaderConv.insert( X_QGIS_WCS_SERVICE_URL, QStringLiteral( "X-Qgis-Wcs-Service-Url" ) );
mRequestHeaderConv.insert( X_QGIS_WMTS_SERVICE_URL, QStringLiteral( "X-Qgis-Wmts-Service-Url" ) );

{
mParams.load( QUrlQuery( url ) );
}

Expand All @@ -55,7 +44,6 @@ QgsServerRequest::QgsServerRequest( const QgsServerRequest &other )
, mMethod( other.mMethod )
, mHeaders( other.mHeaders )
, mParams( other.mParams )
, mRequestHeaderConv( other.mRequestHeaderConv )
{
}

Expand All @@ -73,7 +61,11 @@ QString QgsServerRequest::header( const QString &name ) const

QString QgsServerRequest::header( const QgsServerRequest::RequestHeader &headerEnum ) const
{
return header( mRequestHeaderConv[ headerEnum ] );
const QString headerKey = QString( qgsEnumValueToKey<QgsServerRequest::RequestHeader>( headerEnum ) );
const QString headerName = QgsStringUtils::capitalize(
QString( headerKey ).replace( QLatin1Char( '_' ), QLatin1Char( ' ' ) ), QgsStringUtils::Capitalization::TitleCase
).replace( QLatin1Char( ' ' ), QLatin1Char( '-' ) );
return header( headerName );
}

void QgsServerRequest::setHeader( const QString &name, const QString &value )
Expand Down
7 changes: 6 additions & 1 deletion src/server/qgsserverrequest.h
Expand Up @@ -84,6 +84,12 @@ class SERVER_EXPORT QgsServerRequest
X_QGIS_WCS_SERVICE_URL,
// The QGIS WMTS service URL
X_QGIS_WMTS_SERVICE_URL,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
ACCEPT,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
USER_AGENT,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
AUTHORIZATION,
};
Q_ENUM( RequestHeader )

Expand Down Expand Up @@ -270,7 +276,6 @@ class SERVER_EXPORT QgsServerRequest
// to support lazy initialization
mutable Headers mHeaders;
QgsServerParameters mParams;
QMap<RequestHeader, QString> mRequestHeaderConv;
};

#endif
3 changes: 0 additions & 3 deletions tests/src/python/test_qgsserver.py
Expand Up @@ -496,9 +496,6 @@ def test_wcs_getcapabilities_url(self):
item_found = False
for item in str(r).split("\\n"):
if "OnlineResource" in item:
print(item)
print(header_name)
print(header_value)
self.assertEqual(header_value in item, True)
item_found = True
self.assertTrue(item_found)
Expand Down
27 changes: 27 additions & 0 deletions tests/src/python/test_qgsserver_request.py
Expand Up @@ -215,6 +215,33 @@ def test_add_parameters(self):
self.assertEqual(request.parameter('FOOBAR'), 'foobar')
self.assertEqual(request.parameter('UNKNOWN'), '')

def test_headers(self):
"""Tests that the headers are working in Fcgi mode"""
for header, env, enum, value in (
("Host", "HTTP_HOST", QgsServerRequest.HOST, "example.com"),
("Forwarded", "HTTP_FORWARDED", QgsServerRequest.FORWARDED, "aaa"),
("X-Forwarded-For", "HTTP_X_FORWARDED_FOR", QgsServerRequest.X_FORWARDED_FOR, "bbb"),
("X-Forwarded-Host", "HTTP_X_FORWARDED_HOST", QgsServerRequest.X_FORWARDED_HOST, "ccc"),
("X-Forwarded-Proto", "HTTP_X_FORWARDED_PROTO", QgsServerRequest.X_FORWARDED_PROTO, "ddd"),
("X-Qgis-Service-Url", "HTTP_X_QGIS_SERVICE_URL", QgsServerRequest.X_QGIS_SERVICE_URL, "eee"),
("X-Qgis-Wms-Service-Url", "HTTP_X_QGIS_WMS_SERVICE_URL", QgsServerRequest.X_QGIS_WMS_SERVICE_URL, "fff"),
("X-Qgis-Wfs-Service-Url", "HTTP_X_QGIS_WFS_SERVICE_URL", QgsServerRequest.X_QGIS_WFS_SERVICE_URL, "ggg"),
("X-Qgis-Wcs-Service-Url", "HTTP_X_QGIS_WCS_SERVICE_URL", QgsServerRequest.X_QGIS_WCS_SERVICE_URL, "hhh"),
("X-Qgis-Wmts-Service-Url", "HTTP_X_QGIS_WMTS_SERVICE_URL", QgsServerRequest.X_QGIS_WMTS_SERVICE_URL, "iii"),
("Accept", "HTTP_ACCEPT", QgsServerRequest.ACCEPT, "jjj"),
("User-Agent", "HTTP_USER_AGENT", QgsServerRequest.USER_AGENT, "kkk"),
("Authorization", "HTTP_AUTHORIZATION", QgsServerRequest.AUTHORIZATION, "lll"),
):
try:
os.environ[env] = value
request = QgsFcgiServerRequest()
self.assertEquals(request.headers(), {header: value})
request = QgsServerRequest(request)
self.assertEquals(request.headers(), {header: value})
self.assertEquals(request.header(enum), value)
finally:
del os.environ[env]


if __name__ == '__main__':
unittest.main()
87 changes: 87 additions & 0 deletions tests/src/python/test_qgsserver_service_url_env.py
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
"""
QGIS Unit tests for QgsServer with service URL defined in the environment variables
"""
__author__ = 'Stéphane Brunner'
__date__ = '12/01/2021'
__copyright__ = 'Copyright 2021, The QGIS Project'

import os

# Deterministic XML
os.environ['QT_HASH_SEED'] = '1'

import urllib.request
import urllib.parse
import urllib.error

from qgis.server import QgsServer
from qgis.core import QgsFontUtils
from qgis.testing import unittest, start_app
from utilities import unitTestDataPath


start_app()


class TestQgsServerServiceUrlEnv(unittest.TestCase):

def setUp(self):
"""Create the server instance"""
self.fontFamily = QgsFontUtils.standardTestFontFamily()
QgsFontUtils.loadStandardTestFonts(['All'])

self.testdata_path = unitTestDataPath('qgis_server') + '/'
project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")

del os.environ['QUERY_STRING']
os.environ['QGIS_PROJECT_FILE'] = project
# Disable landing page API to test standard legacy XML responses in case of errors
os.environ["QGIS_SERVER_DISABLED_APIS"] = "Landing Page"

self.server = QgsServer()

def tearDown(self):
"""Cleanup env"""

super().tearDown()
for env in ["QGIS_SERVER_DISABLED_APIS", "QGIS_PROJECT_FILE"]:
if env in os.environ:
del os.environ[env]

"""Tests container"""

def test_getcapabilities_url(self):

# Service URL in environment
for env_name, env_value, service in (
("QGIS_SERVER_SERVICE_URL", "http://test1", "WMS"),
("QGIS_SERVER_WMS_SERVICE_URL", "http://test2", "WMS")
("QGIS_SERVER_SERVICE_URL", "http://test3", "WFS", "href="),
("QGIS_SERVER_WFS_SERVICE_URL", "http://test4", "WFS", "href=")
("QGIS_SERVER_SERVICE_URL", "http://test5", "WCS"),
("QGIS_SERVER_WCS_SERVICE_URL", "http://test6", "WCS")
("QGIS_SERVER_SERVICE_URL", "http://test7", "WMTS"),
("QGIS_SERVER_WMTS_SERVICE_URL", "http://test8", "WMTS")
):
try:
os.environ[env_name] = env_value
qs = "?" + "&".join(["%s=%s" % i for i in list({
"SERVICE": service,
"REQUEST": "GetCapabilities",
}.items())])

r, _ = self._result(self._execute_request(qs))

item_found = False
for item in str(r).split("\\n"):
if "href=" in item:
self.assertEqual(env_value in item, True)
item_found = True
self.assertTrue(item_found)
finally:
del os.environ[env_name]


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

0 comments on commit e32083e

Please sign in to comment.