QGIS API Documentation  3.23.0-Master (dd0cd13a00)
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 <fcgi_stdio.h>
25 #include <QDebug>
26 
28 {
29  // Get the REQUEST_URI from the environment
30  QString uri = getenv( "REQUEST_URI" );
31 
32  if ( uri.isEmpty() )
33  {
34  uri = getenv( "SCRIPT_NAME" );
35  }
36 
37  QUrl url;
38  url.setUrl( uri );
39  fillUrl( url );
40  // Store the URL before the server rewrite that could have been set in QUERY_STRING
42 
43  const QString qs = getenv( "QUERY_STRING" );
44  const QString questionMark = qs.isEmpty() ? QString() : QChar( '?' );
45  const QString extraPath = QStringLiteral( "%1%2%3" ).arg( getenv( "PATH_INFO" ) ).arg( questionMark ).arg( qs );
46 
47  QUrl baseUrl;
48  if ( uri.endsWith( extraPath ) )
49  {
50  baseUrl.setUrl( uri.left( uri.length() - extraPath.length() ) );
51  }
52  else
53  {
54  baseUrl.setUrl( uri );
55  }
56  fillUrl( baseUrl );
57  setBaseUrl( url );
58 
59  // OGC parameters are passed with the query string, which is normally part of
60  // the REQUEST_URI, we override the query string url in case it is defined
61  // independently of REQUEST_URI
62  if ( ! qs.isEmpty() )
63  {
64  url.setQuery( qs );
65  }
66 
67 #ifdef QGISDEBUG
68  qDebug() << "fcgi query string: " << url.query();
69 #endif
70 
72 
73  // Get method
74  const char *me = getenv( "REQUEST_METHOD" );
75 
76  if ( me )
77  {
78  if ( strcmp( me, "POST" ) == 0 )
79  {
81  }
82  else if ( strcmp( me, "PUT" ) == 0 )
83  {
84  method = PutMethod;
85  }
86  else if ( strcmp( me, "DELETE" ) == 0 )
87  {
89  }
90  else if ( strcmp( me, "HEAD" ) == 0 )
91  {
93  }
94  else if ( strcmp( me, "PATCH" ) == 0 )
95  {
97  }
98  }
99 
100  if ( method == PostMethod || method == PutMethod )
101  {
102  // Get post/put data
103  readData();
104  }
105 
106  setUrl( url );
107  setMethod( method );
108 
109  // Get accept header for content-type negotiation
110  const char *accept = getenv( "HTTP_ACCEPT" );
111  if ( accept )
112  {
113  setHeader( QStringLiteral( "Accept" ), accept );
114  }
115 
116  // Output debug infos
118  if ( logLevel <= Qgis::MessageLevel::Info )
119  {
120  printRequestInfos( url );
121  }
122 }
123 
124 void QgsFcgiServerRequest::fillUrl( QUrl &url ) const
125 {
126  // Check if host is defined
127  if ( url.host().isEmpty() )
128  {
129  url.setHost( getenv( "SERVER_NAME" ) );
130  }
131 
132  // Port ?
133  if ( url.port( -1 ) == -1 )
134  {
135  const QString portString = getenv( "SERVER_PORT" );
136  if ( !portString.isEmpty() )
137  {
138  bool portOk;
139  const int portNumber = portString.toInt( &portOk );
140  if ( portOk && portNumber != 80 )
141  {
142  url.setPort( portNumber );
143  }
144  }
145  }
146 
147  // scheme
148  if ( url.scheme().isEmpty() )
149  {
150  QString( getenv( "HTTPS" ) ).compare( QLatin1String( "on" ), Qt::CaseInsensitive ) == 0
151  ? url.setScheme( QStringLiteral( "https" ) )
152  : url.setScheme( QStringLiteral( "http" ) );
153  }
154 }
155 
156 QByteArray QgsFcgiServerRequest::data() const
157 {
158  return mData;
159 }
160 
161 // Read post put data
162 void QgsFcgiServerRequest::readData()
163 {
164  // Check if we have CONTENT_LENGTH defined
165  const char *lengthstr = getenv( "CONTENT_LENGTH" );
166  if ( lengthstr )
167  {
168  bool success = false;
169  int length = QString( lengthstr ).toInt( &success );
170  // Note: REQUEST_BODY is not part of CGI standard, and it is not
171  // normally passed by any CGI web server and it is implemented only
172  // to allow unit tests to inject a request body and simulate a POST
173  // request
174  const char *request_body = getenv( "REQUEST_BODY" );
175  if ( success && request_body )
176  {
177  QString body( request_body );
178  body.truncate( length );
179  mData.append( body.toUtf8() );
180  length = 0;
181  }
182 #ifdef QGISDEBUG
183  qDebug() << "fcgi: reading " << lengthstr << " bytes from " << ( request_body ? "REQUEST_BODY" : "stdin" );
184 #endif
185  if ( success )
186  {
187  // XXX This not efficient at all !!
188  for ( int i = 0; i < length; ++i )
189  {
190  mData.append( getchar() );
191  }
192  }
193  else
194  {
195  QgsMessageLog::logMessage( "fcgi: Failed to parse CONTENT_LENGTH",
196  QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
197  mHasError = true;
198  }
199  }
200  else
201  {
202  QgsMessageLog::logMessage( "fcgi: No POST data" );
203  }
204 }
205 
206 void QgsFcgiServerRequest::printRequestInfos( const QUrl &url )
207 {
208  QgsMessageLog::logMessage( QStringLiteral( "******************** New request ***************" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
209 
210  const QStringList envVars
211  {
212  QStringLiteral( "SERVER_NAME" ),
213  QStringLiteral( "REQUEST_URI" ),
214  QStringLiteral( "SCRIPT_NAME" ),
215  QStringLiteral( "PATH_INFO" ),
216  QStringLiteral( "HTTPS" ),
217  QStringLiteral( "REMOTE_ADDR" ),
218  QStringLiteral( "REMOTE_HOST" ),
219  QStringLiteral( "SERVER_PORT" ),
220  QStringLiteral( "QUERY_STRING" ),
221  QStringLiteral( "REMOTE_USER" ),
222  QStringLiteral( "REMOTE_IDENT" ),
223  QStringLiteral( "CONTENT_TYPE" ),
224  QStringLiteral( "REQUEST_METHOD" ),
225  QStringLiteral( "AUTH_TYPE" ),
226  QStringLiteral( "HTTP_ACCEPT" ),
227  QStringLiteral( "HTTP_USER_AGENT" ),
228  QStringLiteral( "HTTP_PROXY" ),
229  QStringLiteral( "NO_PROXY" ),
230  QStringLiteral( "HTTP_AUTHORIZATION" ),
231  QStringLiteral( "QGIS_PROJECT_FILE" ),
232  QStringLiteral( "QGIS_SERVER_IGNORE_BAD_LAYERS" ),
233  QStringLiteral( "QGIS_SERVER_SERVICE_URL" ),
234  QStringLiteral( "QGIS_SERVER_WMS_SERVICE_URL" ),
235  QStringLiteral( "QGIS_SERVER_WFS_SERVICE_URL" ),
236  QStringLiteral( "QGIS_SERVER_WMTS_SERVICE_URL" ),
237  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" ),
247  QStringLiteral( "SERVER_PROTOCOL" )
248  };
249 
250  QgsMessageLog::logMessage( QStringLiteral( "Request URL: %2" ).arg( url.url() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
251  QgsMessageLog::logMessage( QStringLiteral( "Environment:" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
252  QgsMessageLog::logMessage( QStringLiteral( "------------------------------------------------" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
253 
254  for ( const auto &envVar : envVars )
255  {
256  if ( getenv( envVar.toStdString().c_str() ) )
257  {
258  QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( envVar ).arg( QString( getenv( envVar.toStdString().c_str() ) ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
259  }
260  }
261 }
262 
263 QString QgsFcgiServerRequest::header( const QString &name ) const
264 {
265  // Get from internal dictionary
266  QString result = QgsServerRequest::header( name );
267 
268  // Or from standard environment variable
269  // https://tools.ietf.org/html/rfc3875#section-4.1.18
270  if ( result.isEmpty() )
271  {
272  result = qgetenv( QStringLiteral( "HTTP_%1" ).arg(
273  name.toUpper().replace( QLatin1Char( '-' ), QLatin1Char( '_' ) ) ).toStdString().c_str() );
274  }
275  return result;
276 }
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:107
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.
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.