Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Form submit action and url encode
  • Loading branch information
elpaso authored and nyalldawson committed Dec 7, 2021
1 parent 2b5128e commit 835da71
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 2 deletions.
1 change: 1 addition & 0 deletions python/core/auto_generated/qgsaction.sip.in
Expand Up @@ -27,6 +27,7 @@ Utility class that encapsulates an action based on vector attributes.
Windows,
Unix,
OpenUrl,
SubmitUrl,
};

QgsAction();
Expand Down
1 change: 1 addition & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -948,6 +948,7 @@ QString QgsExpression::group( const QString &name )
sGroups()->insert( QStringLiteral( "Date and Time" ), tr( "Date and Time" ) );
sGroups()->insert( QStringLiteral( "Fields and Values" ), tr( "Fields and Values" ) );
sGroups()->insert( QStringLiteral( "Files and Paths" ), tr( "Files and Paths" ) );
sGroups()->insert( QStringLiteral( "Form Encoding" ), tr( "Form Encoding" ) );
sGroups()->insert( QStringLiteral( "Fuzzy Matching" ), tr( "Fuzzy Matching" ) );
sGroups()->insert( QStringLiteral( "General" ), tr( "General" ) );
sGroups()->insert( QStringLiteral( "GeometryGroup" ), tr( "Geometry" ) );
Expand Down
16 changes: 16 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -69,6 +69,7 @@
#include <QCryptographicHash>
#include <QRegularExpression>
#include <QUuid>
#include <QUrlQuery>

typedef QList<QgsExpressionFunction *> ExpressionFunctionList;

Expand Down Expand Up @@ -6536,6 +6537,17 @@ static QVariant fcnToBase64( const QVariantList &values, const QgsExpressionCont
return QVariant( QString( input.toBase64() ) );
}

static QVariant fcnToFormUrlEncode( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QVariantMap map = QgsExpressionUtils::getMapValue( values.at( 0 ), parent );
QUrlQuery query;
for ( auto it = map.cbegin(); it != map.cend(); it++ )
{
query.addQueryItem( it.key(), it.value().toString() );
}
return query.toString( QUrl::ComponentFormattingOption::FullyEncoded );
}

static QVariant fcnFromBase64( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QString value = QgsExpressionUtils::getStringValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -7281,6 +7293,10 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "from_base64" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ),
fcnFromBase64, QStringLiteral( "Conversions" ) )

// Form encoding
<< new QgsStaticExpressionFunction( QStringLiteral( "url_encode" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "map" ) ),
fcnToFormUrlEncode, QStringLiteral( "Form Encoding" ) )

// deprecated stuff - hidden from users
<< new QgsStaticExpressionFunction( QStringLiteral( "$scale" ), QgsExpressionFunction::ParameterList(), fcnMapScale, QStringLiteral( "deprecated" ) );

Expand Down
10 changes: 10 additions & 0 deletions src/core/qgsaction.cpp
Expand Up @@ -32,6 +32,7 @@ bool QgsAction::runable() const
return mType == Generic ||
mType == GenericPython ||
mType == OpenUrl ||
mType == SubmitUrl ||
#if defined(Q_OS_WIN)
mType == Windows
#elif defined(Q_OS_MAC)
Expand Down Expand Up @@ -74,6 +75,10 @@ void QgsAction::run( const QgsExpressionContext &expressionContext ) const
else
QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
}
else if ( mType == QgsAction::SubmitUrl )
{
// TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
else if ( mType == QgsAction::GenericPython )
{
// TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
Expand Down Expand Up @@ -200,6 +205,11 @@ QString QgsAction::html() const
typeText = QObject::tr( "Open URL" );
break;
}
case SubmitUrl:
{
typeText = QObject::tr( "Submit URL" );
break;
}
}
return { QObject::tr( R"html(
<h2>Action Details</h2>
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsaction.h
Expand Up @@ -42,6 +42,7 @@ class CORE_EXPORT QgsAction
Windows,
Unix,
OpenUrl,
SubmitUrl,
};

/**
Expand Down
114 changes: 113 additions & 1 deletion src/core/qgsactionmanager.cpp
Expand Up @@ -31,6 +31,8 @@
#include "qgsexpression.h"
#include "qgsdataprovider.h"
#include "qgsexpressioncontextutils.h"
#include "qgswebview.h"
#include "qgsnetworkaccessmanager.h"

#include <QList>
#include <QStringList>
Expand All @@ -41,7 +43,10 @@
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>

#include <QDialog>
#include <QLayout>
#include <QNetworkRequest>
#include <QMimeDatabase>

QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
{
Expand Down Expand Up @@ -197,6 +202,113 @@ void QgsActionManager::runAction( const QgsAction &action )
else
QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
}
else if ( action.type() == QgsAction::SubmitUrl )
{
QUrl url{ action.command() };
// Remove payload
QString payload { url.query() };
url.setQuery( QString( ) );
QDialog d;
d.setWindowTitle( tr( "Form Submit Action" ) );
d.setLayout( new QHBoxLayout( &d ) );
QgsWebView *wv = new QgsWebView( &d );
wv->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
#ifdef QGISDEBUG
wv->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
#endif
#ifdef WITH_QTWEBKIT
wv->page()->setForwardUnsupportedContent( true );
#endif
wv->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
wv->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
wv->page()->settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
wv->page()->settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
wv->page()->settings()->setAttribute( QWebSettings::PluginsEnabled, true );


connect( wv->page(), &QWebPage::unsupportedContent, this, [ &d ]( QNetworkReply * reply )
{

QString filename { "unknown.bin" };
if ( const auto header = reply->header( QNetworkRequest::KnownHeaders::ContentDispositionHeader ).toString().toStdString(); ! header.empty() )
{

std::string ascii;
const std::string q1 { R"(filename=")" };
if ( const auto pos = header.find( q1 ); pos != std::string::npos )
{
const auto len = pos + q1.size();

const std::string q2 { R"(")" };
if ( auto pos = header.find( q2, len ); pos != std::string::npos )
{
bool escaped = false;
while ( pos != std::string::npos && header[pos - 1] == '\\' )
{
std::cout << pos << std::endl;
pos = header.find( q2, pos + 1 );
escaped = true;
}
ascii = header.substr( len, pos - len );
if ( escaped )
{
std::string cleaned;
for ( size_t i = 0; i < ascii.size(); ++i )
{
if ( ascii[i] == '\\' )
{
if ( i > 0 && ascii[i - 1] == '\\' )
{
cleaned.push_back( ascii[i] );
}
}
else
{
cleaned.push_back( ascii[i] );
}
}
ascii = cleaned;
}
}
}

std::string utf8;

const std::string u { R"(UTF-8'')" };
if ( const auto pos = header.find( u ); pos != std::string::npos )
{
utf8 = header.substr( pos + u.size() );
}

// Prefer ascii over utf8
if ( ascii.empty() )
{
filename = QString::fromStdString( utf8 );
}
else
{
filename = QString::fromStdString( ascii );
}
}

QTemporaryDir tempDir;
tempDir.setAutoRemove( false );
tempDir.path();
const QString tempFilePath{ tempDir.path() + QDir::separator() + filename };
QFile tempFile{tempFilePath};
tempFile.open( QIODevice::WriteOnly );
tempFile.write( reply->readAll() );
tempFile.close();
d.close();
QDesktopServices::openUrl( QUrl::fromLocalFile( tempFilePath ) );
} );

d.layout()->addWidget( wv );
QNetworkRequest req { url };
// TODO: guess content type req.setHeader();
wv->load( req, QNetworkAccessManager::Operation::PostOperation, payload.toUtf8() );
d.exec();
}
else if ( action.type() == QgsAction::GenericPython )
{
// TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
Expand Down
2 changes: 2 additions & 0 deletions src/gui/vector/qgsattributeactiondialog.cpp
Expand Up @@ -256,6 +256,8 @@ QString QgsAttributeActionDialog::textForType( QgsAction::ActionType type )
return tr( "Unix" );
case QgsAction::OpenUrl:
return tr( "Open URL" );
case QgsAction::SubmitUrl:
return tr( "Submit URL" );
}
return QString();
}
Expand Down
7 changes: 6 additions & 1 deletion src/ui/qgsattributeactionpropertiesdialogbase.ui
Expand Up @@ -234,7 +234,12 @@
</item>
<item>
<property name="text">
<string>Open</string>
<string>Open URL</string>
</property>
</item>
<item>
<property name="text">
<string>Submit URL</string>
</property>
</item>
</widget>
Expand Down
4 changes: 4 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -1980,6 +1980,10 @@ class TestQgsExpression: public QObject
QTest::newRow( "exif bad file path" ) << QStringLiteral( "exif('bad path','Exif.Image.DateTime')" ) << false << QVariant();
QTest::newRow( "exif_geotag" ) << QStringLiteral( "geom_to_wkt(exif_geotag('%1photos/0997.JPG'))" ).arg( testDataDir ) << false << QVariant( "PointZ (149.27516667 -37.2305 422.19101124)" );
QTest::newRow( "exif_geotag bad file path" ) << QStringLiteral( "geom_to_wkt(exif_geotag('bad path'))" ).arg( testDataDir ) << false << QVariant( "Point EMPTY" );

// Form encoding tests
QTest::newRow( "url_encode" ) << QStringLiteral( "url_encode(map())" ).arg( testDataDir ) << false << QVariant( "" );
QTest::newRow( "url_encode" ) << QStringLiteral( "url_encode(map('a b', 'a b', 'c &% d', 'c &% d'))" ).arg( testDataDir ) << false << QVariant( "a%20b=a%20b&c%20%26%25%20d=c%20%26%25%20d" );
}

void run_evaluation_test( QgsExpression &exp, bool evalError, QVariant &expected )
Expand Down

0 comments on commit 835da71

Please sign in to comment.