blob: 55d8ac6e811038650c17926f1f3b1bbcd4a2dfb0 [file] [log] [blame]
/****************************************************************************
**
** 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 <QCoreApplication>
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTest>
#include <QRegularExpression>
#include <QWebEngineDownloadItem>
#include <QWebEnginePage>
#include <QWebEngineProfile>
#include <QWebEngineSettings>
#include <QWebEngineView>
#include <httpserver.h>
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<QWebEngineDownloadItem *> m_requestedDownloads;
QSet<QWebEngineDownloadItem *> m_finishedDownloads;
};
class ScopedConnection {
public:
ScopedConnection(QMetaObject::Connection connection) : m_connection(std::move(connection)) {}
~ScopedConnection() { QObject::disconnect(m_connection); }
private:
QMetaObject::Connection m_connection;
};
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>("userAction");
QTest::addColumn<bool>("anchorHasDownloadAttribute");
QTest::addColumn<QByteArray>("fileName");
QTest::addColumn<QByteArray>("fileContents");
QTest::addColumn<QByteArray>("fileMimeTypeDeclared");
QTest::addColumn<QByteArray>("fileMimeTypeDetected");
QTest::addColumn<QByteArray>("fileDisposition");
QTest::addColumn<bool>("fileHasReferer");
QTest::addColumn<FileAction>("fileAction");
QTest::addColumn<QWebEngineDownloadItem::DownloadType>("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("<html><body><a href=\"");
html += fileName;
html += QByteArrayLiteral("\" ");
if (anchorHasDownloadAttribute)
html += QByteArrayLiteral("download");
html += QByteArrayLiteral(">Link</a></body></html>");
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->setResponseStatus(404);
rr->sendResponse();
}
});
// 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 <a> 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<UserAction>("action1");
QTest::addColumn<UserAction>("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("<html><body><a href=\"file1\" download>Link1</a><br/><a href=\"file2\">Link2</a></body></html>"));
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();
} else {
rr->setResponseStatus(404);
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<bool>("saveWithPageAction");
QTest::addColumn<QWebEngineDownloadItem::SavePageFormat>("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("<html><body>Hello</body></html>"));
rr->sendResponse();
} else {
rr->setResponseStatus(404);
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("<html><body>Hello</body></html>"));
rr->sendResponse();
} else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file") {
rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
rr->setResponseBody(QByteArrayLiteral("redacted"));
rr->sendResponse();
} else {
rr->setResponseStatus(404);
rr->sendResponse();
}
});
// Set up profile and download handler
QVector<QUrl> 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->setResponseStatus(404);
rr->sendResponse();
});
QPointer<QWebEngineDownloadItem> 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->setResponseStatus(404);
rr->sendResponse();
});
QPointer<QWebEngineDownloadItem> 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<QWebEngineDownloadItem> downloadItem;
m_server->setExpectError(true);
int downloadCount = 0;
int 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++;
});
item->accept();
downloadCount++;
});
m_page->download(m_server->url(QByteArrayLiteral("/file")));
QTRY_COMPARE(downloadCount, 1);
QVERIFY(downloadItem);
QCOMPARE(finishedCount, 0);
downloadItem->deleteLater();
QTRY_COMPARE(finishedCount, 1);
}
void tst_QWebEngineDownloadItem::downloadDeletedByProfile()
{
m_server->setExpectError(true);
QPointer<QWebEngineProfile> profile(new QWebEngineProfile);
profile->setHttpCacheType(QWebEngineProfile::NoCache);
profile->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false);
bool downloadFinished = false;
QPointer<QWebEngineDownloadItem> downloadItem;
connect(profile, &QWebEngineProfile::downloadRequested, [&] (QWebEngineDownloadItem *item) {
connect(item, &QWebEngineDownloadItem::finished, [&] () {
downloadFinished = true;
});
downloadItem = item;
item->accept();
});
QPointer<QWebEnginePage> 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<QString>("baseName");
QTest::addColumn<QString>("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->setResponseStatus(404);
rr->sendResponse();
}
});
// 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->setResponseStatus(404);
rr->sendResponse();
}
});
// 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
QRegExp 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.exactMatch(match.captured(1)));
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->setResponseStatus(404);
rr->sendResponse();
}
});
// 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->setResponseStatus(404);
rr->sendResponse();
}
});
QPointer<QWebEngineDownloadItem> 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->setResponseStatus(404);
rr->sendResponse();
}
});
// Set up profile and download handler
QPointer<QWebEngineDownloadItem> 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<bool>("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->setResponseStatus(404);
rr->sendResponse();
}
});
// 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"