1 /***************************************************************************
2 qgsserverparameters.cpp
3 --------------------
4 begin : Jun 27, 2018
5 copyright : (C) 2018 by Paul Blottiere
6 email : paul dot blottiere at oslandia dot com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "qgsserverparameters.h"
19 #include "qgsserverexception.h"
20 #include "qgsnetworkcontentfetcher.h"
21 #include "qgsmessagelog.h"
22 #include <QObject>
23 #include <QUrl>
24 #include <QNetworkReply>
25 #include <QNetworkRequest>
26 #include <QEventLoop>
27
28 //
29 // QgsServerParameterDefinition
30 //
QgsServerParameterDefinition(const QVariant::Type type,const QVariant defaultValue)31 QgsServerParameterDefinition::QgsServerParameterDefinition( const QVariant::Type type,
32 const QVariant defaultValue )
33 : mType( type )
34 , mDefaultValue( defaultValue )
35 {
36 }
37
typeName() const38 QString QgsServerParameterDefinition::typeName() const
39 {
40 return QVariant::typeToName( mType );
41 }
42
toColor(bool & ok) const43 QColor QgsServerParameterDefinition::toColor( bool &ok ) const
44 {
45 ok = true;
46 QColor color = mDefaultValue.value<QColor>();
47 QString cStr = mValue.toString();
48
49 if ( !cStr.isEmpty() )
50 {
51 // support hexadecimal notation to define colors
52 if ( cStr.startsWith( QLatin1String( "0x" ), Qt::CaseInsensitive ) )
53 {
54 cStr.replace( 0, 2, QStringLiteral( "#" ) );
55 }
56
57 color = QColor( cStr );
58
59 ok = color.isValid();
60 }
61
62 return color;
63 }
64
toString(const bool defaultValue) const65 QString QgsServerParameterDefinition::toString( const bool defaultValue ) const
66 {
67 QString value = mValue.toString();
68
69 if ( value.isEmpty() && defaultValue )
70 value = mDefaultValue.toString();
71
72 return value;
73 }
74
toStringList(const char delimiter,const bool skipEmptyParts) const75 QStringList QgsServerParameterDefinition::toStringList( const char delimiter, const bool skipEmptyParts ) const
76 {
77 if ( skipEmptyParts )
78 {
79 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
80 return toString().split( delimiter, QString::SkipEmptyParts );
81 #else
82 return toString().split( delimiter, Qt::SkipEmptyParts );
83 #endif
84 }
85 else
86 {
87 QStringList list;
88 if ( !toString().isEmpty() )
89 {
90 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
91 list = toString().split( delimiter, QString::KeepEmptyParts );
92 #else
93 list = toString().split( delimiter, Qt::KeepEmptyParts );
94 #endif
95 }
96 return list;
97 }
98 }
99
toGeomList(bool & ok,const char delimiter) const100 QList<QgsGeometry> QgsServerParameterDefinition::toGeomList( bool &ok, const char delimiter ) const
101 {
102 ok = true;
103 QList<QgsGeometry> geoms;
104
105 const auto constStringList( toStringList( delimiter ) );
106 for ( const auto &wkt : constStringList )
107 {
108 const QgsGeometry g( QgsGeometry::fromWkt( wkt ) );
109
110 if ( g.isGeosValid() )
111 {
112 geoms.append( g );
113 }
114 else
115 {
116 ok = false;
117 return QList<QgsGeometry>();
118 }
119 }
120
121 return geoms;
122 }
123
toColorList(bool & ok,const char delimiter) const124 QList<QColor> QgsServerParameterDefinition::toColorList( bool &ok, const char delimiter ) const
125 {
126 ok = true;
127 QList<QColor> colors;
128
129 const auto constStringList( toStringList( delimiter ) );
130 for ( const auto &part : constStringList )
131 {
132 QString cStr( part );
133 if ( !cStr.isEmpty() )
134 {
135 // support hexadecimal notation to define colors
136 if ( cStr.startsWith( QLatin1String( "0x" ), Qt::CaseInsensitive ) )
137 {
138 cStr.replace( 0, 2, QStringLiteral( "#" ) );
139 }
140
141 const QColor color = QColor( cStr );
142 ok = color.isValid();
143
144 if ( !ok )
145 {
146 return QList<QColor>();
147 }
148
149 colors.append( color );
150 }
151 }
152
153 return colors;
154 }
155
toIntList(bool & ok,const char delimiter) const156 QList<int> QgsServerParameterDefinition::toIntList( bool &ok, const char delimiter ) const
157 {
158 ok = true;
159 QList<int> ints;
160
161 const auto constStringList( toStringList( delimiter ) );
162 for ( const auto &part : constStringList )
163 {
164 const int val = part.toInt( &ok );
165
166 if ( !ok )
167 {
168 return QList<int>();
169 }
170
171 ints.append( val );
172 }
173
174 return ints;
175 }
176
toDoubleList(bool & ok,const char delimiter) const177 QList<double> QgsServerParameterDefinition::toDoubleList( bool &ok, const char delimiter ) const
178 {
179 ok = true;
180 QList<double> vals;
181
182 const auto constStringList( toStringList( delimiter ) );
183 for ( const auto &part : constStringList )
184 {
185 const double val = part.toDouble( &ok );
186
187 if ( !ok )
188 {
189 return QList<double>();
190 }
191
192 vals.append( val );
193 }
194
195 return vals;
196 }
197
toRectangle(bool & ok) const198 QgsRectangle QgsServerParameterDefinition::toRectangle( bool &ok ) const
199 {
200 ok = true;
201 QgsRectangle extent;
202
203 if ( !mValue.toString().isEmpty() )
204 {
205 QStringList corners = mValue.toString().split( ',' );
206
207 if ( corners.size() == 4 )
208 {
209 double d[4];
210
211 for ( int i = 0; i < 4; i++ )
212 {
213 corners[i].replace( ' ', '+' );
214 d[i] = corners[i].toDouble( &ok );
215 if ( !ok )
216 {
217 return QgsRectangle();
218 }
219 }
220
221 if ( d[0] > d[2] || d[1] > d[3] )
222 {
223 ok = false;
224 return QgsRectangle();
225 }
226
227 extent = QgsRectangle( d[0], d[1], d[2], d[3] );
228 }
229 else
230 {
231 ok = false;
232 return QgsRectangle();
233 }
234 }
235
236 return extent;
237 }
238
loadUrl(bool & ok) const239 QString QgsServerParameterDefinition::loadUrl( bool &ok ) const
240 {
241 ok = true;
242
243 // Get URL
244 const QUrl url = toUrl( ok );
245 if ( !ok )
246 {
247 return QString();
248 }
249
250 // fetching content
251 QgsNetworkContentFetcher fetcher;
252 QEventLoop loop;
253 QObject::connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
254
255 QgsMessageLog::logMessage(
256 QObject::tr( "Request started [url: %1]" ).arg( url.toString() ),
257 QStringLiteral( "Server" ) );
258 QNetworkRequest request( url );
259 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
260 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
261 fetcher.fetchContent( request );
262
263 //wait until content fetched
264 loop.exec( QEventLoop::ExcludeUserInputEvents );
265
266 QNetworkReply *reply = fetcher.reply();
267 if ( !reply )
268 {
269 ok = false;
270 QgsMessageLog::logMessage(
271 QObject::tr( "Request failed [error: no reply - url: %1]" ).arg( url.toString() ),
272 QStringLiteral( "Server" ) );
273 return QString();
274 }
275
276 const QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
277 if ( !status.isNull() && status.toInt() >= 400 )
278 {
279 ok = false;
280 if ( reply->error() != QNetworkReply::NoError )
281 {
282 QgsMessageLog::logMessage(
283 QObject::tr( "Request failed [error: %1 - url: %2]" ).arg( reply->errorString(), reply->url().toString() ),
284 QStringLiteral( "Server" ) );
285 }
286 const QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
287 QgsMessageLog::logMessage(
288 QObject::tr( "Request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), reply->url().toString() ),
289 QStringLiteral( "Server" ) );
290 return QString();
291 }
292
293 if ( reply->error() != QNetworkReply::NoError )
294 {
295 ok = false;
296 QgsMessageLog::logMessage(
297 QObject::tr( "Request failed [error: %1 - url: %2]" ).arg( reply->errorString(), reply->url().toString() ),
298 QStringLiteral( "Server" ) );
299 return QString();
300 }
301
302 QgsMessageLog::logMessage(
303 QObject::tr( "Request finished [url: %1]" ).arg( url.toString() ),
304 QStringLiteral( "Server" ) );
305
306 QString content = fetcher.contentAsString();
307 ok = ( !content.isEmpty() );
308 return content;
309 }
310
toUrl(bool & ok) const311 QUrl QgsServerParameterDefinition::toUrl( bool &ok ) const
312 {
313 ok = true;
314 QUrl val;
315
316 if ( !mValue.toString().isEmpty() )
317 {
318 val = mValue.toUrl();
319 }
320
321 ok = ( !val.isEmpty() && val.isValid() );
322 return val;
323 }
324
toInt(bool & ok) const325 int QgsServerParameterDefinition::toInt( bool &ok ) const
326 {
327 ok = true;
328 int val = mDefaultValue.toInt();
329
330 if ( !mValue.toString().isEmpty() )
331 {
332 val = mValue.toInt( &ok );
333 }
334
335 return val;
336 }
337
toBool() const338 bool QgsServerParameterDefinition::toBool() const
339 {
340 int val = mDefaultValue.toBool();
341
342 if ( !mValue.toString().isEmpty() )
343 {
344 val = mValue.toBool();
345 }
346
347 return val;
348 }
349
toDouble(bool & ok) const350 double QgsServerParameterDefinition::toDouble( bool &ok ) const
351 {
352 ok = true;
353 double val = mDefaultValue.toDouble();
354
355 if ( !mValue.toString().isEmpty() )
356 {
357 val = mValue.toDouble( &ok );
358 }
359
360 return val;
361 }
362
isValid() const363 bool QgsServerParameterDefinition::isValid() const
364 {
365 return mValue.canConvert( mType );
366 }
367
raiseError(const QString & msg)368 void QgsServerParameterDefinition::raiseError( const QString &msg )
369 {
370 throw QgsBadRequestException( QStringLiteral( "Invalid Parameter" ), msg );
371 }
372
373 //
374 // QgsServerParameter
375 //
QgsServerParameter(const QgsServerParameter::Name name,const QVariant::Type type,const QVariant defaultValue)376 QgsServerParameter::QgsServerParameter( const QgsServerParameter::Name name,
377 const QVariant::Type type, const QVariant defaultValue )
378 : QgsServerParameterDefinition( type, defaultValue )
379 , mName( name )
380 {
381 }
382
name(const QgsServerParameter::Name name)383 QString QgsServerParameter::name( const QgsServerParameter::Name name )
384 {
385 if ( name == QgsServerParameter::VERSION_SERVICE )
386 {
387 return QStringLiteral( "VERSION" );
388 }
389 else
390 {
391 const QMetaEnum metaEnum( QMetaEnum::fromType<QgsServerParameter::Name>() );
392 return metaEnum.valueToKey( name );
393 }
394 }
395
name(const QString & name)396 QgsServerParameter::Name QgsServerParameter::name( const QString &name )
397 {
398 if ( name.compare( QLatin1String( "VERSION" ) ) == 0 )
399 {
400 return QgsServerParameter::VERSION_SERVICE;
401 }
402 else
403 {
404 const QMetaEnum metaEnum( QMetaEnum::fromType<QgsServerParameter::Name>() );
405 return ( QgsServerParameter::Name ) metaEnum.keyToValue( name.toUpper().toStdString().c_str() );
406 }
407 }
408
raiseError() const409 void QgsServerParameter::raiseError() const
410 {
411 const QString msg = QString( "%1 ('%2') cannot be converted into %3" ).arg( name( mName ), mValue.toString(), typeName() );
412 QgsServerParameterDefinition::raiseError( msg );
413 }
414
415 //
416 // QgsServerParameters
417 //
QgsServerParameters()418 QgsServerParameters::QgsServerParameters()
419 {
420 save( QgsServerParameter( QgsServerParameter::SERVICE ) );
421 save( QgsServerParameter( QgsServerParameter::REQUEST ) );
422 save( QgsServerParameter( QgsServerParameter::VERSION_SERVICE ) );
423 save( QgsServerParameter( QgsServerParameter::MAP ) );
424 save( QgsServerParameter( QgsServerParameter::FILE_NAME ) );
425 }
426
QgsServerParameters(const QUrlQuery & query)427 QgsServerParameters::QgsServerParameters( const QUrlQuery &query )
428 : QgsServerParameters()
429 {
430 mUrlQuery = query;
431 load( query );
432 }
433
save(const QgsServerParameter & parameter)434 void QgsServerParameters::save( const QgsServerParameter ¶meter )
435 {
436 mParameters[ parameter.mName ] = parameter;
437 }
438
add(const QString & key,const QString & value)439 void QgsServerParameters::add( const QString &key, const QString &value )
440 {
441 QUrlQuery query;
442 query.addQueryItem( key, value );
443 load( query );
444 }
445
urlQuery() const446 QUrlQuery QgsServerParameters::urlQuery() const
447 {
448 QUrlQuery query = mUrlQuery;
449
450 if ( query.isEmpty() )
451 {
452 query.clear();
453
454 const auto constMap( toMap().toStdMap() );
455 for ( const auto ¶m : constMap )
456 {
457 const QString value = QUrl::toPercentEncoding( QString( param.second ) );
458 query.addQueryItem( param.first, value );
459 }
460 }
461
462 return query;
463 }
464
remove(QgsServerParameter::Name name)465 void QgsServerParameters::remove( QgsServerParameter::Name name )
466 {
467 remove( QgsServerParameter::name( name ) );
468 }
469
remove(const QString & key)470 void QgsServerParameters::remove( const QString &key )
471 {
472 if ( mUnmanagedParameters.contains( key ) )
473 {
474 mUnmanagedParameters.take( key );
475 }
476 else
477 {
478 const QgsServerParameter::Name paramName = QgsServerParameter::name( key );
479 if ( mParameters.contains( paramName ) )
480 {
481 mParameters.take( paramName );
482 }
483 }
484 }
485
map() const486 QString QgsServerParameters::map() const
487 {
488 return value( QgsServerParameter::MAP ).toString();
489 }
490
version() const491 QString QgsServerParameters::version() const
492 {
493 return value( QgsServerParameter::VERSION_SERVICE ).toString();
494 }
495
fileName() const496 QString QgsServerParameters::fileName() const
497 {
498 return value( QgsServerParameter::FILE_NAME ).toString();
499 }
500
service() const501 QString QgsServerParameters::service() const
502 {
503 QString serviceValue = value( QgsServerParameter::SERVICE ).toString();
504
505 if ( serviceValue.isEmpty() )
506 {
507 // SERVICE not mandatory for WMS 1.3.0 GetMap & GetFeatureInfo
508 if ( request() == QLatin1String( "GetMap" ) \
509 || request() == QLatin1String( "GetFeatureInfo" ) )
510 {
511 serviceValue = "WMS";
512 }
513 }
514
515 return serviceValue;
516 }
517
toMap() const518 QMap<QString, QString> QgsServerParameters::toMap() const
519 {
520 QMap<QString, QString> params = mUnmanagedParameters;
521
522 for ( const auto ¶meter : mParameters.toStdMap() )
523 {
524 if ( parameter.second.mValue.isNull() )
525 continue;
526
527 if ( parameter.second.mName == QgsServerParameter::VERSION_SERVICE )
528 {
529 params["VERSION"] = parameter.second.mValue.toString();
530 }
531 else
532 {
533 const QString paramName = QgsServerParameter::name( parameter.first );
534 params[paramName] = parameter.second.mValue.toString();
535 }
536 }
537
538 return params;
539 }
540
request() const541 QString QgsServerParameters::request() const
542 {
543 return value( QgsServerParameter::REQUEST ).toString();
544 }
545
value(const QString & key) const546 QString QgsServerParameters::value( const QString &key ) const
547 {
548 if ( ! mParameters.contains( QgsServerParameter::name( key ) ) )
549 {
550 return mUnmanagedParameters[key];
551 }
552 else
553 {
554 return value( QgsServerParameter::name( key ) ).toString();
555 }
556 }
557
value(QgsServerParameter::Name name) const558 QVariant QgsServerParameters::value( QgsServerParameter::Name name ) const
559 {
560 return mParameters[name].mValue;
561 }
562
load(const QUrlQuery & query)563 void QgsServerParameters::load( const QUrlQuery &query )
564 {
565 // clean query string first
566 QUrlQuery cleanQuery( query );
567 cleanQuery.setQuery( query.query().replace( '+', QLatin1String( "%20" ) ) );
568
569 // load parameters
570 const auto constQueryItems( cleanQuery.queryItems( QUrl::FullyDecoded ) );
571 for ( const auto &item : constQueryItems )
572 {
573 const QgsServerParameter::Name name = QgsServerParameter::name( item.first );
574 if ( name >= 0 )
575 {
576 mParameters[name].mValue = item.second;
577 if ( ! mParameters[name].isValid() )
578 {
579 mParameters[name].raiseError();
580 }
581 }
582 else if ( item.first.compare( QLatin1String( "VERSION" ), Qt::CaseInsensitive ) == 0 )
583 {
584 const QgsServerParameter::Name name = QgsServerParameter::VERSION_SERVICE;
585 mParameters[name].mValue = item.second;
586 if ( ! mParameters[name].isValid() )
587 {
588 mParameters[name].raiseError();
589 }
590 }
591 else if ( ! loadParameter( item.first, item.second ) )
592 {
593 mUnmanagedParameters[item.first.toUpper()] = item.second;
594 }
595 }
596 }
597
loadParameter(const QString &,const QString &)598 bool QgsServerParameters::loadParameter( const QString &, const QString & )
599 {
600 return false;
601 }
602
clear()603 void QgsServerParameters::clear()
604 {
605 mParameters.clear();
606 mUnmanagedParameters.clear();
607 }
608