QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
21 #include "qgsfcgiserverrequest.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  {
85  method = PutMethod;
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 
131 void 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 
163 QByteArray QgsFcgiServerRequest::data() const
164 {
165  return mData;
166 }
167 
168 // Read post put data
169 void 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 
213 void QgsFcgiServerRequest::printRequestInfos( const QUrl &url )
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 
265 QString 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:107
@ 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.