/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "../util.h" #include #include #include #include #include #include #include #include #include #include #include #include class tst_QWebEngineDownloadItem : public QObject { Q_OBJECT public: enum UserAction { SaveLink, ClickLink, }; enum FileAction { FileIsDownloaded, FileIsDisplayed, }; private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void cleanupTestCase(); void downloadLink_data(); void downloadLink(); void downloadTwoLinks_data(); void downloadTwoLinks(); void downloadPage_data(); void downloadPage(); void downloadViaSetUrl(); void downloadFileNot1(); void downloadFileNot2(); void downloadDeleted(); void downloadDeletedByProfile(); void downloadUniqueFilename_data(); void downloadUniqueFilename(); void downloadUniqueFilenameWithTimestamp(); void downloadToDefaultLocation(); void downloadToNonExistentDir(); void downloadToReadOnlyDir(); #if QT_DEPRECATED_SINCE(5, 14) void downloadPathValidation(); #endif void downloadToDirectoryWithFileName_data(); void downloadToDirectoryWithFileName(); private: void saveLink(QPoint linkPos); void clickLink(QPoint linkPos); void simulateUserAction(QPoint linkPos, UserAction action); QWebEngineDownloadItem::DownloadType expectedDownloadType( UserAction userAction, const QByteArray &contentDisposition = QByteArray()); HttpServer *m_server; QWebEngineProfile *m_profile; QWebEnginePage *m_page; QWebEngineView *m_view; QSet m_requestedDownloads; QSet m_finishedDownloads; }; Q_DECLARE_METATYPE(tst_QWebEngineDownloadItem::UserAction) Q_DECLARE_METATYPE(tst_QWebEngineDownloadItem::FileAction) void tst_QWebEngineDownloadItem::initTestCase() { m_server = new HttpServer(); m_profile = new QWebEngineProfile; m_profile->setHttpCacheType(QWebEngineProfile::NoCache); m_profile->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); connect(m_profile, &QWebEngineProfile::downloadRequested, [this](QWebEngineDownloadItem *item) { m_requestedDownloads.insert(item); connect(item, &QWebEngineDownloadItem::destroyed, [this, item](){ m_requestedDownloads.remove(item); m_finishedDownloads.remove(item); }); connect(item, &QWebEngineDownloadItem::finished, [this, item](){ m_finishedDownloads.insert(item); }); }); m_page = new QWebEnginePage(m_profile); m_view = new QWebEngineView; m_view->setPage(m_page); m_view->resize(640, 480); m_view->show(); } void tst_QWebEngineDownloadItem::init() { QVERIFY(m_server->start()); } void tst_QWebEngineDownloadItem::cleanup() { for (QWebEngineDownloadItem *item : m_finishedDownloads) { item->deleteLater(); } QTRY_COMPARE(m_requestedDownloads.count(), 0); QCOMPARE(m_finishedDownloads.count(), 0); QVERIFY(m_server->stop()); // Set download path to default. m_profile->setDownloadPath(""); } void tst_QWebEngineDownloadItem::cleanupTestCase() { delete m_view; delete m_page; delete m_profile; delete m_server; } void tst_QWebEngineDownloadItem::saveLink(QPoint linkPos) { // Simulate right-clicking on link and choosing "save link as" from menu. QSignalSpy menuSpy(m_view, &QWebEngineView::customContextMenuRequested); m_view->setContextMenuPolicy(Qt::CustomContextMenu); auto event1 = new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos); auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, Qt::RightButton, {}, {}); auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, Qt::RightButton, {}, {}); QTRY_VERIFY(m_view->focusWidget()); QWidget *renderWidget = m_view->focusWidget(); QCoreApplication::postEvent(renderWidget, event1); QCoreApplication::postEvent(renderWidget, event2); QCoreApplication::postEvent(renderWidget, event3); QTRY_COMPARE(menuSpy.count(), 1); m_page->triggerAction(QWebEnginePage::DownloadLinkToDisk); } void tst_QWebEngineDownloadItem::clickLink(QPoint linkPos) { // Simulate left-clicking on link. QTRY_VERIFY(m_view->focusWidget()); QWidget *renderWidget = m_view->focusWidget(); QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos); } void tst_QWebEngineDownloadItem::simulateUserAction(QPoint linkPos, UserAction action) { switch (action) { case SaveLink: return saveLink(linkPos); case ClickLink: return clickLink(linkPos); } } QWebEngineDownloadItem::DownloadType tst_QWebEngineDownloadItem::expectedDownloadType( UserAction userAction, const QByteArray &contentDisposition) { if (userAction == SaveLink) return QWebEngineDownloadItem::UserRequested; if (contentDisposition == QByteArrayLiteral("attachment")) return QWebEngineDownloadItem::Attachment; return QWebEngineDownloadItem::DownloadAttribute; } void tst_QWebEngineDownloadItem::downloadLink_data() { QTest::addColumn("userAction"); QTest::addColumn("anchorHasDownloadAttribute"); QTest::addColumn("fileName"); QTest::addColumn("fileContents"); QTest::addColumn("fileMimeTypeDeclared"); QTest::addColumn("fileMimeTypeDetected"); QTest::addColumn("fileDisposition"); QTest::addColumn("fileHasReferer"); QTest::addColumn("fileAction"); QTest::addColumn("downloadType"); // SaveLink should always trigger a download, even for empty files. QTest::newRow("save link to empty file") /* userAction */ << SaveLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("") /* fileMimeTypeDeclared */ << QByteArrayLiteral("") /* fileMimeTypeDetected */ << QByteArrayLiteral("") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // SaveLink should always trigger a download, also for text files. QTest::newRow("save link to text file") /* userAction */ << SaveLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... adding the "download" attribute should have no effect. QTest::newRow("save link to text file (attribute)") /* userAction */ << SaveLink /* anchorHasDownloadAttribute */ << true /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... adding the "attachment" content disposition should also have no effect. QTest::newRow("save link to text file (attachment)") /* userAction */ << SaveLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("attachment") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... even adding both should have no effect. QTest::newRow("save link to text file (attribute+attachment)") /* userAction */ << SaveLink /* anchorHasDownloadAttribute */ << true /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("attachment") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // Navigating to an empty file should show an empty page. QTest::newRow("navigate to empty file") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("") /* fileMimeTypeDeclared */ << QByteArrayLiteral("") /* fileMimeTypeDetected */ << QByteArrayLiteral("") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDisplayed; // Navigating to a text file should show the text file. QTest::newRow("navigate to text file") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDisplayed; // ... unless the link has the "download" attribute: then the file should be downloaded. QTest::newRow("navigate to text file (attribute)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << true /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << false // crbug.com/455987 /* fileAction */ << FileIsDownloaded; // ... same with the content disposition header save for the download type. QTest::newRow("navigate to text file (attachment)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("attachment") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... and both. QTest::newRow("navigate to text file (attribute+attachment)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << true /* fileName */ << QByteArrayLiteral("foo.txt") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("text/plain") /* fileMimeTypeDetected */ << QByteArrayLiteral("text/plain") /* fileDisposition */ << QByteArrayLiteral("attachment") /* fileHasReferer */ << false // crbug.com/455987 /* fileAction */ << FileIsDownloaded; // The file's extension has no effect. QTest::newRow("navigate to supposed zip file") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.zip") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("") /* fileMimeTypeDetected */ << QByteArrayLiteral("") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDisplayed; // ... the file's mime type however does. QTest::newRow("navigate to supposed zip file (application/zip)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.zip") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("application/zip") /* fileMimeTypeDetected */ << QByteArrayLiteral("application/zip") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // ... but we're not very picky about the exact type. QTest::newRow("navigate to supposed zip file (application/octet-stream)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.zip") /* fileContents */ << QByteArrayLiteral("bar") /* fileMimeTypeDeclared */ << QByteArrayLiteral("application/octet-stream") /* fileMimeTypeDetected */ << QByteArrayLiteral("application/octet-stream") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // empty zip file (consisting only of "end of central directory record") QByteArray zipFile = QByteArrayLiteral("PK\x05\x06") + QByteArray(18, 0); // The mime type is guessed automatically if not provided by the server. QTest::newRow("navigate to actual zip file") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.zip") /* fileContents */ << zipFile /* fileMimeTypeDeclared */ << QByteArrayLiteral("") /* fileMimeTypeDetected */ << QByteArrayLiteral("application/octet-stream") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; // The mime type is not guessed automatically if provided by the server. QTest::newRow("navigate to actual zip file (application/zip)") /* userAction */ << ClickLink /* anchorHasDownloadAttribute */ << false /* fileName */ << QByteArrayLiteral("foo.zip") /* fileContents */ << zipFile /* fileMimeTypeDeclared */ << QByteArrayLiteral("application/zip") /* fileMimeTypeDetected */ << QByteArrayLiteral("application/zip") /* fileDisposition */ << QByteArrayLiteral("") /* fileHasReferer */ << true /* fileAction */ << FileIsDownloaded; } void tst_QWebEngineDownloadItem::downloadLink() { QFETCH(UserAction, userAction); QFETCH(bool, anchorHasDownloadAttribute); QFETCH(QByteArray, fileName); QFETCH(QByteArray, fileContents); QFETCH(QByteArray, fileMimeTypeDeclared); QFETCH(QByteArray, fileMimeTypeDetected); QFETCH(QByteArray, fileDisposition); QFETCH(bool, fileHasReferer); QFETCH(FileAction, fileAction); // Set up HTTP server int indexRequestCount = 0; int fileRequestCount = 0; QByteArray fileRequestReferer; ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == "/") { indexRequestCount++; rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); QByteArray html; html += QByteArrayLiteral("Link"); rr->setResponseBody(html); rr->sendResponse(); } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/" + fileName) { fileRequestCount++; fileRequestReferer = rr->requestHeader(QByteArrayLiteral("referer")); if (!fileDisposition.isEmpty()) rr->setResponseHeader(QByteArrayLiteral("content-disposition"), fileDisposition); if (!fileMimeTypeDeclared.isEmpty()) rr->setResponseHeader(QByteArrayLiteral("content-type"), fileMimeTypeDeclared); rr->setResponseBody(fileContents); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); QByteArray slashFileName = QByteArrayLiteral("/") + fileName; QString suggestedPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + slashFileName; QString downloadDirectory = tmpDir.path(); QString downloadFileName = fileName; QString downloadPath = tmpDir.path() + slashFileName; QUrl downloadUrl = m_server->url(slashFileName); int acceptedCount = 0; int finishedCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); QCOMPARE(item->isFinished(), false); QCOMPARE(item->totalBytes(), -1); QCOMPARE(item->receivedBytes(), 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), suggestedPath); QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), fileContents.size()); QCOMPARE(item->receivedBytes(), fileContents.size()); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition)); QCOMPARE(item->isSavePageDownload(), false); QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected)); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); finishedCount++; }); item->setDownloadDirectory(downloadDirectory); item->setDownloadFileName(downloadFileName); item->accept(); acceptedCount++; }); // Load an HTML page with a link // // The only variation being whether the element has a "download" // attribute or not. QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_view->load(m_server->url()); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(indexRequestCount, 1); simulateUserAction(QPoint(10, 10), userAction); // If file is expected to be displayed and not downloaded then end test if (fileAction == FileIsDisplayed) { QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(acceptedCount, 0); return; } // Otherwise file is downloaded QTRY_COMPARE(acceptedCount, 1); QTRY_COMPARE(finishedCount, 1); QCOMPARE(fileRequestCount, 1); if (fileHasReferer) QCOMPARE(fileRequestReferer, m_server->url().toEncoded()); else QCOMPARE(fileRequestReferer, QByteArray()); QFile file(downloadPath); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(file.readAll(), fileContents); } void tst_QWebEngineDownloadItem::downloadTwoLinks_data() { QTest::addColumn("action1"); QTest::addColumn("action2"); QTest::newRow("Save+Save") << SaveLink << SaveLink; QTest::newRow("Save+Click") << SaveLink << ClickLink; QTest::newRow("Click+Save") << ClickLink << SaveLink; QTest::newRow("Click+Click") << ClickLink << ClickLink; } void tst_QWebEngineDownloadItem::downloadTwoLinks() { QFETCH(UserAction, action1); QFETCH(UserAction, action2); // Set up HTTP server int file1RequestCount = 0; int file2RequestCount = 0; ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == "/") { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); rr->setResponseBody(QByteArrayLiteral("Link1
Link2")); rr->sendResponse(); } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file1") { file1RequestCount++; rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain")); rr->setResponseBody(QByteArrayLiteral("file1")); rr->sendResponse(); } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file2") { file2RequestCount++; rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("file2")); rr->sendResponse(); } }); // Set up profile and download handler QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); QString standardDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); int acceptedCount = 0; int finishedCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); QCOMPARE(item->isFinished(), false); QCOMPARE(item->totalBytes(), -1); QCOMPARE(item->receivedBytes(), 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat); QCOMPARE(item->mimeType(), QStringLiteral("text/plain")); QString filePart = QChar('/') + item->url().fileName(); QString fileName = item->url().fileName(); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), standardDir + filePart); // type() is broken due to race condition in DownloadManagerDelegateQt if (action1 == ClickLink && action2 == ClickLink) { if (filePart == QStringLiteral("/file1")) QCOMPARE(item->type(), expectedDownloadType(action1)); else if (filePart == QStringLiteral("/file2")) QCOMPARE(item->type(), expectedDownloadType(action2, QByteArrayLiteral("attachment"))); else QFAIL(qPrintable("Unexpected file name: " + filePart)); } connect(item, &QWebEngineDownloadItem::finished, [&]() { finishedCount++; }); item->setDownloadDirectory(tmpDir.path()); item->setDownloadFileName(fileName); item->accept(); acceptedCount++; }); QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_view->load(m_server->url()); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); // Trigger downloads simulateUserAction(QPoint(10, 10), action1); simulateUserAction(QPoint(10, 30), action2); // Wait for downloads QTRY_COMPARE(file1RequestCount, 1); QTRY_COMPARE(file2RequestCount, 1); QTRY_COMPARE(acceptedCount, 2); QTRY_COMPARE(finishedCount, 2); } void tst_QWebEngineDownloadItem::downloadPage_data() { QTest::addColumn("saveWithPageAction"); QTest::addColumn("savePageFormat"); QTest::newRow("SingleHtmlSaveFormat") << false << QWebEngineDownloadItem::SingleHtmlSaveFormat; QTest::newRow("CompleteHtmlSaveFormat") << false << QWebEngineDownloadItem::CompleteHtmlSaveFormat; QTest::newRow("MimeHtmlSaveFormat") << false << QWebEngineDownloadItem::MimeHtmlSaveFormat; QTest::newRow("SavePageAction") << true << QWebEngineDownloadItem::MimeHtmlSaveFormat; } void tst_QWebEngineDownloadItem::downloadPage() { QFETCH(bool, saveWithPageAction); QFETCH(QWebEngineDownloadItem::SavePageFormat, savePageFormat); // Set up HTTP server int indexRequestCount = 0; ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == "/") { indexRequestCount++; rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); rr->setResponseBody(QByteArrayLiteral("Hello")); rr->sendResponse(); } }); // Set up profile and download handler QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); QString downloadFileName("test.html"), downloadPath = tmpDir.filePath(downloadFileName); QUrl downloadUrl = m_server->url("/"); int acceptedCount = 0; int finishedCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QCOMPARE(item->state(), saveWithPageAction ? QWebEngineDownloadItem::DownloadRequested : QWebEngineDownloadItem::DownloadInProgress); QCOMPARE(item->isFinished(), false); QCOMPARE(item->totalBytes(), -1); QCOMPARE(item->receivedBytes(), 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage); QCOMPARE(item->isSavePageDownload(), true); // FIXME(juvaldma): why is mimeType always the same? QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); QCOMPARE(item->savePageFormat(), savePageFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); if (saveWithPageAction) { QVERIFY(!item->downloadDirectory().isEmpty()); QVERIFY(!item->downloadFileName().isEmpty()); item->setDownloadDirectory(tmpDir.path()); item->setDownloadFileName(downloadFileName); item->accept(); } // save with explicit path accepts download automatically QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage); QCOMPARE(item->isSavePageDownload(), true); QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive")); QCOMPARE(QDir(item->downloadDirectory()).filePath(item->downloadFileName()), downloadPath); QCOMPARE(item->savePageFormat(), savePageFormat); QCOMPARE(item->url(), downloadUrl); QCOMPARE(item->page(), m_page); finishedCount++; }); acceptedCount++; }); // Load some HTML QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); m_page->load(m_server->url()); QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(indexRequestCount, 1); // Save some HTML if (saveWithPageAction) m_page->triggerAction(QWebEnginePage::SavePage); else m_page->save(downloadPath, savePageFormat); QTRY_COMPARE(acceptedCount, 1); QTRY_COMPARE(finishedCount, 1); QFile file(downloadPath); QVERIFY(file.exists()); } void tst_QWebEngineDownloadItem::downloadViaSetUrl() { // Reproduce the scenario described in QTBUG-63388 by triggering downloads // of the same file multiple times via QWebEnginePage::setUrl // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == "/") { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html")); rr->setResponseBody(QByteArrayLiteral("Hello")); rr->sendResponse(); } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file") { rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("redacted")); rr->sendResponse(); } }); // Set up profile and download handler QVector downloadUrls; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { downloadUrls.append(item->url()); }); // Set up the test scenario by trying to load some unrelated HTML. QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished); QSignalSpy urlSpy(m_page, &QWebEnginePage::urlChanged); const QUrl indexUrl = m_server->url(); m_page->setUrl(indexUrl); QTRY_COMPARE(loadSpy.count(), 1); QTRY_COMPARE(urlSpy.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); // Download files via setUrl. With QTBUG-63388 after the first iteration the // downloads would be triggered for indexUrl and not fileUrl. const QUrl fileUrl = m_server->url(QByteArrayLiteral("/file")); for (int i = 0; i != 3; ++i) { m_page->setUrl(fileUrl); QCOMPARE(m_page->url(), fileUrl); QTRY_COMPARE(loadSpy.count(), 1); QTRY_COMPARE(urlSpy.count(), 2); QTRY_COMPARE(downloadUrls.count(), 1); QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), fileUrl); QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl); QCOMPARE(downloadUrls.takeFirst(), fileUrl); QCOMPARE(m_page->url(), indexUrl); } } void tst_QWebEngineDownloadItem::downloadFileNot1() { // Trigger file download via download() but don't accept(). ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { rr->sendResponse(404); }); QPointer downloadItem; int downloadCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QVERIFY(item); QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); downloadItem = item; downloadCount++; }); m_page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadCount, 1); QVERIFY(!downloadItem); } void tst_QWebEngineDownloadItem::downloadFileNot2() { // Trigger file download via download() but call cancel() instead of accept(). ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { rr->sendResponse(404); }); QPointer downloadItem; int downloadCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QVERIFY(item); QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); item->cancel(); downloadItem = item; downloadCount++; }); m_page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadCount, 1); QVERIFY(downloadItem); QCOMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCancelled); } void tst_QWebEngineDownloadItem::downloadDeleted() { QPointer downloadItem; int downloadCount = 0, finishedCount = 0; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { QVERIFY(item); QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested); downloadItem = item; connect(downloadItem, &QWebEngineDownloadItem::finished, [&]() { ++finishedCount; }); ++downloadCount; // accept and schedule deletion, and check if it still finishes item->accept(); item->deleteLater(); QVERIFY(downloadItem); }); m_page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadCount, 1); QTRY_COMPARE(finishedCount, 1); QTRY_VERIFY(!downloadItem); QCOMPARE(downloadCount, 1); QCOMPARE(finishedCount, 1); } void tst_QWebEngineDownloadItem::downloadDeletedByProfile() { QPointer profile(new QWebEngineProfile); profile->setHttpCacheType(QWebEngineProfile::NoCache); profile->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false); bool downloadFinished = false; QPointer downloadItem; connect(profile, &QWebEngineProfile::downloadRequested, [&] (QWebEngineDownloadItem *item) { connect(item, &QWebEngineDownloadItem::finished, [&] () { downloadFinished = true; }); downloadItem = item; item->accept(); }); QPointer page(new QWebEnginePage(profile)); page->download(m_server->url(QByteArrayLiteral("/file"))); QTRY_COMPARE(downloadItem.isNull(), false); QVERIFY(downloadItem); page->deleteLater(); profile->deleteLater(); QTRY_COMPARE(downloadFinished, true); QTRY_COMPARE(downloadItem.isNull(), true); } void tst_QWebEngineDownloadItem::downloadUniqueFilename_data() { QTest::addColumn("baseName"); QTest::addColumn("extension"); QTest::newRow("txt") << QString("test(1.test)") << QString("txt"); QTest::newRow("tar.gz") << QString("test(1.test)") << QString("tar.gz"); } void tst_QWebEngineDownloadItem::downloadUniqueFilename() { QFETCH(QString, baseName); QFETCH(QString, extension); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->type(), QWebEngineDownloadItem::Attachment); QCOMPARE(item->isSavePageDownload(), false); downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); downloadFinished = true; }); }); m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, m_profile->downloadPath() + "/" + baseName + "." + extension); for (int i = 1; i <= 2; ++i) { downloadFinished = false; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, m_profile->downloadPath() + "/" + baseName + " (" + QString::number(i) + ")." + extension); QCOMPARE(suggestedFileName, fileName); } } void tst_QWebEngineDownloadItem::downloadUniqueFilenameWithTimestamp() { // Set up HTTP server QString baseName("test(1.test)"); QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); }); }); // Create the first empty file without uniquifier. { QFile file(m_profile->downloadPath() + "/" + fileName); file.open(QIODevice::ReadWrite); } // Create 99 empty files with uniquifier. for (int i = 1; i < 100; i++) { QFile file(m_profile->downloadPath() + "/" + baseName + " (" + QString::number(i) + ")." + extension); file.open(QIODevice::ReadWrite); } // Create 100th (kMaxUniqueFiles) empty file with uniquifier. m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, m_profile->downloadPath() + "/" + baseName + " (100)." + extension); QCOMPARE(suggestedFileName, fileName); // Check if the downloaded files are suffixed with timestamp after the 100th download. for (int i = 101; i < 103; i++) { downloadFinished = false; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QRegularExpression fileNameCheck("^.*" + QRegularExpression::escape(baseName) + " - (.*)[.]" + QRegularExpression::escape(extension) + "$"); QRegularExpressionMatch match = fileNameCheck.match(downloadedFilePath); QVERIFY(match.hasMatch()); // ISO 8601 Date and time in UTC QRegularExpression timestamp("^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[" "12][0-9])T(2[0-3]|[01][0-9])([0-5][0-9])([0-5][0-9])([.][0-9]" "+)?(Z|[+-](?:2[0-3]|[01][0-9])[0-5][0-9])?$"); QVERIFY(timestamp.match(match.captured(1)).hasMatch()); QCOMPARE(suggestedFileName, fileName); } } void tst_QWebEngineDownloadItem::downloadToDefaultLocation() { QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); QCOMPARE(m_profile->downloadPath(), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_profile->setDownloadPath(""); QCOMPARE(m_profile->downloadPath(), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_profile->setDownloadPath(tmpDir.path()); QCOMPARE(m_profile->downloadPath(), tmpDir.path()); m_profile->setDownloadPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); QCOMPARE(m_profile->downloadPath(), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); } void tst_QWebEngineDownloadItem::downloadToNonExistentDir() { QString baseName("test(1.test)"); QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; QString suggestedFileName; bool downloadFinished = false; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { suggestedFileName = item->suggestedFileName(); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); }); }); // Set a non-existent directory for the default download location. QString nonExistentDownloadPath(m_profile->downloadPath() + "/non_existent_dir"); m_profile->setDownloadPath(nonExistentDownloadPath); QCOMPARE(m_profile->downloadPath(), nonExistentDownloadPath); m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, nonExistentDownloadPath + "/" + fileName); QCOMPARE(suggestedFileName, fileName); } void tst_QWebEngineDownloadItem::downloadToReadOnlyDir() { #ifdef Q_OS_WIN QSKIP("Cannot change file permissions on Windows."); #endif QString baseName("test(1.test)"); QString extension("txt"); QString fileName = QString("%1.%2").arg(baseName).arg(extension); QString downloadedFilePath; QString suggestedFileName; bool downloadAccepted = false; bool downloadFinished = false; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); QPointer downloadItem; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { suggestedFileName = item->suggestedFileName(); downloadItem = item; item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&]() { downloadFinished = true; }); downloadAccepted = true; }); // Change permission for directory. QFile(m_profile->downloadPath()).setPermissions(QFileDevice::ReadOwner); QVERIFY(QFile(m_profile->downloadPath()).exists()); m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadAccepted); QVERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadInterrupted); QCOMPARE(downloadItem->isFinished(), false); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::FileAccessDenied); QVERIFY(!QFile(downloadedFilePath).exists()); QCOMPARE(suggestedFileName, fileName); // Clear m_requestedDownloads explicitly because download is accepted but never finished. m_requestedDownloads.clear(); QVERIFY(!downloadFinished); QFile(m_profile->downloadPath()).setPermissions(QFileDevice::WriteOwner); } #if QT_DEPRECATED_SINCE(5, 14) void tst_QWebEngineDownloadItem::downloadPathValidation() { const QString fileName = "test.txt"; QString downloadPath; QString originalDownloadPath; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler QPointer downloadItem; ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { downloadItem = item; originalDownloadPath = item->path(); item->setPath(downloadPath); item->accept(); connect(item, &QWebEngineDownloadItem::stateChanged, [&, item](QWebEngineDownloadItem::DownloadState downloadState) { if (downloadState == QWebEngineDownloadItem::DownloadInterrupted) { item->cancel(); } }); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->page(), m_page); }); }); QString oldPath = QDir::currentPath(); QDir::setCurrent(tmpDir.path()); // Set only the file name. downloadItem.clear(); originalDownloadPath = ""; downloadPath = fileName; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), fileName); // Set only the directory path. downloadItem.clear(); originalDownloadPath = ""; downloadPath = tmpDir.path(); m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), originalDownloadPath); // Set only the directory path with separator. downloadItem.clear(); originalDownloadPath = ""; downloadPath = tmpDir.path() + QDir::separator(); m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), originalDownloadPath); // Set only the directory with the current directory path without ending separator. downloadItem.clear(); originalDownloadPath = ""; downloadPath = "."; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), originalDownloadPath); // Set only the directory with the current directory path with ending separator. downloadItem.clear(); originalDownloadPath = ""; downloadPath = "./"; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), originalDownloadPath); downloadItem.clear(); originalDownloadPath = ""; downloadPath = "..."; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadItem); #if !defined(Q_OS_WIN) QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCancelled); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::FileFailed); QCOMPARE(downloadItem->path(), downloadPath); #else // Windows interprets the "..." path as a valid path. It will be the current path. QTRY_COMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(downloadItem->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(downloadItem->path(), originalDownloadPath); #endif // !defined(Q_OS_WIN) QDir::setCurrent(oldPath); } #endif void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName_data() { QTest::addColumn("setDirectoryFirst"); QTest::newRow("setDirectoryFirst") << true; QTest::newRow("setFileNameFirst") << false; } void tst_QWebEngineDownloadItem::downloadToDirectoryWithFileName() { QFETCH(bool, setDirectoryFirst); QString downloadDirectory; QString downloadFileName; QString downloadedFilePath; QString downloadedSuggestedFileName; QString fileName = "test.txt"; QString uniqueFileName = "test (1).txt"; bool downloadFinished = false; QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); m_profile->setDownloadPath(tmpDir.path()); // Set up HTTP server ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) { if (rr->requestMethod() == "GET" && rr->requestPath() == ("/" + fileName)) { rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("application/octet-stream")); rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment")); rr->setResponseBody(QByteArrayLiteral("a")); rr->sendResponse(); } else { rr->sendResponse(404); } }); // Set up profile and download handler ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) { if (!downloadDirectory.isEmpty() && setDirectoryFirst) { item->setDownloadDirectory(downloadDirectory); QCOMPARE(item->downloadDirectory(), downloadDirectory); } if (!downloadFileName.isEmpty()) { item->setDownloadFileName(downloadFileName); QCOMPARE(item->downloadFileName(), downloadFileName); } if (!downloadDirectory.isEmpty() && !setDirectoryFirst) { item->setDownloadDirectory(downloadDirectory); QCOMPARE(item->downloadDirectory(), downloadDirectory); } QCOMPARE(item->path(), QDir(item->downloadDirectory()).filePath(item->downloadFileName())); item->accept(); connect(item, &QWebEngineDownloadItem::finished, [&, item]() { QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted); QCOMPARE(item->isFinished(), true); QCOMPARE(item->totalBytes(), item->receivedBytes()); QVERIFY(item->receivedBytes() > 0); QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason); QCOMPARE(item->page(), m_page); downloadFinished = true; downloadedFilePath = QDir(item->downloadDirectory()).filePath(item->downloadFileName()); downloadedSuggestedFileName = item->suggestedFileName(); }); }); // Download file to the default download directory. downloadDirectory = ""; downloadFileName = ""; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(m_profile->downloadPath()).filePath(fileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file to another directory downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); downloadFileName = ""; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(fileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file to the same directory and the file name must be unique. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); downloadFileName = ""; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(uniqueFileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download another file to the same directory and set file name by // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test1" + QDir::separator(); downloadFileName = "test1.txt"; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file to another directory without uniquifying the file name downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); downloadFileName = "test1.txt"; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file to same directory and set file name by // QWebEngineDownloadItem::setDownloadDirectory() and setDownloadFileName() to avoid uniquification. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); downloadFileName = "test1.txt"; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(downloadFileName)); QCOMPARE(downloadedSuggestedFileName, fileName); // Download the same file in the same directory. // Use the suggested file name (test.txt) and the file name will not be unique because this file name don't yet exists. downloadFinished = false; downloadDirectory = m_profile->downloadPath() + QDir::separator() + "test2" + QDir::separator(); downloadFileName = ""; m_page->setUrl(m_server->url("/" + fileName)); QTRY_VERIFY(downloadFinished); QVERIFY(QFile(downloadedFilePath).exists()); QCOMPARE(downloadedFilePath, QDir(downloadDirectory).filePath(fileName)); QCOMPARE(downloadedSuggestedFileName, fileName); } QTEST_MAIN(tst_QWebEngineDownloadItem) #include "tst_qwebenginedownloaditem.moc"