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 &parameter )
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 &param : 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 &parameter : 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