QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsfcgiserverrequest.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfcgiserverrequest.cpp
3
4 Define response wrapper for fcgi request
5 -------------------
6 begin : 2017-01-03
7 copyright : (C) 2017 by David Marteau
8 email : david dot marteau at 3liz dot com
9 ***************************************************************************/
10
11/***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 ***************************************************************************/
19
20#include "qgis.h"
22#include "qgsserverlogger.h"
23#include "qgsmessagelog.h"
24#include "qgsstringutils.h"
25#include <fcgi_stdio.h>
26#include <QDebug>
27
29{
30 // Get the REQUEST_URI from the environment
31 QString uri = getenv( "REQUEST_URI" );
32
33 if ( uri.isEmpty() )
34 {
35 uri = getenv( "SCRIPT_NAME" );
36 }
37
38 QUrl url;
39 url.setUrl( uri );
40 fillUrl( url );
41 // Store the URL before the server rewrite that could have been set in QUERY_STRING
43
44 const QString qs = getenv( "QUERY_STRING" );
45 const QString questionMark = qs.isEmpty() ? QString() : QChar( '?' );
46 const QString extraPath = QStringLiteral( "%1%2%3" ).arg( getenv( "PATH_INFO" ) ).arg( questionMark ).arg( qs );
47
48 QUrl baseUrl;
49 if ( uri.endsWith( extraPath ) )
50 {
51 baseUrl.setUrl( uri.left( uri.length() - extraPath.length() ) );
52 }
53 else
54 {
55 baseUrl.setUrl( uri );
56 }
57 fillUrl( baseUrl );
58 setBaseUrl( url );
59
60 // OGC parameters are passed with the query string, which is normally part of
61 // the REQUEST_URI, we override the query string url in case it is defined
62 // independently of REQUEST_URI
63 if ( ! qs.isEmpty() )
64 {
65 url.setQuery( qs );
66 }
67
68#ifdef QGISDEBUG
69 qDebug() << "fcgi query string: " << url.query();
70#endif
71
73
74 // Get method
75 const char *me = getenv( "REQUEST_METHOD" );
76
77 if ( me )
78 {
79 if ( strcmp( me, "POST" ) == 0 )
80 {
82 }
83 else if ( strcmp( me, "PUT" ) == 0 )
84 {
86 }
87 else if ( strcmp( me, "DELETE" ) == 0 )
88 {
90 }
91 else if ( strcmp( me, "HEAD" ) == 0 )
92 {
94 }
95 else if ( strcmp( me, "PATCH" ) == 0 )
96 {
98 }
99 }
100
101 if ( method == PostMethod || method == PutMethod )
102 {
103 // Get post/put data
104 readData();
105 }
106
107 setUrl( url );
108 setMethod( method );
109
110 // Fill the headers dictionary
111 for ( const auto &headerKey : qgsEnumMap<QgsServerRequest::RequestHeader>().values() )
112 {
113 const QString headerName = QgsStringUtils::capitalize(
114 QString( headerKey ).replace( QLatin1Char( '_' ), QLatin1Char( ' ' ) ), Qgis::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 }
121 }
122
123 // Output debug infos
125 if ( logLevel <= Qgis::MessageLevel::Info )
126 {
127 printRequestInfos( url );
128 }
129}
130
131void QgsFcgiServerRequest::fillUrl( QUrl &url ) const
132{
133 // Check if host is defined
134 if ( url.host().isEmpty() )
135 {
136 url.setHost( getenv( "SERVER_NAME" ) );
137 }
138
139 // Port ?
140 if ( url.port( -1 ) == -1 )
141 {
142 const QString portString = getenv( "SERVER_PORT" );
143 if ( !portString.isEmpty() )
144 {
145 bool portOk;
146 const int portNumber = portString.toInt( &portOk );
147 if ( portOk && portNumber != 80 )
148 {
149 url.setPort( portNumber );
150 }
151 }
152 }
153
154 // scheme
155 if ( url.scheme().isEmpty() )
156 {
157 QString( getenv( "HTTPS" ) ).compare( QLatin1String( "on" ), Qt::CaseInsensitive ) == 0
158 ? url.setScheme( QStringLiteral( "https" ) )
159 : url.setScheme( QStringLiteral( "http" ) );
160 }
161}
162
164{
165 return mData;
166}
167
168// Read post put data
169void QgsFcgiServerRequest::readData()
170{
171 // Check if we have CONTENT_LENGTH defined
172 const char *lengthstr = getenv( "CONTENT_LENGTH" );
173 if ( lengthstr )
174 {
175 bool success = false;
176 int length = QString( lengthstr ).toInt( &success );
177 // Note: REQUEST_BODY is not part of CGI standard, and it is not
178 // normally passed by any CGI web server and it is implemented only
179 // to allow unit tests to inject a request body and simulate a POST
180 // request
181 const char *request_body = getenv( "REQUEST_BODY" );
182 if ( success && request_body )
183 {
184 QString body( request_body );
185 body.truncate( length );
186 mData.append( body.toUtf8() );
187 length = 0;
188 }
189#ifdef QGISDEBUG
190 qDebug() << "fcgi: reading " << lengthstr << " bytes from " << ( request_body ? "REQUEST_BODY" : "stdin" );
191#endif
192 if ( success )
193 {
194 // XXX This not efficient at all !!
195 for ( int i = 0; i < length; ++i )
196 {
197 mData.append( getchar() );
198 }
199 }
200 else
201 {
202 QgsMessageLog::logMessage( "fcgi: Failed to parse CONTENT_LENGTH",
203 QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
204 mHasError = true;
205 }
206 }
207 else
208 {
209 QgsMessageLog::logMessage( "fcgi: No POST data" );
210 }
211}
212
213void QgsFcgiServerRequest::printRequestInfos( const QUrl &url ) const
214{
215 QgsMessageLog::logMessage( QStringLiteral( "******************** New request ***************" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
216
217 const QStringList envVars
218 {
219 QStringLiteral( "SERVER_NAME" ),
220 QStringLiteral( "REQUEST_URI" ),
221 QStringLiteral( "SCRIPT_NAME" ),
222 QStringLiteral( "PATH_INFO" ),
223 QStringLiteral( "HTTPS" ),
224 QStringLiteral( "REMOTE_ADDR" ),
225 QStringLiteral( "REMOTE_HOST" ),
226 QStringLiteral( "SERVER_PORT" ),
227 QStringLiteral( "QUERY_STRING" ),
228 QStringLiteral( "REMOTE_USER" ),
229 QStringLiteral( "REMOTE_IDENT" ),
230 QStringLiteral( "CONTENT_TYPE" ),
231 QStringLiteral( "REQUEST_METHOD" ),
232 QStringLiteral( "AUTH_TYPE" ),
233 QStringLiteral( "HTTP_PROXY" ),
234 QStringLiteral( "NO_PROXY" ),
235 QStringLiteral( "QGIS_PROJECT_FILE" ),
236 QStringLiteral( "QGIS_SERVER_IGNORE_BAD_LAYERS" ),
237 QStringLiteral( "QGIS_SERVER_SERVICE_URL" ),
238 QStringLiteral( "QGIS_SERVER_WMS_SERVICE_URL" ),
239 QStringLiteral( "QGIS_SERVER_WFS_SERVICE_URL" ),
240 QStringLiteral( "QGIS_SERVER_WMTS_SERVICE_URL" ),
241 QStringLiteral( "QGIS_SERVER_WCS_SERVICE_URL" ),
242 QStringLiteral( "SERVER_PROTOCOL" )
243 };
244
245 QgsMessageLog::logMessage( QStringLiteral( "Request URL: %2" ).arg( url.url() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
246
247 QgsMessageLog::logMessage( QStringLiteral( "Environment:" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
248 QgsMessageLog::logMessage( QStringLiteral( "------------------------------------------------" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
249 for ( const auto &envVar : envVars )
250 {
251 if ( getenv( envVar.toStdString().c_str() ) )
252 {
253 QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( envVar ).arg( QString( getenv( envVar.toStdString().c_str() ) ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
254 }
255 }
256
257 qDebug() << "Headers:";
258 qDebug() << "------------------------------------------------";
259 for ( const auto &headerName : headers().keys() )
260 {
261 qDebug() << headerName << ": " << headers().value( headerName );
262 }
263}
264
265QString QgsFcgiServerRequest::header( const QString &name ) const
266{
267 // Get from internal dictionary
268 QString result = QgsServerRequest::header( name );
269
270 // Or from standard environment variable
271 // https://tools.ietf.org/html/rfc3875#section-4.1.18
272 if ( result.isEmpty() )
273 {
274 result = qgetenv( QStringLiteral( "HTTP_%1" ).arg(
275 name.toUpper().replace( QLatin1Char( '-' ), QLatin1Char( '_' ) ) ).toStdString().c_str() );
276 }
277 return result;
278}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:115
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
QString header(const QString &name) const override
Returns the header value.
QByteArray data() const override
Returns post/put data Check for QByteArray::isNull() to check if data is available.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QgsServerLogger * instance()
Gets the singleton instance.
Qgis::MessageLevel logLevel() const
Gets the current log level.
void setOriginalUrl(const QUrl &url)
Set the request original url (the request url as seen by the web server)
Method
HTTP Method (or equivalent) used for the request.
virtual QString header(const QString &name) const
Returns the header value.
virtual void setUrl(const QUrl &url)
Set the request url.
QMap< QString, QString > headers() const
Returns the header map.
QUrl baseUrl() const
Returns the base URL of QGIS server.
QgsServerRequest::Method method() const
void setMethod(QgsServerRequest::Method method)
Set the request method.
void setBaseUrl(const QUrl &url)
Set the base URL of QGIS server.
void setHeader(const QString &name, const QString &value)
Set an header.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.