From b8f6e4ce6462f074c34a8b7a286cbabe0e2897aa Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 3 Dec 2019 02:26:41 +0100 Subject: Add encrypted file download --- src/timeline/TimelineModel.cpp | 184 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) (limited to 'src/timeline/TimelineModel.cpp') diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index b904dfd7..f606b603 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -3,11 +3,15 @@ #include #include +#include +#include #include +#include #include "ChatPage.h" #include "Logging.h" #include "MainWindow.h" +#include "MxcImageProvider.h" #include "Olm.h" #include "TimelineViewManager.h" #include "Utils.h" @@ -88,17 +92,42 @@ eventFormattedBody(const mtx::events::RoomEvent &e) } } +template +boost::optional +eventEncryptionInfo(const mtx::events::Event &) +{ + return boost::none; +} + +template +auto +eventEncryptionInfo(const mtx::events::RoomEvent &e) -> std::enable_if_t< + std::is_same>::value, + boost::optional> +{ + return e.content.file; +} + template QString eventUrl(const mtx::events::Event &) { return ""; } + +QString +eventUrl(const mtx::events::StateEvent &e) +{ + return QString::fromStdString(e.content.url); +} + template auto eventUrl(const mtx::events::RoomEvent &e) -> std::enable_if_t::value, QString> { + if (e.content.file) + return QString::fromStdString(e.content.file->url); return QString::fromStdString(e.content.url); } @@ -1342,3 +1371,158 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event) if (!isProcessingPending) emit nextPendingMessage(); } + +void +TimelineModel::saveMedia(QString eventId) const +{ + mtx::events::collections::TimelineEvents event = events.value(eventId); + + if (auto e = boost::get>(&event)) { + event = decryptEvent(*e).event; + } + + QString mxcUrl = + boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event); + QString originalFilename = + boost::apply_visitor([](const auto &e) -> QString { return eventFilename(e); }, event); + QString mimeType = + boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event); + + using EncF = boost::optional; + EncF encryptionInfo = + boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event); + + qml_mtx_events::EventType eventType = boost::apply_visitor( + [](const auto &e) -> qml_mtx_events::EventType { return toRoomEventType(e); }, event); + + QString dialogTitle; + if (eventType == qml_mtx_events::EventType::ImageMessage) { + dialogTitle = tr("Save image"); + } else if (eventType == qml_mtx_events::EventType::VideoMessage) { + dialogTitle = tr("Save video"); + } else if (eventType == qml_mtx_events::EventType::AudioMessage) { + dialogTitle = tr("Save audio"); + } else { + dialogTitle = tr("Save file"); + } + + QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString(); + + auto filename = QFileDialog::getSaveFileName( + manager_->getWidget(), dialogTitle, originalFilename, filterString); + + if (filename.isEmpty()) + return; + + const auto url = mxcUrl.toStdString(); + + http::client()->download( + url, + [filename, url, encryptionInfo](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + try { + auto temp = data; + if (encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly)) + return; + + file.write(QByteArray(temp.data(), (int)temp.size())); + file.close(); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); +} + +void +TimelineModel::cacheMedia(QString eventId) +{ + mtx::events::collections::TimelineEvents event = events.value(eventId); + + if (auto e = boost::get>(&event)) { + event = decryptEvent(*e).event; + } + + QString mxcUrl = + boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event); + QString mimeType = + boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event); + + using EncF = boost::optional; + EncF encryptionInfo = + boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event); + + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + emit mediaCached(mxcUrl, mxcUrl); + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + QFileInfo filename(QString("%1/media_cache/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(QString(mxcUrl).remove("mxc://")) + .arg(suffix)); + if (QDir::cleanPath(filename.path()) != filename.path()) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + if (filename.isReadable()) { + emit mediaCached(mxcUrl, filename.filePath()); + return; + } + + http::client()->download( + url, + [this, mxcUrl, filename, url, encryptionInfo](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + try { + auto temp = data; + if (encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + file.write(QByteArray(temp.data(), temp.size())); + file.close(); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + + emit mediaCached(mxcUrl, filename.filePath()); + }); +} -- cgit 1.5.1