QGIS API Documentation 4.1.0-Master (3b8ef1f72a3)
Loading...
Searching...
No Matches
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
21
22#include "qgis.h"
23#include "qgsmessagelog.h"
24#include "qgsserverlogger.h"
25#include "qgsstringutils.h"
26
27#include <QDebug>
28#include <QString>
29
30#include <fcgi_stdio.h>
31
32using namespace Qt::StringLiterals;
33
35{
36 // Get the REQUEST_URI from the environment
37 QString uri = getenv( "REQUEST_URI" );
38
39 if ( uri.isEmpty() )
40 {
41 uri = getenv( "SCRIPT_NAME" );
42 }
43
44 QUrl url;
45 url.setUrl( uri );
46 fillUrl( url );
47 // Store the URL before the server rewrite that could have been set in QUERY_STRING
49
50 const QString qs = getenv( "QUERY_STRING" );
51 const QString questionMark = qs.isEmpty() ? QString() : QChar( '?' );
52 const QString extraPath = u"%1%2%3"_s.arg( getenv( "PATH_INFO" ) ).arg( questionMark ).arg( qs );
53
54 QUrl baseUrl;
55 if ( uri.endsWith( extraPath ) )
56 {
57 baseUrl.setUrl( uri.left( uri.length() - extraPath.length() ) );
58 }
59 else
60 {
61 baseUrl.setUrl( uri );
62 }
63 fillUrl( baseUrl );
64 setBaseUrl( url );
65
66 // OGC parameters are passed with the query string, which is normally part of
67 // the REQUEST_URI, we override the query string url in case it is defined
68 // independently of REQUEST_URI
69 if ( !qs.isEmpty() )
70 {
71 url.setQuery( qs );
72 }
73
74#ifdef QGISDEBUG
75 qDebug() << "fcgi query string: " << url.query();
76#endif
77
79
80 // Get method
81 const char *me = getenv( "REQUEST_METHOD" );
82
83 if ( me )
84 {
85 if ( strcmp( me, "POST" ) == 0 )
86 {
88 }
89 else if ( strcmp( me, "PUT" ) == 0 )
90 {
92 }
93 else if ( strcmp( me, "DELETE" ) == 0 )
94 {
96 }
97 else if ( strcmp( me, "HEAD" ) == 0 )
98 {
100 }
101 else if ( strcmp( me, "PATCH" ) == 0 )
102 {
104 }
105 else if ( strcmp( me, "OPTIONS" ) == 0 )
106 {
108 }
109 }
110
111 if ( method == PostMethod || method == PutMethod || method == PatchMethod )
112 {
113 // Get post/put/patch data
114 readData();
115 }
116
117 setUrl( url );
118 setMethod( method );
119
120 // Fill the headers dictionary
121 for ( const auto &headerKey : qgsEnumMap<QgsServerRequest::RequestHeader>() )
122 {
123 const QString headerName = QgsStringUtils::capitalize( QString( headerKey ).replace( '_'_L1, ' '_L1 ), Qgis::Capitalization::TitleCase ).replace( ' '_L1, '-'_L1 );
124 const char *result = getenv( u"HTTP_%1"_s.arg( headerKey ).toStdString().c_str() );
125 if ( result && strlen( result ) > 0 )
126 {
127 setHeader( headerName, result );
128 }
129 }
130
131 // Output debug infos
133 if ( logLevel <= Qgis::MessageLevel::Info )
134 {
135 printRequestInfos( url );
136 }
137}
138
139void QgsFcgiServerRequest::fillUrl( QUrl &url ) const
140{
141 // Check if host is defined
142 if ( url.host().isEmpty() )
143 {
144 url.setHost( getenv( "SERVER_NAME" ) );
145 }
146
147 // Port ?
148 if ( url.port( -1 ) == -1 )
149 {
150 const QString portString = getenv( "SERVER_PORT" );
151 if ( !portString.isEmpty() )
152 {
153 bool portOk;
154 const int portNumber = portString.toInt( &portOk );
155 if ( portOk && portNumber != 80 )
156 {
157 url.setPort( portNumber );
158 }
159 }
160 }
161
162 // scheme
163 if ( url.scheme().isEmpty() )
164 {
165 QString( getenv( "HTTPS" ) ).compare( "on"_L1, Qt::CaseInsensitive ) == 0 ? url.setScheme( u"https"_s ) : url.setScheme( u"http"_s );
166 }
167}
168
170{
171 return mData;
172}
173
174// Read post put data
175void QgsFcgiServerRequest::readData()
176{
177 // Check if we have CONTENT_LENGTH defined
178 const char *lengthstr = getenv( "CONTENT_LENGTH" );
179 if ( lengthstr )
180 {
181 bool success = false;
182 const int length = QString( lengthstr ).toInt( &success );
183 if ( !success || length < 0 )
184 {
185 QgsMessageLog::logMessage( "fcgi: Invalid CONTENT_LENGTH", u"Server"_s, Qgis::MessageLevel::Critical );
186 mHasError = true;
187 }
188 else
189 {
190 // Note: REQUEST_BODY is not part of CGI standard, and it is not
191 // normally passed by any CGI web server and it is implemented only
192 // to allow unit tests to inject a request body and simulate a POST
193 // request
194 const char *requestBody = getenv( "REQUEST_BODY" );
195
196#ifdef QGISDEBUG
197 qDebug() << "fcgi: reading " << lengthstr << " bytes from " << ( requestBody ? "REQUEST_BODY" : "stdin" );
198#endif
199
200 if ( requestBody )
201 {
202 const size_t requestBodyLength = strlen( requestBody );
203 const int actualLength = static_cast<int>( std::min<size_t>( length, requestBodyLength ) );
204 if ( static_cast<size_t>( actualLength ) < requestBodyLength )
205 {
206 QgsMessageLog::logMessage( "fcgi: CONTENT_LENGTH is larger than actual length of REQUEST_BODY", u"Server"_s, Qgis::MessageLevel::Critical );
207 mHasError = true;
208 }
209 mData = QByteArray::fromRawData( requestBody, actualLength );
210 }
211 else
212 {
213 mData.resize( length );
214 const int actualLength = static_cast<int>( fread( mData.data(), 1, length, stdin ) );
215 if ( actualLength < length )
216 {
217 mData.resize( actualLength );
218 QgsMessageLog::logMessage( "fcgi: CONTENT_LENGTH is larger than actual length of stdin", u"Server"_s, Qgis::MessageLevel::Critical );
219 mHasError = true;
220 }
221 }
222 }
223 }
224 else
225 {
226 QgsMessageLog::logMessage( "fcgi: No POST/PUT/PATCH data" );
227 }
228}
229
230void QgsFcgiServerRequest::printRequestInfos( const QUrl &url ) const
231{
232 QgsMessageLog::logMessage( u"******************** New request ***************"_s, u"Server"_s, Qgis::MessageLevel::Info );
233
234 const QStringList envVars {
235 u"SERVER_NAME"_s,
236 u"REQUEST_URI"_s,
237 u"SCRIPT_NAME"_s,
238 u"PATH_INFO"_s,
239 u"HTTPS"_s,
240 u"REMOTE_ADDR"_s,
241 u"REMOTE_HOST"_s,
242 u"SERVER_PORT"_s,
243 u"QUERY_STRING"_s,
244 u"REMOTE_USER"_s,
245 u"REMOTE_IDENT"_s,
246 u"CONTENT_TYPE"_s,
247 u"REQUEST_METHOD"_s,
248 u"AUTH_TYPE"_s,
249 u"HTTP_PROXY"_s,
250 u"NO_PROXY"_s,
251 u"QGIS_PROJECT_FILE"_s,
252 u"QGIS_SERVER_IGNORE_BAD_LAYERS"_s,
253 u"QGIS_SERVER_SERVICE_URL"_s,
254 u"QGIS_SERVER_WMS_SERVICE_URL"_s,
255 u"QGIS_SERVER_WFS_SERVICE_URL"_s,
256 u"QGIS_SERVER_WMTS_SERVICE_URL"_s,
257 u"QGIS_SERVER_WCS_SERVICE_URL"_s,
258 u"SERVER_PROTOCOL"_s
259 };
260
261 QgsMessageLog::logMessage( u"Request URL: %2"_s.arg( url.url() ), u"Server"_s, Qgis::MessageLevel::Info );
262
263 QgsMessageLog::logMessage( u"Environment:"_s, u"Server"_s, Qgis::MessageLevel::Info );
264 QgsMessageLog::logMessage( u"------------------------------------------------"_s, u"Server"_s, Qgis::MessageLevel::Info );
265 for ( const auto &envVar : envVars )
266 {
267 if ( getenv( envVar.toStdString().c_str() ) )
268 {
269 QgsMessageLog::logMessage( u"%1: %2"_s.arg( envVar ).arg( QString( getenv( envVar.toStdString().c_str() ) ) ), u"Server"_s, Qgis::MessageLevel::Info );
270 }
271 }
272
273 qDebug() << "Headers:";
274 qDebug() << "------------------------------------------------";
275 const QMap<QString, QString> &hdrs = headers();
276 for ( auto it = hdrs.constBegin(); it != hdrs.constEnd(); it++ )
277 {
278 qDebug() << it.key() << ": " << it.value();
279 }
280}
281
282QString QgsFcgiServerRequest::header( const QString &name ) const
283{
284 // Get from internal dictionary
285 QString result = QgsServerRequest::header( name );
286
287 // Or from standard environment variable
288 // https://tools.ietf.org/html/rfc3875#section-4.1.18
289 if ( result.isEmpty() )
290 {
291 result = qgetenv( u"HTTP_%1"_s.arg( name.toUpper().replace( '-'_L1, '_'_L1 ) ).toStdString().c_str() );
292 }
293 return result;
294}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:160
@ Critical
Critical/error message.
Definition qgis.h:163
@ Info
Information message.
Definition qgis.h:161
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
Definition qgis.h:3561
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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.
QUrl url() const
Returns the request URL as seen by QGIS server.
QMap< QString, QString > headers() const
Returns the header map.
QUrl baseUrl() const
Returns the base URL of QGIS server.
QgsServerRequest::Method method() const
Returns the request method.
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.
const QMap< T, QString > qgsEnumMap()
Returns a map of all enum entries.
Definition qgis.h:7242