Skip to content

Commit

Permalink
Merge pull request #9833 from pblottiere/create_image
Browse files Browse the repository at this point in the history
[server] Sanitise image creation
  • Loading branch information
pblottiere committed May 22, 2019
2 parents 86f35e8 + 4e0a90a commit 10f6c8d
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 233 deletions.
19 changes: 19 additions & 0 deletions src/server/services/wms/qgswmsgetfeatureinfo.cpp
Expand Up @@ -31,6 +31,25 @@ namespace QgsWms
// get wms parameters from query
QgsWmsParameters parameters( QUrlQuery( request.url() ) );

// WIDTH and HEIGHT are not mandatory, but we need to set a default size
if ( ( parameters.widthAsInt() <= 0
|| parameters.heightAsInt() <= 0 )
&& ! parameters.infoFormatIsImage() )
{
QSize size( 10, 10 );

if ( ! parameters.filterGeom().isEmpty() )
{
const QgsRectangle bbox = QgsGeometry::fromWkt( parameters.filterGeom() ).boundingBox();
const int defaultWidth = 800;
size.setWidth( defaultWidth );
size.setHeight( defaultWidth * bbox.height() / bbox.width() );
}

parameters.set( QgsWmsParameter::WIDTH, size.width() );
parameters.set( QgsWmsParameter::HEIGHT, size.height() );
}

// prepare render context
QgsWmsRenderContext context( project, serverIface );
context.setFlag( QgsWmsRenderContext::AddQueryLayers );
Expand Down
1 change: 1 addition & 0 deletions src/server/services/wms/qgswmsgetlegendgraphics.cpp
Expand Up @@ -47,6 +47,7 @@ namespace QgsWms
// init render context
QgsWmsRenderContext context( project, serverIface );
context.setFlag( QgsWmsRenderContext::UseScaleDenominator );
context.setFlag( QgsWmsRenderContext::UseSrcWidthHeight );
context.setParameters( parameters );

const QString format = request.parameters().value( QStringLiteral( "FORMAT" ), QStringLiteral( "PNG" ) );
Expand Down
5 changes: 5 additions & 0 deletions src/server/services/wms/qgswmsparameters.cpp
Expand Up @@ -552,6 +552,11 @@ namespace QgsWms
return mWmsParameters[name];
}

void QgsWmsParameters::set( QgsWmsParameter::Name name, const QVariant &value )
{
mWmsParameters[name].mValue = value;
}

bool QgsWmsParameters::loadParameter( const QString &key, const QString &value )
{
bool loaded = false;
Expand Down
6 changes: 6 additions & 0 deletions src/server/services/wms/qgswmsparameters.h
Expand Up @@ -371,6 +371,12 @@ namespace QgsWms
*/
QgsWmsParameter operator[]( QgsWmsParameter::Name name ) const;

/**
* Sets a parameter \a value thanks to its \a name.
* \since QGIS 3.8
*/
void set( QgsWmsParameter::Name name, const QVariant &value );

/**
* Dumps parameters.
*/
Expand Down
158 changes: 158 additions & 0 deletions src/server/services/wms/qgswmsrendercontext.cpp
Expand Up @@ -538,6 +538,164 @@ QMap<QString, QList<QgsMapLayer *> > QgsWmsRenderContext::layerGroups() const
return mLayerGroups;
}

int QgsWmsRenderContext::mapWidth() const
{
int width = mParameters.widthAsInt();

// May use SRCWIDTH to define image map size
if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcWidthAsInt() > 0 )
{
width = mParameters.srcWidthAsInt();
}

return width;
}

int QgsWmsRenderContext::mapHeight() const
{
int height = mParameters.heightAsInt();

// May use SRCHEIGHT to define image map size
if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcHeightAsInt() > 0 )
{
height = mParameters.srcHeightAsInt();
}

return height;
}

bool QgsWmsRenderContext::isValidWidthHeight() const
{
//test if maxWidth / maxHeight are set in the project or as an env variable
//and WIDTH / HEIGHT parameter is in the range allowed range
//WIDTH
const int wmsMaxWidthProj = QgsServerProjectUtils::wmsMaxWidth( *mProject );
const int wmsMaxWidthEnv = settings().wmsMaxWidth();
int wmsMaxWidth;
if ( wmsMaxWidthEnv != -1 && wmsMaxWidthProj != -1 )
{
// both are set, so we take the more conservative one
wmsMaxWidth = std::min( wmsMaxWidthProj, wmsMaxWidthEnv );
}
else
{
// none or one are set, so we take the bigger one which is the one set or -1
wmsMaxWidth = std::max( wmsMaxWidthProj, wmsMaxWidthEnv );
}

if ( wmsMaxWidth != -1 && mapWidth() > wmsMaxWidth )
{
return false;
}

//HEIGHT
const int wmsMaxHeightProj = QgsServerProjectUtils::wmsMaxHeight( *mProject );
const int wmsMaxHeightEnv = settings().wmsMaxHeight();
int wmsMaxHeight;
if ( wmsMaxHeightEnv != -1 && wmsMaxHeightProj != -1 )
{
// both are set, so we take the more conservative one
wmsMaxHeight = std::min( wmsMaxHeightProj, wmsMaxHeightEnv );
}
else
{
// none or one are set, so we take the bigger one which is the one set or -1
wmsMaxHeight = std::max( wmsMaxHeightProj, wmsMaxHeightEnv );
}

if ( wmsMaxHeight != -1 && mapHeight() > wmsMaxHeight )
{
return false;
}

// Sanity check from internal QImage checks (see qimage.cpp)
// this is to report a meaningful error message in case of
// image creation failure and to differentiate it from out
// of memory conditions.

// depth for now it cannot be anything other than 32, but I don't like
// to hardcode it: I hope we will support other depths in the future.
uint depth = 32;
switch ( mParameters.format() )
{
case QgsWmsParameters::Format::JPG:
case QgsWmsParameters::Format::PNG:
default:
depth = 32;
}

const int bytes_per_line = ( ( mapWidth() * depth + 31 ) >> 5 ) << 2; // bytes per scanline (must be multiple of 4)

if ( std::numeric_limits<int>::max() / depth < static_cast<uint>( mapWidth() )
|| bytes_per_line <= 0
|| mapHeight() <= 0
|| std::numeric_limits<int>::max() / static_cast<uint>( bytes_per_line ) < static_cast<uint>( mapHeight() )
|| std::numeric_limits<int>::max() / sizeof( uchar * ) < static_cast<uint>( mapHeight() ) )
{
return false;
}

return true;
}

QSize QgsWmsRenderContext::mapSize( const bool aspectRatio ) const
{
int width = mapWidth();
int height = mapHeight();

// Adapt width / height if the aspect ratio does not correspond with the BBOX.
// Required by WMS spec. 1.3.
if ( aspectRatio
&& mParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
{
QgsRectangle extent = mParameters.bboxAsRectangle();
if ( !mParameters.bbox().isEmpty() && extent.isEmpty() )
{
throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
mParameters[QgsWmsParameter::BBOX] );
}

QString crs = mParameters.crs();
if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
{
crs = QString( "EPSG:4326" );
extent.invert();
}

QgsCoordinateReferenceSystem outputCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs );
if ( outputCrs.hasAxisInverted() )
{
extent.invert();
}

if ( !extent.isEmpty() && height > 0 && width > 0 )
{
const double mapRatio = extent.width() / extent.height();
const double imageRatio = static_cast<double>( width ) / static_cast<double>( height );
if ( !qgsDoubleNear( mapRatio, imageRatio, 0.0001 ) )
{
// inspired by MapServer, mapdraw.c L115
const double cellsize = ( extent.width() / static_cast<double>( width ) ) * 0.5 + ( extent.height() / static_cast<double>( height ) ) * 0.5;
width = extent.width() / cellsize;
height = extent.height() / cellsize;
}
}
}

if ( width <= 0 )
{
throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
mParameters[QgsWmsParameter::WIDTH] );
}
else if ( height <= 0 )
{
throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
mParameters[QgsWmsParameter::HEIGHT] );
}

return QSize( width, height );
}

void QgsWmsRenderContext::removeUnwantedLayers()
{
QList<QgsMapLayer *> layers;
Expand Down
27 changes: 26 additions & 1 deletion src/server/services/wms/qgswmsrendercontext.h
Expand Up @@ -46,7 +46,8 @@ namespace QgsWms
SetAccessControl = 0x40,
AddQueryLayers = 0x80,
UseWfsLayersOnly = 0x100,
AddExternalLayers = 0x200
AddExternalLayers = 0x200,
UseSrcWidthHeight = 0x400
};
Q_DECLARE_FLAGS( Flags, Flag )

Expand Down Expand Up @@ -199,6 +200,30 @@ namespace QgsWms
*/
QMap<QString, QList<QgsMapLayer *> > layerGroups() const;

/**
* Returns the size (in pixels) of the map to render, according to width
* and height WMS parameters as well as the \a aspectRatio option.
* \since QGIS 3.8
*/
QSize mapSize( bool aspectRatio = true ) const;

/**
* Returns true if width and height are valid according to the maximum
* values defined within the project, false otherwise.
* \since QGIS 3.8
*/
bool isValidWidthHeight() const;

/**
* Returns WIDTH or SRCWIDTH according to \a UseSrcWidthHeight flag.
*/
int mapWidth() const;

/**
* Returns HEIGHT or SRCHEIGHT according to \a UseSrcWidthHeight flag.
*/
int mapHeight() const;

private:
void initNicknameLayers();
void initRestrictedLayers();
Expand Down

0 comments on commit 10f6c8d

Please sign in to comment.