19 #include <QDesktopServices> 
   24 #include <QTemporaryDir> 
   25 #include <QNetworkRequest> 
   26 #include <QJsonDocument> 
   27 #include <QHttpMultiPart> 
   28 #include <QMimeDatabase> 
   29 #include <QApplication> 
   51 #elif defined(Q_OS_MAC) 
   69 void QgsAction::handleFormSubmitAction( 
const QString &expandedAction )
 const 
   73   QApplication::setOverrideCursor( Qt::WaitCursor );
 
   75   QUrl url{ expandedAction };
 
   78   const QString payload { url.query( QUrl::ComponentFormattingOption::FullyEncoded ).replace( QChar( 
'+' ), QStringLiteral( 
"%2B" ) ) };
 
   81   const QUrlQuery queryString { url.query( ) };
 
   82   url.setQuery( QString( ) );
 
   84   QNetworkRequest req { url };
 
   88   if ( url.toString().contains( QLatin1String( 
"fake_qgis_http_endpoint" ) ) )
 
   90     req.setUrl( QStringLiteral( 
"file://%1" ).arg( url.path() ) );
 
   93   QNetworkReply *reply = 
nullptr;
 
   97     QString contentType { QStringLiteral( 
"application/x-www-form-urlencoded" ) };
 
   99     QJsonParseError jsonError;
 
  100     QJsonDocument::fromJson( payload.toUtf8(), &jsonError );
 
  101     if ( jsonError.error == QJsonParseError::ParseError::NoError )
 
  103       contentType = QStringLiteral( 
"application/json" );
 
  105     req.setHeader( QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType );
 
  111     QHttpMultiPart *multiPart = 
new QHttpMultiPart( QHttpMultiPart::FormDataType );
 
  112     const QList<QPair<QString, QString>> queryItems { queryString.queryItems( QUrl::ComponentFormattingOption::FullyDecoded ) };
 
  113     for ( 
const QPair<QString, QString> &queryItem : std::as_const( queryItems ) )
 
  116       part.setHeader( QNetworkRequest::ContentDispositionHeader,
 
  117                       QStringLiteral( 
"form-data; name=\"%1\"" )
 
  118                       .arg( QString( queryItem.first ).replace( 
'"', QLatin1String( R
"(\")" ) ) ) ); 
  119       part.setBody( queryItem.second.toUtf8() ); 
  120       multiPart->append( part ); 
  123     multiPart->setParent( reply ); 
  126   QObject::connect( reply, &QNetworkReply::finished, reply, [ reply ] 
  128     if ( reply->error() == QNetworkReply::NoError )
 
  131       if ( reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).isNull() )
 
  134         const QByteArray replyData = reply->readAll();
 
  136         QString filename { 
"download.bin" };
 
  137         if ( const std::string header = reply->header( QNetworkRequest::KnownHeaders::ContentDispositionHeader ).toString().toStdString(); ! header.empty() )
 
  141           const std::string q1 { R
"(filename=")" }; 
  142           if ( const unsigned long pos = header.find( q1 ); pos != std::string::npos ) 
  144             const unsigned long len = pos + q1.size(); 
  146             const std::string q2 { R"(")" }; 
  147             if ( unsigned long pos = header.find( q2, len ); pos != std::string::npos ) 
  149               bool escaped = false; 
  150               while ( pos != std::string::npos && header[pos - 1] == '\\' )
 
  152                 pos = header.find( q2, pos + 1 );
 
  155               ascii = header.substr( len, pos - len );
 
  159                 for ( size_t i = 0; i < ascii.size(); ++i )
 
  161                   if ( ascii[i] == 
'\\' )
 
  163                     if ( i > 0 && ascii[i - 1] == 
'\\' )
 
  165                       cleaned.push_back( ascii[i] );
 
  170                     cleaned.push_back( ascii[i] );
 
  180           const std::string u { R
"(UTF-8'')" }; 
  181           if ( const unsigned long pos = header.find( u ); pos != std::string::npos ) 
  183             utf8 = header.substr( pos + u.size() ); 
  189             if ( ! utf8.empty( ) )
 
  191               filename = QString::fromStdString( utf8 );
 
  196             filename = QString::fromStdString( ascii );
 
  199         else if ( !reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).isNull() )
 
  201           QString contentTypeHeader { reply->header( QNetworkRequest::KnownHeaders::ContentTypeHeader ).toString() };
 
  203           if ( contentTypeHeader.contains( 
';' ) )
 
  205             contentTypeHeader = contentTypeHeader.left( contentTypeHeader.indexOf( 
';' ) );
 
  208           QMimeType mimeType { QMimeDatabase().mimeTypeForName( contentTypeHeader ) };
 
  209           if ( mimeType.isValid() )
 
  211             filename = QStringLiteral( 
"download.%1" ).arg( mimeType.preferredSuffix() );
 
  215         QTemporaryDir tempDir;
 
  216         tempDir.setAutoRemove( false );
 
  218         const QString tempFilePath{ tempDir.path() + QDir::separator() + filename };
 
  219         QFile tempFile{ tempFilePath };
 
  220         tempFile.open( QIODevice::WriteOnly );
 
  221         tempFile.write( replyData );
 
  223         QDesktopServices::openUrl( QUrl::fromLocalFile( tempFilePath ) );
 
  227         QgsMessageLog::logMessage( QObject::tr( 
"Redirect is not supported!" ), QStringLiteral( 
"Form Submit Action" ), Qgis::MessageLevel::Critical );
 
  234     reply->deleteLater();
 
  235     QApplication::restoreOverrideCursor( );
 
  242   mCommand = newCommand;
 
  249     QgsDebugMsg( QStringLiteral( 
"Invalid action cannot be run" ) );
 
  258   QApplication::setOverrideCursor( Qt::WaitCursor );
 
  260   QApplication::restoreOverrideCursor();
 
  264     const QFileInfo finfo( expandedAction );
 
  265     if ( finfo.exists() && finfo.isFile() )
 
  266       QDesktopServices::openUrl( QUrl::fromLocalFile( expandedAction ) );
 
  268       QDesktopServices::openUrl( QUrl( expandedAction, QUrl::TolerantMode ) );
 
  272     handleFormSubmitAction( expandedAction );
 
  289   return mActionScopes;
 
  299   QDomElement actionElement = actionNode.toElement();
 
  300   const QDomNodeList actionScopeNodes = actionElement.elementsByTagName( QStringLiteral( 
"actionScope" ) );
 
  302   if ( actionScopeNodes.isEmpty() )
 
  305         << QStringLiteral( 
"Canvas" )
 
  306         << QStringLiteral( 
"Field" )
 
  307         << QStringLiteral( 
"Feature" );
 
  311     for ( 
int j = 0; j < actionScopeNodes.length(); ++j )
 
  313       const QDomElement actionScopeElem = actionScopeNodes.item( j ).toElement();
 
  314       mActionScopes << actionScopeElem.attribute( QStringLiteral( 
"id" ) );
 
  318   mType = 
static_cast< QgsAction::ActionType >( actionElement.attributeNode( QStringLiteral( 
"type" ) ).value().toInt() );
 
  319   mDescription = actionElement.attributeNode( QStringLiteral( 
"name" ) ).value();
 
  320   mCommand = actionElement.attributeNode( QStringLiteral( 
"action" ) ).value();
 
  321   mIcon = actionElement.attributeNode( QStringLiteral( 
"icon" ) ).value();
 
  322   mCaptureOutput = actionElement.attributeNode( QStringLiteral( 
"capture" ) ).value().toInt() != 0;
 
  323   mShortTitle = actionElement.attributeNode( QStringLiteral( 
"shortTitle" ) ).value();
 
  324   mNotificationMessage = actionElement.attributeNode( QStringLiteral( 
"notificationMessage" ) ).value();
 
  325   mIsEnabledOnlyWhenEditable = actionElement.attributeNode( QStringLiteral( 
"isEnabledOnlyWhenEditable" ) ).value().toInt() != 0;
 
  326   mId = QUuid( actionElement.attributeNode( QStringLiteral( 
"id" ) ).value() );
 
  328     mId = QUuid::createUuid();
 
  333   QDomElement actionSetting = actionsNode.ownerDocument().createElement( QStringLiteral( 
"actionsetting" ) );
 
  334   actionSetting.setAttribute( QStringLiteral( 
"type" ), mType );
 
  335   actionSetting.setAttribute( QStringLiteral( 
"name" ), mDescription );
 
  336   actionSetting.setAttribute( QStringLiteral( 
"shortTitle" ), mShortTitle );
 
  337   actionSetting.setAttribute( QStringLiteral( 
"icon" ), mIcon );
 
  338   actionSetting.setAttribute( QStringLiteral( 
"action" ), mCommand );
 
  339   actionSetting.setAttribute( QStringLiteral( 
"capture" ), mCaptureOutput );
 
  340   actionSetting.setAttribute( QStringLiteral( 
"notificationMessage" ), mNotificationMessage );
 
  341   actionSetting.setAttribute( QStringLiteral( 
"isEnabledOnlyWhenEditable" ), mIsEnabledOnlyWhenEditable );
 
  342   actionSetting.setAttribute( QStringLiteral( 
"id" ), mId.toString() );
 
  344   const auto constMActionScopes = mActionScopes;
 
  345   for ( 
const QString &scope : constMActionScopes )
 
  347     QDomElement actionScopeElem = actionsNode.ownerDocument().createElement( QStringLiteral( 
"actionScope" ) );
 
  348     actionScopeElem.setAttribute( QStringLiteral( 
"id" ), scope );
 
  349     actionSetting.appendChild( actionScopeElem );
 
  352   actionsNode.appendChild( actionSetting );
 
  357   mExpressionContextScope = scope;
 
  362   return mExpressionContextScope;
 
  372       typeText = QObject::tr( 
"Generic" );
 
  377       typeText = QObject::tr( 
"Generic Python" );
 
  382       typeText = QObject::tr( 
"Mac" );
 
  387       typeText = QObject::tr( 
"Windows" );
 
  392       typeText = QObject::tr( 
"Unix" );
 
  397       typeText = QObject::tr( 
"Open URL" );
 
  402       typeText = QObject::tr( 
"Submit URL (urlencoded or JSON)" );
 
  407       typeText = QObject::tr( 
"Submit URL (multipart)" );
 
  411   return { QObject::tr( R
"html( 
  412 <h2>Action Details</h2> 
  414    <b>Description:</b> %1<br> 
  415    <b>Short title:</b> %2<br> 
  421   )html" ).arg( mDescription, mShortTitle, typeText, actionScopes().values().join( QLatin1String( ", " ) ), mCommand )};