Skip to content

Commit e32083e

Browse files
authored
Merge pull request #46825 from qgis/backport-46629-to-release-3_22
[Backport release-3_22] Fix headers
2 parents 4f11be9 + 2b17552 commit e32083e

8 files changed

+153
-37
lines changed

python/server/auto_generated/qgsserverrequest.sip.in

+6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class QgsServerRequest
5757
X_QGIS_WCS_SERVICE_URL,
5858
// The QGIS WMTS service URL
5959
X_QGIS_WMTS_SERVICE_URL,
60+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
61+
ACCEPT,
62+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
63+
USER_AGENT,
64+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
65+
AUTHORIZATION,
6066
};
6167

6268
QgsServerRequest();

src/server/qgsfcgiserverrequest.cpp

+19-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "qgsfcgiserverrequest.h"
2222
#include "qgsserverlogger.h"
2323
#include "qgsmessagelog.h"
24+
#include "qgsstringutils.h"
2425
#include <fcgi_stdio.h>
2526
#include <QDebug>
2627

@@ -106,11 +107,17 @@ QgsFcgiServerRequest::QgsFcgiServerRequest()
106107
setUrl( url );
107108
setMethod( method );
108109

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

116123
// Output debug infos
@@ -223,41 +230,36 @@ void QgsFcgiServerRequest::printRequestInfos( const QUrl &url )
223230
QStringLiteral( "CONTENT_TYPE" ),
224231
QStringLiteral( "REQUEST_METHOD" ),
225232
QStringLiteral( "AUTH_TYPE" ),
226-
QStringLiteral( "HTTP_ACCEPT" ),
227-
QStringLiteral( "HTTP_USER_AGENT" ),
228233
QStringLiteral( "HTTP_PROXY" ),
229234
QStringLiteral( "NO_PROXY" ),
230-
QStringLiteral( "HTTP_AUTHORIZATION" ),
231235
QStringLiteral( "QGIS_PROJECT_FILE" ),
232236
QStringLiteral( "QGIS_SERVER_IGNORE_BAD_LAYERS" ),
233237
QStringLiteral( "QGIS_SERVER_SERVICE_URL" ),
234238
QStringLiteral( "QGIS_SERVER_WMS_SERVICE_URL" ),
235239
QStringLiteral( "QGIS_SERVER_WFS_SERVICE_URL" ),
236240
QStringLiteral( "QGIS_SERVER_WMTS_SERVICE_URL" ),
237241
QStringLiteral( "QGIS_SERVER_WCS_SERVICE_URL" ),
238-
QStringLiteral( "HTTP_X_QGIS_SERVICE_URL" ),
239-
QStringLiteral( "HTTP_X_QGIS_WMS_SERVICE_URL" ),
240-
QStringLiteral( "HTTP_X_QGIS_WFS_SERVICE_URL" ),
241-
QStringLiteral( "HTTP_X_QGIS_WCS_SERVICE_URL" ),
242-
QStringLiteral( "HTTP_X_QGIS_WMTS_SERVICE_URL" ),
243-
QStringLiteral( "HTTP_FORWARDED" ),
244-
QStringLiteral( "HTTP_X_FORWARDED_HOST" ),
245-
QStringLiteral( "HTTP_X_FORWARDED_PROTO" ),
246-
QStringLiteral( "HTTP_HOST" ),
247242
QStringLiteral( "SERVER_PROTOCOL" )
248243
};
249244

250245
QgsMessageLog::logMessage( QStringLiteral( "Request URL: %2" ).arg( url.url() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
246+
251247
QgsMessageLog::logMessage( QStringLiteral( "Environment:" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
252248
QgsMessageLog::logMessage( QStringLiteral( "------------------------------------------------" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
253-
254249
for ( const auto &envVar : envVars )
255250
{
256251
if ( getenv( envVar.toStdString().c_str() ) )
257252
{
258253
QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( envVar ).arg( QString( getenv( envVar.toStdString().c_str() ) ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
259254
}
260255
}
256+
257+
qDebug() << "Headers:";
258+
qDebug() << "------------------------------------------------";
259+
for ( const auto &headerName : headers().keys() )
260+
{
261+
qDebug() << headerName << ": " << headers().value( headerName );
262+
}
261263
}
262264

263265
QString QgsFcgiServerRequest::header( const QString &name ) const

src/server/qgsserverprojectutils.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ QString QgsServerProjectUtils::serviceUrl( const QString &service, const QgsServ
355355
QString proto;
356356
QString host;
357357

358-
QString forwarded = request.header( QgsServerRequest::FORWARDED );
358+
QString forwarded = request.header( QgsServerRequest::FORWARDED );
359359
if ( ! forwarded.isEmpty() )
360360
{
361361
forwarded = forwarded.split( QLatin1Char( ',' ) )[0];

src/server/qgsserverrequest.cpp

+7-15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
***************************************************************************/
1919

2020
#include "qgsserverrequest.h"
21+
#include "qgsstringutils.h"
2122
#include <QUrlQuery>
2223

2324

@@ -32,19 +33,7 @@ QgsServerRequest::QgsServerRequest( const QUrl &url, Method method, const Header
3233
, mBaseUrl( url )
3334
, mMethod( method )
3435
, mHeaders( headers )
35-
, mRequestHeaderConv()
36-
{
37-
mRequestHeaderConv.insert( HOST, QStringLiteral( "Host" ) );
38-
mRequestHeaderConv.insert( FORWARDED, QStringLiteral( "Forwarded" ) );
39-
mRequestHeaderConv.insert( X_FORWARDED_FOR, QStringLiteral( "X-Forwarded-For" ) );
40-
mRequestHeaderConv.insert( X_FORWARDED_HOST, QStringLiteral( "X-Forwarded-Host" ) );
41-
mRequestHeaderConv.insert( X_FORWARDED_PROTO, QStringLiteral( "X-Forwarded-Proto" ) );
42-
mRequestHeaderConv.insert( X_QGIS_SERVICE_URL, QStringLiteral( "X-Qgis-Service-Url" ) );
43-
mRequestHeaderConv.insert( X_QGIS_WMS_SERVICE_URL, QStringLiteral( "X-Qgis-Wms-Service-Url" ) );
44-
mRequestHeaderConv.insert( X_QGIS_WFS_SERVICE_URL, QStringLiteral( "X-Qgis-Wfs-Service-Url" ) );
45-
mRequestHeaderConv.insert( X_QGIS_WCS_SERVICE_URL, QStringLiteral( "X-Qgis-Wcs-Service-Url" ) );
46-
mRequestHeaderConv.insert( X_QGIS_WMTS_SERVICE_URL, QStringLiteral( "X-Qgis-Wmts-Service-Url" ) );
47-
36+
{
4837
mParams.load( QUrlQuery( url ) );
4938
}
5039

@@ -55,7 +44,6 @@ QgsServerRequest::QgsServerRequest( const QgsServerRequest &other )
5544
, mMethod( other.mMethod )
5645
, mHeaders( other.mHeaders )
5746
, mParams( other.mParams )
58-
, mRequestHeaderConv( other.mRequestHeaderConv )
5947
{
6048
}
6149

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

7462
QString QgsServerRequest::header( const QgsServerRequest::RequestHeader &headerEnum ) const
7563
{
76-
return header( mRequestHeaderConv[ headerEnum ] );
64+
const QString headerKey = QString( qgsEnumValueToKey<QgsServerRequest::RequestHeader>( headerEnum ) );
65+
const QString headerName = QgsStringUtils::capitalize(
66+
QString( headerKey ).replace( QLatin1Char( '_' ), QLatin1Char( ' ' ) ), QgsStringUtils::Capitalization::TitleCase
67+
).replace( QLatin1Char( ' ' ), QLatin1Char( '-' ) );
68+
return header( headerName );
7769
}
7870

7971
void QgsServerRequest::setHeader( const QString &name, const QString &value )

src/server/qgsserverrequest.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ class SERVER_EXPORT QgsServerRequest
8484
X_QGIS_WCS_SERVICE_URL,
8585
// The QGIS WMTS service URL
8686
X_QGIS_WMTS_SERVICE_URL,
87+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
88+
ACCEPT,
89+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
90+
USER_AGENT,
91+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
92+
AUTHORIZATION,
8793
};
8894
Q_ENUM( RequestHeader )
8995

@@ -270,7 +276,6 @@ class SERVER_EXPORT QgsServerRequest
270276
// to support lazy initialization
271277
mutable Headers mHeaders;
272278
QgsServerParameters mParams;
273-
QMap<RequestHeader, QString> mRequestHeaderConv;
274279
};
275280

276281
#endif

tests/src/python/test_qgsserver.py

-3
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,6 @@ def test_wcs_getcapabilities_url(self):
496496
item_found = False
497497
for item in str(r).split("\\n"):
498498
if "OnlineResource" in item:
499-
print(item)
500-
print(header_name)
501-
print(header_value)
502499
self.assertEqual(header_value in item, True)
503500
item_found = True
504501
self.assertTrue(item_found)

tests/src/python/test_qgsserver_request.py

+27
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,33 @@ def test_add_parameters(self):
215215
self.assertEqual(request.parameter('FOOBAR'), 'foobar')
216216
self.assertEqual(request.parameter('UNKNOWN'), '')
217217

218+
def test_headers(self):
219+
"""Tests that the headers are working in Fcgi mode"""
220+
for header, env, enum, value in (
221+
("Host", "HTTP_HOST", QgsServerRequest.HOST, "example.com"),
222+
("Forwarded", "HTTP_FORWARDED", QgsServerRequest.FORWARDED, "aaa"),
223+
("X-Forwarded-For", "HTTP_X_FORWARDED_FOR", QgsServerRequest.X_FORWARDED_FOR, "bbb"),
224+
("X-Forwarded-Host", "HTTP_X_FORWARDED_HOST", QgsServerRequest.X_FORWARDED_HOST, "ccc"),
225+
("X-Forwarded-Proto", "HTTP_X_FORWARDED_PROTO", QgsServerRequest.X_FORWARDED_PROTO, "ddd"),
226+
("X-Qgis-Service-Url", "HTTP_X_QGIS_SERVICE_URL", QgsServerRequest.X_QGIS_SERVICE_URL, "eee"),
227+
("X-Qgis-Wms-Service-Url", "HTTP_X_QGIS_WMS_SERVICE_URL", QgsServerRequest.X_QGIS_WMS_SERVICE_URL, "fff"),
228+
("X-Qgis-Wfs-Service-Url", "HTTP_X_QGIS_WFS_SERVICE_URL", QgsServerRequest.X_QGIS_WFS_SERVICE_URL, "ggg"),
229+
("X-Qgis-Wcs-Service-Url", "HTTP_X_QGIS_WCS_SERVICE_URL", QgsServerRequest.X_QGIS_WCS_SERVICE_URL, "hhh"),
230+
("X-Qgis-Wmts-Service-Url", "HTTP_X_QGIS_WMTS_SERVICE_URL", QgsServerRequest.X_QGIS_WMTS_SERVICE_URL, "iii"),
231+
("Accept", "HTTP_ACCEPT", QgsServerRequest.ACCEPT, "jjj"),
232+
("User-Agent", "HTTP_USER_AGENT", QgsServerRequest.USER_AGENT, "kkk"),
233+
("Authorization", "HTTP_AUTHORIZATION", QgsServerRequest.AUTHORIZATION, "lll"),
234+
):
235+
try:
236+
os.environ[env] = value
237+
request = QgsFcgiServerRequest()
238+
self.assertEquals(request.headers(), {header: value})
239+
request = QgsServerRequest(request)
240+
self.assertEquals(request.headers(), {header: value})
241+
self.assertEquals(request.header(enum), value)
242+
finally:
243+
del os.environ[env]
244+
218245

219246
if __name__ == '__main__':
220247
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
QGIS Unit tests for QgsServer with service URL defined in the environment variables
4+
"""
5+
__author__ = 'Stéphane Brunner'
6+
__date__ = '12/01/2021'
7+
__copyright__ = 'Copyright 2021, The QGIS Project'
8+
9+
import os
10+
11+
# Deterministic XML
12+
os.environ['QT_HASH_SEED'] = '1'
13+
14+
import urllib.request
15+
import urllib.parse
16+
import urllib.error
17+
18+
from qgis.server import QgsServer
19+
from qgis.core import QgsFontUtils
20+
from qgis.testing import unittest, start_app
21+
from utilities import unitTestDataPath
22+
23+
24+
start_app()
25+
26+
27+
class TestQgsServerServiceUrlEnv(unittest.TestCase):
28+
29+
def setUp(self):
30+
"""Create the server instance"""
31+
self.fontFamily = QgsFontUtils.standardTestFontFamily()
32+
QgsFontUtils.loadStandardTestFonts(['All'])
33+
34+
self.testdata_path = unitTestDataPath('qgis_server') + '/'
35+
project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")
36+
37+
del os.environ['QUERY_STRING']
38+
os.environ['QGIS_PROJECT_FILE'] = project
39+
# Disable landing page API to test standard legacy XML responses in case of errors
40+
os.environ["QGIS_SERVER_DISABLED_APIS"] = "Landing Page"
41+
42+
self.server = QgsServer()
43+
44+
def tearDown(self):
45+
"""Cleanup env"""
46+
47+
super().tearDown()
48+
for env in ["QGIS_SERVER_DISABLED_APIS", "QGIS_PROJECT_FILE"]:
49+
if env in os.environ:
50+
del os.environ[env]
51+
52+
"""Tests container"""
53+
54+
def test_getcapabilities_url(self):
55+
56+
# Service URL in environment
57+
for env_name, env_value, service in (
58+
("QGIS_SERVER_SERVICE_URL", "http://test1", "WMS"),
59+
("QGIS_SERVER_WMS_SERVICE_URL", "http://test2", "WMS")
60+
("QGIS_SERVER_SERVICE_URL", "http://test3", "WFS", "href="),
61+
("QGIS_SERVER_WFS_SERVICE_URL", "http://test4", "WFS", "href=")
62+
("QGIS_SERVER_SERVICE_URL", "http://test5", "WCS"),
63+
("QGIS_SERVER_WCS_SERVICE_URL", "http://test6", "WCS")
64+
("QGIS_SERVER_SERVICE_URL", "http://test7", "WMTS"),
65+
("QGIS_SERVER_WMTS_SERVICE_URL", "http://test8", "WMTS")
66+
):
67+
try:
68+
os.environ[env_name] = env_value
69+
qs = "?" + "&".join(["%s=%s" % i for i in list({
70+
"SERVICE": service,
71+
"REQUEST": "GetCapabilities",
72+
}.items())])
73+
74+
r, _ = self._result(self._execute_request(qs))
75+
76+
item_found = False
77+
for item in str(r).split("\\n"):
78+
if "href=" in item:
79+
self.assertEqual(env_value in item, True)
80+
item_found = True
81+
self.assertTrue(item_found)
82+
finally:
83+
del os.environ[env_name]
84+
85+
86+
if __name__ == '__main__':
87+
unittest.main()

0 commit comments

Comments
 (0)