diff --git a/CHANGES b/CHANGES index 07c5c8b9c..720908ca4 100644 --- a/CHANGES +++ b/CHANGES @@ -15,10 +15,9 @@ This release contains contributions from (alphabetically by first name): ## Core ## - Uploading your log files (in case of installation failure) has been - expanded and is now more - configurable. Users should still take care when uploading logs, - and distro's should configure a URL with no public viewing - for the uploading of those logs. (Thanks Anubhav) + expanded and is now more configurable. Users should still take care + when uploading logs, and distro's should configure a URL with + no public viewing of those logs. (Thanks Anubhav) ## Modules ## - A new QML-based *finishedq* module has been added. (Thanks Anke) diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index cd83b02f1..90f92b5f1 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -220,15 +220,13 @@ slideshowAPI: 2 # These options are to customize online uploading of logs to pastebins: -# - type : Defines the kind of pastebin service to be used.Currently -# it accepts two values: -# - none : disables the pastebin functionality -# - fiche : use fiche pastebin server -# - url : Defines the address of pastebin service to be used. -# Takes string as input -# - port : Defines the port number to be used to send logs. Takes -# integer as input +# - type : Defines the kind of pastebin service to be used. Currently +# it accepts two values: +# - none : disables the pastebin functionality +# - fiche : use fiche pastebin server +# - url : Defines the address of pastebin service to be used. +# Takes string as input. Important bits are the host and port, +# the scheme is not used. uploadServer : type : "fiche" - url : "termbin.com" - port : 9999 + url : "http://termbin.com:9999" diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index a71675e38..3668c0b4b 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -138,6 +138,32 @@ loadStrings( QMap< QString, QString >& map, } } +static Branding::UploadServerInfo +uploadServerFromMap( const QVariantMap& map ) +{ + using Type = Branding::UploadServerType; + // *INDENT-OFF* + // clang-format off + static const NamedEnumTable< Type > names { + { "none", Type::None }, + { "fiche", Type::Fiche } + }; + // clang-format on + // *INDENT-ON* + + QString typestring = map[ "type" ].toString(); + QString urlstring = map[ "url" ].toString(); + + if ( typestring.isEmpty() || urlstring.isEmpty() ) + { + return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl() ); + } + + bool bogus = false; // we don't care about type-name lookup success here + return Branding::UploadServerInfo( names.find( typestring, bogus ), + QUrl( urlstring, QUrl::ParsingMode::StrictMode ) ); +} + /** @brief Load the @p map with strings from @p config * * If os-release is supported (with KF5 CoreAddons >= 5.58) then @@ -227,11 +253,7 @@ Branding::Branding( const QString& brandingFilePath, QObject* parent ) } ); loadStrings( m_style, doc, "style", []( const QString& s ) -> QString { return s; } ); - const QVariantMap temp = CalamaresUtils::yamlMapToVariant( doc[ "uploadServer" ] ); - for ( auto it = temp.constBegin(); it != temp.constEnd(); ++it ) - { - m_uploadServer.insert( it.key(), it.value().toString() ); - } + m_uploadServer = uploadServerFromMap( CalamaresUtils::yamlMapToVariant( doc[ "uploadServer" ] ) ); } catch ( YAML::Exception& e ) { @@ -292,12 +314,6 @@ Branding::imagePath( Branding::ImageEntry imageEntry ) const return m_images.value( s_imageEntryStrings.value( imageEntry ) ); } -QString -Branding::uploadServer( Branding::UploadServerEntry uploadServerEntry ) const -{ - return m_uploadServer.value( s_uploadServerStrings.value( uploadServerEntry ) ); -} - QPixmap Branding::image( Branding::ImageEntry imageEntry, const QSize& size ) const { diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index 87f71e862..831b2adec 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -83,13 +83,16 @@ public: }; Q_ENUM( StyleEntry ) - enum UploadServerEntry : short + /** @brief Supported log-upload servers. + * + * 'None' is here as a fallback. + */ + enum UploadServerType : short { - Type, - URL, - Port + None, + Fiche }; - Q_ENUM( UploadServerEntry ) + Q_ENUM( UploadServerType ) /** @brief Setting for how much the main window may expand. */ enum class WindowExpansion @@ -218,6 +221,14 @@ public: ///@brief Which navigation flavor is configured PanelFlavor navigationFlavor() const { return m_navigationFlavor; } + /** @brief Upload server configuration + * + * This is both the type (which may be none, in which case the URL + * is irrelevant and usually empty) and the URL for the upload. + */ + using UploadServerInfo = QPair< UploadServerType, QUrl >; + UploadServerInfo uploadServer() const { return m_uploadServer; } + /** * Creates a map called "branding" in the global storage, and inserts an * entry for each of the branding strings. This makes the branding @@ -234,7 +245,6 @@ public slots: QString styleString( StyleEntry styleEntry ) const; QString imagePath( ImageEntry imageEntry ) const; - QString uploadServer( UploadServerEntry uploadServerEntry ) const; PanelSide sidebarSide() const { return m_sidebarSide; } PanelSide navigationSide() const { return m_navigationSide; } @@ -252,7 +262,7 @@ private: QMap< QString, QString > m_strings; QMap< QString, QString > m_images; QMap< QString, QString > m_style; - QMap< QString, QString > m_uploadServer; + UploadServerInfo m_uploadServer; /* The slideshow can be done in one of two ways: * - as a sequence of images diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index f48008c2d..4afdc6614 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -111,3 +111,12 @@ foreach( subdir modulesystem utils viewpages widgets ) file( GLOB subdir_headers "${subdir}/*.h" ) install( FILES ${subdir_headers} DESTINATION include/libcalamares/${subdir} ) endforeach() + +calamares_add_test( + test_libcalamaresuipaste + SOURCES + utils/TestPaste.cpp + utils/Paste.cpp + LIBRARIES + calamaresui +) diff --git a/src/libcalamaresui/ViewManager.cpp b/src/libcalamaresui/ViewManager.cpp index 4e53fb90b..b22d34c88 100644 --- a/src/libcalamaresui/ViewManager.cpp +++ b/src/libcalamaresui/ViewManager.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -142,8 +143,8 @@ ViewManager::insertViewStep( int before, ViewStep* step ) void ViewManager::onInstallationFailed( const QString& message, const QString& details ) { - QString serverType = Calamares::Branding::instance()->uploadServer( Calamares::Branding::Type ); - bool shouldOfferWebPaste = CalamaresUtils::UploadServersList.contains( serverType ); + bool shouldOfferWebPaste + = Calamares::Branding::instance()->uploadServer().first != Calamares::Branding::UploadServerType::None; cError() << "Installation failed:" << message; cDebug() << Logger::SubEntry << "- message:" << message; @@ -189,25 +190,26 @@ ViewManager::onInstallationFailed( const QString& message, const QString& detail connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton* button ) { if ( msgBox->buttonRole( button ) == QMessageBox::ButtonRole::YesRole ) { - QString pasteUrlMsg; - QString serverType = Calamares::Branding::instance()->uploadServer( Calamares::Branding::Type ); - if ( serverType == "fiche" ) + QString pasteUrl = CalamaresUtils::Paste::doLogUpload( msgBox ); + QString pasteUrlMessage; + if ( pasteUrl.isEmpty() ) { - pasteUrlMsg = CalamaresUtils::ficheLogUpload( msgBox ); + pasteUrlMessage = tr( "The upload was unsuccessful. No web-paste was done." ); } else { - pasteUrlMsg = QString(); + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText( pasteUrl, QClipboard::Clipboard ); + + if ( clipboard->supportsSelection() ) + { + clipboard->setText( pasteUrl, QClipboard::Selection ); + } + QString pasteUrlFmt = tr( "Install log posted to\n\n%1\n\nLink copied to clipboard" ); + pasteUrlMessage = pasteUrlFmt.arg( pasteUrl ); } - QString pasteUrlTitle = tr( "Install Log Paste URL" ); - if ( pasteUrlMsg.isEmpty() ) - { - pasteUrlMsg = tr( "The upload was unsuccessful. No web-paste was done." ); - } - - // TODO: make the URL clickable, or copy it to the clipboard automatically - QMessageBox::critical( nullptr, pasteUrlTitle, pasteUrlMsg ); + QMessageBox::critical( nullptr, tr( "Install Log Paste URL" ), pasteUrlMessage ); } QApplication::quit(); } ); diff --git a/src/libcalamaresui/utils/Paste.cpp b/src/libcalamaresui/utils/Paste.cpp index 27ae7f8f7..2d2f5404e 100644 --- a/src/libcalamaresui/utils/Paste.cpp +++ b/src/libcalamaresui/utils/Paste.cpp @@ -10,49 +10,36 @@ #include "Paste.h" #include "Branding.h" +#include "DllMacro.h" #include "utils/Logger.h" #include -#include #include #include -#include -#include -#include -namespace CalamaresUtils -{ - -QStringList UploadServersList = { - "fiche" - // In future more serverTypes can be added as Calamares support them - // "none" serverType is explicitly not mentioned here -}; - -QString -ficheLogUpload( QObject* parent ) +/** @brief Reads the logfile, returns its contents. + * + * Returns an empty QByteArray() on any kind of error. + */ +STATICTEST QByteArray +logFileContents() { - - const QString& ficheHost = Calamares::Branding::instance()->uploadServer( Calamares::Branding::URL ); - quint16 fichePort = Calamares::Branding::instance()->uploadServer( Calamares::Branding::Port ).toInt(); - - QString pasteUrlFmt = parent->tr( "Install log posted to\n\n%1\n\nLink copied to clipboard" ); - QFile pasteSourceFile( Logger::logFile() ); if ( !pasteSourceFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) { cError() << "Could not open log file"; - return QString(); + return QByteArray(); } + // TODO: read the **last** 16kiB? + return pasteSourceFile.read( 16384 /* bytes */ ); +} - QByteArray pasteData; - while ( !pasteSourceFile.atEnd() ) - { - pasteData += pasteSourceFile.readLine(); - } +STATICTEST QString +ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent ) +{ QTcpSocket* socket = new QTcpSocket( parent ); - socket->connectToHost( ficheHost, fichePort ); + socket->connectToHost( serverUrl.host(), serverUrl.port() ); if ( !socket->waitForConnected() ) { @@ -61,7 +48,7 @@ ficheLogUpload( QObject* parent ) return QString(); } - cDebug() << "Connected to paste server"; + cDebug() << "Connected to paste server" << serverUrl.host(); socket->write( pasteData ); @@ -72,7 +59,7 @@ ficheLogUpload( QObject* parent ) return QString(); } - cDebug() << "Paste data written to paste server"; + cDebug() << Logger::SubEntry << "Paste data written to paste server"; if ( !socket->waitForReadyRead() ) { @@ -81,35 +68,52 @@ ficheLogUpload( QObject* parent ) return QString(); } - cDebug() << "Reading response from paste server"; - - char resp[ 1024 ]; - resp[ 0 ] = '\0'; - qint64 nBytesRead = socket->readLine( resp, 1024 ); + cDebug() << Logger::SubEntry << "Reading response from paste server"; + QByteArray responseText = socket->readLine( 1024 ); socket->close(); - QUrl pasteUrl = QUrl( QString( resp ).trimmed(), QUrl::StrictMode ); - QString pasteUrlStr = pasteUrl.toString(); - QRegularExpression pasteUrlRegex( "^http[s]?://" + ficheHost ); - QString pasteUrlMsg = QString( pasteUrlFmt ).arg( pasteUrlStr ); - - if ( nBytesRead >= 8 && pasteUrl.isValid() && pasteUrlRegex.match( pasteUrlStr ).hasMatch() ) + QUrl pasteUrl = QUrl( QString( responseText ).trimmed(), QUrl::StrictMode ); + if ( pasteUrl.isValid() && pasteUrl.host() == serverUrl.host() ) { - QClipboard* clipboard = QApplication::clipboard(); - clipboard->setText(pasteUrlStr, QClipboard::Clipboard); - - if (clipboard->supportsSelection()) - { - clipboard->setText(pasteUrlStr, QClipboard::Selection); - } + cDebug() << Logger::SubEntry << "Paste server results:" << pasteUrl; + return pasteUrl.toString(); } else { cError() << "No data from paste server"; return QString(); } +} - cDebug() << "Paste server results:" << pasteUrlMsg; - return pasteUrlMsg; +QString +CalamaresUtils::Paste::doLogUpload( QObject* parent ) +{ + auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer(); + if ( !serverUrl.isValid() ) + { + cWarning() << "Upload configure with invalid URL"; + return QString(); + } + if ( type == Calamares::Branding::UploadServerType::None ) + { + // Early return to avoid reading the log file + return QString(); + } + + QByteArray pasteData = logFileContents(); + if ( pasteData.isEmpty() ) + { + // An error has already been logged + return QString(); + } + + switch ( type ) + { + case Calamares::Branding::UploadServerType::None: + cWarning() << "No upload configured."; + return QString(); + case Calamares::Branding::UploadServerType::Fiche: + return ficheLogUpload( pasteData, serverUrl, parent ); + } + return QString(); } -} // namespace CalamaresUtils diff --git a/src/libcalamaresui/utils/Paste.h b/src/libcalamaresui/utils/Paste.h index 29e73fc1c..6258e57a0 100644 --- a/src/libcalamaresui/utils/Paste.h +++ b/src/libcalamaresui/utils/Paste.h @@ -10,21 +10,21 @@ #ifndef UTILS_PASTE_H #define UTILS_PASTE_H -#include +#include class QObject; -class QString; namespace CalamaresUtils { +namespace Paste +{ /** @brief Send the current log file to a pastebin * * Returns the (string) URL that the pastebin gives us. */ -QString ficheLogUpload( QObject* parent ); - -extern QStringList UploadServersList; +QString doLogUpload( QObject* parent ); +} // namespace Paste } // namespace CalamaresUtils diff --git a/src/libcalamaresui/utils/TestPaste.cpp b/src/libcalamaresui/utils/TestPaste.cpp new file mode 100644 index 000000000..ff75be02d --- /dev/null +++ b/src/libcalamaresui/utils/TestPaste.cpp @@ -0,0 +1,67 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * + * Calamares is Free Software: see the License-Identifier above. + * + * + */ + +#include "Paste.h" + +#include "utils/Logger.h" + +#include +#include + +extern QByteArray logFileContents(); +extern QString ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent ); + +class TestPaste : public QObject +{ + Q_OBJECT + +public: + TestPaste() {} + ~TestPaste() override {} + +private Q_SLOTS: + void testGetLogFile(); + void testFichePaste(); +}; + +void +TestPaste::testGetLogFile() +{ + // This test assumes nothing **else** has set up logging yet + QByteArray b = logFileContents(); + QVERIFY( b.isEmpty() ); + + Logger::setupLogLevel( Logger::LOGDEBUG ); + Logger::setupLogfile(); + + b = logFileContents(); + QVERIFY( !b.isEmpty() ); +} + +void +TestPaste::testFichePaste() +{ + QString blabla( "the quick brown fox tested Calamares and found it rubbery" ); + QDateTime now = QDateTime::currentDateTime(); + + QByteArray d = ( blabla + now.toString() ).toUtf8(); + QString s = ficheLogUpload( d, QUrl( "http://termbin.com:9999" ), nullptr ); + + cDebug() << "Paste data to" << s; + QVERIFY( !s.isEmpty() ); +} + + +QTEST_GUILESS_MAIN( TestPaste ) + +#include "utils/moc-warnings.h" + +#include "TestPaste.moc"