Skip to content

Commit

Permalink
Merge pull request #8830 from elpaso/bugfix-server-fcgi-rewritten-uri
Browse files Browse the repository at this point in the history
[server] Use REQUEST_URI as default URL for FCGI requests
  • Loading branch information
elpaso committed Jan 14, 2019
1 parent 5c24b4e commit dedbbd4
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 55 deletions.
57 changes: 57 additions & 0 deletions python/server/auto_generated/qgsfcgiserverrequest.sip.in
@@ -0,0 +1,57 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfcgiserverrequest.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsFcgiServerRequest: QgsServerRequest
{
%Docstring
Class defining fcgi request

.. versionadded:: 3.0
%End

%TypeHeaderCode
#include "qgsfcgiserverrequest.h"
%End
public:
QgsFcgiServerRequest();

virtual QByteArray data() const;


bool hasError() const;
%Docstring
Returns true if an error occurred during initialization
%End

virtual QUrl url() const;

%Docstring

:return: the request url

Overrides base implementation because FCGI is typically behind
a proxy server and QGIS Server will see a rewritten QUERY_STRING.
FCGI implementation stores the REQUEST_URI (which is the URL seen
by the proxy before it gets rewritten) and returns it instead of
the rewritten one.
%End


};

/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsfcgiserverrequest.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
6 changes: 5 additions & 1 deletion python/server/auto_generated/qgsserverrequest.sip.in
Expand Up @@ -56,10 +56,14 @@ Constructor

virtual ~QgsServerRequest();

QUrl url() const;
virtual QUrl url() const;
%Docstring

:return: the request url

Subclasses may override in case the original URL (not rewritten, e.g.from
a web server rewrite module) needs to be returned instead of the URL
seen by QGIS server.
%End

QgsServerRequest::Method method() const;
Expand Down
1 change: 1 addition & 0 deletions python/server/server_auto.sip
Expand Up @@ -8,6 +8,7 @@
%Include auto_generated/qgsserverparameters.sip
%Include auto_generated/qgsbufferserverrequest.sip
%Include auto_generated/qgsbufferserverresponse.sip
%Include auto_generated/qgsfcgiserverrequest.sip
%Include auto_generated/qgsrequesthandler.sip
%Include auto_generated/qgsserver.sip
%Include auto_generated/qgsserverexception.sip
Expand Down
84 changes: 35 additions & 49 deletions src/server/qgsfcgiserverrequest.cpp
Expand Up @@ -29,13 +29,11 @@

QgsFcgiServerRequest::QgsFcgiServerRequest()
{
mHasError = false;

// Rebuild the full URL

// Get the REQUEST_URI from the environment
QUrl url;
QString uri = getenv( "REQUEST_URI" );

if ( uri.isEmpty() )
{
uri = getenv( "SCRIPT_NAME" );
Expand Down Expand Up @@ -72,9 +70,12 @@ QgsFcgiServerRequest::QgsFcgiServerRequest()
: url.setScheme( QStringLiteral( "http" ) );
}

// XXX OGC paremetrs are passed with the query string
// we override the query string url in case it is
// defined independently of REQUEST_URI
// Store the URL before the server rewrite that could have been set in QUERY_STRING
mOriginalUrl = url;

// OGC parameters are passed with the query string, which is normally part of
// the REQUEST_URI, we override the query string url in case it is defined
// independently of REQUEST_URI
const char *qs = getenv( "QUERY_STRING" );
if ( qs )
{
Expand Down Expand Up @@ -132,6 +133,11 @@ QByteArray QgsFcgiServerRequest::data() const
return mData;
}

QUrl QgsFcgiServerRequest::url() const
{
return mOriginalUrl.isEmpty() ? QgsServerRequest::url() : mOriginalUrl;
}

// Read post put data
void QgsFcgiServerRequest::readData()
{
Expand Down Expand Up @@ -168,48 +174,28 @@ void QgsFcgiServerRequest::readData()
void QgsFcgiServerRequest::printRequestInfos()
{
QgsMessageLog::logMessage( QStringLiteral( "******************** New request ***************" ), QStringLiteral( "Server" ), Qgis::Info );
if ( getenv( "REMOTE_ADDR" ) )
{
QgsMessageLog::logMessage( "REMOTE_ADDR: " + QString( getenv( "REMOTE_ADDR" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "REMOTE_HOST" ) )
{
QgsMessageLog::logMessage( "REMOTE_HOST: " + QString( getenv( "REMOTE_HOST" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "REMOTE_USER" ) )
{
QgsMessageLog::logMessage( "REMOTE_USER: " + QString( getenv( "REMOTE_USER" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "REMOTE_IDENT" ) )
{
QgsMessageLog::logMessage( "REMOTE_IDENT: " + QString( getenv( "REMOTE_IDENT" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "CONTENT_TYPE" ) )
{
QgsMessageLog::logMessage( "CONTENT_TYPE: " + QString( getenv( "CONTENT_TYPE" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "AUTH_TYPE" ) )
{
QgsMessageLog::logMessage( "AUTH_TYPE: " + QString( getenv( "AUTH_TYPE" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "HTTP_USER_AGENT" ) )
{
QgsMessageLog::logMessage( "HTTP_USER_AGENT: " + QString( getenv( "HTTP_USER_AGENT" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "HTTP_PROXY" ) )
{
QgsMessageLog::logMessage( "HTTP_PROXY: " + QString( getenv( "HTTP_PROXY" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "HTTPS_PROXY" ) )
{
QgsMessageLog::logMessage( "HTTPS_PROXY: " + QString( getenv( "HTTPS_PROXY" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "NO_PROXY" ) )
{
QgsMessageLog::logMessage( "NO_PROXY: " + QString( getenv( "NO_PROXY" ) ), QStringLiteral( "Server" ), Qgis::Info );
}
if ( getenv( "HTTP_AUTHORIZATION" ) )
{
QgsMessageLog::logMessage( "HTTP_AUTHORIZATION: " + QString( getenv( "HTTP_AUTHORIZATION" ) ), QStringLiteral( "Server" ), Qgis::Info );

const QStringList envVars
{
QStringLiteral( "SERVER_NAME" ),
QStringLiteral( "REQUEST_URI" ),
QStringLiteral( "REMOTE_ADDR" ),
QStringLiteral( "REMOTE_HOST" ),
QStringLiteral( "REMOTE_USER" ),
QStringLiteral( "REMOTE_IDENT" ),
QStringLiteral( "CONTENT_TYPE" ),
QStringLiteral( "AUTH_TYPE" ),
QStringLiteral( "HTTP_USER_AGENT" ),
QStringLiteral( "HTTP_PROXY" ),
QStringLiteral( "NO_PROXY" ),
QStringLiteral( "HTTP_AUTHORIZATION" )
};

for ( const auto &envVar : envVars )
{
if ( getenv( envVar.toStdString().c_str() ) )
{
QgsMessageLog::logMessage( envVar + QString( getenv( envVar.toStdString().c_str() ) ), QStringLiteral( "Server" ), Qgis::Info );
}
}
}
18 changes: 15 additions & 3 deletions src/server/qgsfcgiserverrequest.h
Expand Up @@ -19,8 +19,6 @@
#ifndef QGSFCGISERVERREQUEST_H
#define QGSFCGISERVERREQUEST_H

#define SIP_NO_FILE


#include "qgsserverrequest.h"

Expand All @@ -44,6 +42,18 @@ class SERVER_EXPORT QgsFcgiServerRequest: public QgsServerRequest
*/
bool hasError() const { return mHasError; }

/**
* \returns the request url
*
* Overrides base implementation because FCGI is typically behind
* a proxy server and QGIS Server will see a rewritten QUERY_STRING.
* FCGI implementation stores the REQUEST_URI (which is the URL seen
* by the proxy before it gets rewritten) and returns it instead of
* the rewritten one.
*/
QUrl url() const override;


private:
void readData();

Expand All @@ -53,7 +63,9 @@ class SERVER_EXPORT QgsFcgiServerRequest: public QgsServerRequest


QByteArray mData;
bool mHasError;
bool mHasError = false;
//! Url before the server rewrite
QUrl mOriginalUrl;
};

#endif
6 changes: 5 additions & 1 deletion src/server/qgsserverrequest.h
Expand Up @@ -83,8 +83,12 @@ class SERVER_EXPORT QgsServerRequest

/**
* \returns the request url
*
* Subclasses may override in case the original URL (not rewritten, e.g.from
* a web server rewrite module) needs to be returned instead of the URL
* seen by QGIS server.
*/
QUrl url() const;
virtual QUrl url() const;

/**
* \returns the request method
Expand Down
45 changes: 44 additions & 1 deletion tests/src/python/test_qgsserver_request.py
Expand Up @@ -10,6 +10,8 @@
"""
import unittest
import os
from urllib.parse import parse_qs, urlparse

__author__ = 'Alessandro Pasotti'
__date__ = '29/04/2017'
Expand All @@ -19,7 +21,7 @@


from qgis.PyQt.QtCore import QUrl
from qgis.server import QgsServerRequest
from qgis.server import QgsServerRequest, QgsFcgiServerRequest


class QgsServerRequestTest(unittest.TestCase):
Expand Down Expand Up @@ -66,6 +68,47 @@ def test_requestMethod(self):
request.setMethod(QgsServerRequest.PostMethod)
self.assertEqual(request.method(), QgsServerRequest.PostMethod)

def test_fcgiRequest(self):
"""Test various combinations of FCGI env parameters with rewritten urls"""

def _test_url(url, env={}):
for k in ('QUERY_STRING', 'REQUEST_URI', 'SERVER_NAME', 'SERVER_PORT', 'SCRIPT_NAME'):
try:
del os.environ[k]
except KeyError:
pass
try:
os.environ[k] = env[k]
except KeyError:
pass
request = QgsFcgiServerRequest()
self.assertEqual(request.url().toString(), url)
# Check MAP
if 'QUERY_STRING' in env:
map = {k.upper(): v[0] for k, v in parse_qs(env['QUERY_STRING']).items()}['MAP']
else:
map = {k.upper(): v[0] for k, v in parse_qs(urlparse(env['REQUEST_URI']).query).items()}['MAP']
self.assertEqual(request.parameter('MAP'), map)

_test_url('http://somesite.com/somepath/index.html?map=/my/path.qgs', {
'REQUEST_URI': '/somepath/index.html?map=/my/path.qgs',
'SERVER_NAME': 'somesite.com',
})
_test_url('http://somesite.com/somepath?map=/my/path.qgs', {
'REQUEST_URI': '/somepath?map=/my/path.qgs',
'SERVER_NAME': 'somesite.com',
})
_test_url('http://somesite.com/somepath/path', {
'REQUEST_URI': '/somepath/path',
'SERVER_NAME': 'somesite.com',
'QUERY_STRING': 'map=/my/path.qgs'
})
_test_url('http://somesite.com/somepath/path/?token=QGIS2019', {
'REQUEST_URI': '/somepath/path/?token=QGIS2019',
'SERVER_NAME': 'somesite.com',
'QUERY_STRING': 'map=/my/path.qgs',
})


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

1 comment on commit dedbbd4

@andreassteffens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this seems to break the server in cases where a POST is used to transmit the query parameters. In this case QgsServerRequest analyzes the posted parameters and appends them to the original url so the server services can build QgsWmsParameters, QgsWfsParameters etc. from the query string. With the overriden url() getter in QgsFcgiServerRequest the original url is returned to the services which in a POST scenario has no query resulting in the services (at least in their current implementation) to not be able to get the posted parameters.

Please sign in to comment.