2015-09-08 17:05:59 +02:00
|
|
|
#include "./httpdownload.h"
|
|
|
|
|
|
|
|
#include "./misc/contentdispositionparser.h"
|
2015-04-22 19:32:04 +02:00
|
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
namespace Network {
|
|
|
|
|
|
|
|
QNetworkAccessManager *HttpDownload::m_mgr = nullptr;
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class HttpDownloadInfo
|
|
|
|
* \brief The HttpDownloadInfo class wraps a QNetworkReply, the corresponding option index and additional meta information.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class HttpDownload
|
|
|
|
* \brief The HttpDownload class is an implementation of Download for HTTP and if OpenSSL is available
|
|
|
|
* HTTPS downloads. It is used as base class for more advanced HTTP downloads such as YouTube downloads.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new HttpDownload with the specified \a url.
|
|
|
|
*/
|
|
|
|
HttpDownload::HttpDownload(const QUrl &url, QObject *parent)
|
|
|
|
: Download(url, parent),
|
|
|
|
//m_replies(nullptr),
|
|
|
|
m_method(HttpDownloadMethod::Get),
|
|
|
|
m_redirectionIndex(-1)
|
|
|
|
{
|
|
|
|
if(!m_mgr) {
|
|
|
|
m_mgr = new QNetworkAccessManager();
|
|
|
|
}
|
|
|
|
// connect signals and slots
|
|
|
|
connect(m_mgr, &QNetworkAccessManager::authenticationRequired, this, &HttpDownload::slotAuthenticationRequired);
|
|
|
|
#ifndef QT_NO_OPENSSL
|
|
|
|
connect(m_mgr, &QNetworkAccessManager::sslErrors, this, &HttpDownload::slotSslErrors);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpDownload::~HttpDownload()
|
|
|
|
{
|
|
|
|
qDeleteAll(m_replies);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpDownload::doInit()
|
|
|
|
{
|
|
|
|
setTitleFromFilename(readTitleFromUrl(initialUrl()));
|
|
|
|
QString protocol = initialUrl().scheme();
|
|
|
|
if(protocol.compare(QStringLiteral("http"), Qt::CaseInsensitive)
|
|
|
|
|| protocol.compare(QStringLiteral("https"), Qt::CaseInsensitive)
|
|
|
|
|| protocol.compare(QStringLiteral("ftp"), Qt::CaseInsensitive)) {
|
|
|
|
addDownloadUrl(tr("Standard %1 download").arg(protocol), initialUrl());
|
|
|
|
reportInitiated(true);
|
|
|
|
} else {
|
|
|
|
reportInitiated(false, tr("The protocol is not supported."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Starts the request for the specified \a optionIndex.
|
|
|
|
*/
|
|
|
|
void HttpDownload::startRequest(size_t optionIndex)
|
|
|
|
{
|
|
|
|
// apply current configuration
|
|
|
|
m_mgr->setProxy(proxy());
|
|
|
|
m_request.setUrl(downloadUrl(optionIndex));
|
|
|
|
if(!userAgent().isEmpty()) {
|
|
|
|
m_request.setHeader(QNetworkRequest::UserAgentHeader, userAgent().toLocal8Bit());
|
|
|
|
}
|
|
|
|
const DownloadRange &range = this->range(); // set range only if used
|
|
|
|
if(range.isUsedForRequest()) {
|
|
|
|
qint64 currentOffset = range.currentOffset();
|
|
|
|
qint64 endOffset = range.endOffset();
|
|
|
|
if(currentOffset > 0 || endOffset > 0) {
|
|
|
|
QByteArray rangeVal;
|
|
|
|
rangeVal.append("bytes=");
|
|
|
|
if(currentOffset > 0) {
|
|
|
|
rangeVal.append(QString::number(currentOffset));
|
|
|
|
} else {
|
|
|
|
rangeVal.append('0');
|
|
|
|
}
|
|
|
|
rangeVal.append('-');
|
|
|
|
if(endOffset > 0) {
|
|
|
|
rangeVal.append(QString::number(endOffset));
|
|
|
|
}
|
|
|
|
m_request.setRawHeader("Range", rangeVal);
|
|
|
|
} else {
|
|
|
|
if(m_request.hasRawHeader("Range")) {
|
|
|
|
m_request.setRawHeader("Range", QByteArray());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// send request
|
|
|
|
QNetworkReply *reply;
|
|
|
|
switch(m_method) {
|
|
|
|
case HttpDownloadMethod::Get:
|
|
|
|
reply = m_mgr->get(m_request);
|
|
|
|
break;
|
|
|
|
case HttpDownloadMethod::Post:
|
|
|
|
reply = m_mgr->post(m_request, m_postData);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
m_replies << reply;
|
|
|
|
reply->setProperty("optionindex", QVariant::fromValue(optionIndex));
|
|
|
|
reply->setProperty("headerread", false);
|
|
|
|
connect(reply, &QNetworkReply::downloadProgress, this, &HttpDownload::slotDownloadProgress);
|
|
|
|
connect(reply, &QNetworkReply::readyRead, this, &HttpDownload::slotReadyRead);
|
|
|
|
connect(reply, &QNetworkReply::finished, this, &HttpDownload::slotFinished);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns a title derived from the specified \a url.
|
|
|
|
*/
|
|
|
|
QString HttpDownload::readTitleFromUrl(const QUrl &url)
|
|
|
|
{
|
|
|
|
QString title = QFileInfo(url.path()).fileName();
|
|
|
|
if(title.isEmpty()) {
|
|
|
|
title = url.host();
|
|
|
|
}
|
|
|
|
return title;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpDownload::checkStatusAndClear(size_t optionIndex)
|
|
|
|
{
|
|
|
|
bool ok;
|
|
|
|
for(QNetworkReply *reply : m_replies) {
|
|
|
|
if(reply->property("optionindex").toUInt(&ok) == optionIndex && ok) {
|
|
|
|
QString reasonForFail;
|
|
|
|
QNetworkReply::NetworkError error = reply->error();
|
|
|
|
if(error != QNetworkReply::NoError) {
|
|
|
|
reasonForFail = reply->errorString();
|
|
|
|
if((error == QNetworkReply::OperationCanceledError)
|
|
|
|
&& (status() == DownloadStatus::Interrupting)) {
|
|
|
|
// download has been interrupted by the user
|
|
|
|
reportDownloadInterrupted(optionIndex);
|
|
|
|
reply->deleteLater();
|
|
|
|
m_replies.removeAll(reply);
|
2015-06-24 00:54:58 +02:00
|
|
|
} else if(error == QNetworkReply::AuthenticationRequiredError) {
|
2015-04-22 19:32:04 +02:00
|
|
|
// authentication is required
|
|
|
|
reportAuthenticationRequired(optionIndex, m_realm);
|
|
|
|
reply->deleteLater();
|
|
|
|
m_replies.removeAll(reply);
|
|
|
|
// wrong to report a failed download here?
|
|
|
|
reportFinalDownloadStatus(optionIndex, false, reasonForFail, error);
|
|
|
|
} else {
|
|
|
|
// some other error occured
|
|
|
|
reply->deleteLater();
|
|
|
|
m_replies.removeAll(reply);
|
|
|
|
reportFinalDownloadStatus(optionIndex, false, reasonForFail, error);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// no error occured
|
|
|
|
// check if there's a redirection
|
|
|
|
QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
|
|
|
reply->deleteLater();
|
|
|
|
m_replies.removeAll(reply);
|
|
|
|
if(!redirectionTarget.isNull()) {
|
|
|
|
// there's a redirection available
|
|
|
|
QUrl newUrl = downloadUrl().resolved(redirectionTarget.toUrl());
|
|
|
|
QString newOption;
|
|
|
|
int counter = 1;
|
|
|
|
size_t option = optionIndex;
|
|
|
|
while(option != options().at(option).redirectionOf()) {
|
|
|
|
option = options().at(option).redirectionOf();
|
|
|
|
++counter;
|
|
|
|
}
|
|
|
|
if(counter > 1) {
|
|
|
|
newOption = tr("%1 - redirection (%2)").arg(optionName(option)).arg(counter);
|
|
|
|
} else {
|
|
|
|
newOption = tr("%1 - redirection").arg(optionName(option));
|
|
|
|
}
|
|
|
|
addDownloadUrl(newOption, newUrl, chosenOption());
|
|
|
|
reportRedirectionAvailable(optionIndex);
|
|
|
|
} else {
|
|
|
|
// the download has been finished successfully
|
|
|
|
reportFinalDownloadStatus(optionIndex, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HttpDownload::followRedirection(size_t redirectionOptionIndex)
|
|
|
|
{
|
|
|
|
setChosenOption(redirectionOptionIndex);
|
|
|
|
if(isValidOptionChosen()) {
|
|
|
|
setTitleFromFilename(readTitleFromUrl(downloadUrl()));
|
|
|
|
size_t originalIndex = options().at(redirectionOptionIndex).redirectionOf();
|
|
|
|
if(originalIndex < availableOptionCount()) {
|
|
|
|
finalizeOutputDevice(originalIndex);
|
|
|
|
}
|
|
|
|
startRequest(redirectionOptionIndex);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles the authentication required signal emitted by the network reply.
|
|
|
|
*/
|
|
|
|
void HttpDownload::slotAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
|
|
|
|
{
|
|
|
|
if(m_replies.contains(reply)) {
|
|
|
|
bool ok;
|
|
|
|
auto optionIndex = reply->property("optionindex").toUInt(&ok);
|
|
|
|
if(ok) {
|
|
|
|
m_realm = authenticator->realm();
|
|
|
|
AuthenticationCredentials &credentials = options().at(optionIndex).authenticationCredentials();
|
|
|
|
if(!credentials.isIncomplete()) {
|
|
|
|
authenticator->setUser(credentials.userName());
|
|
|
|
authenticator->setPassword(credentials.password());
|
|
|
|
// this slot will be called again if the credentials are wrong
|
|
|
|
// so we need clear the supplied credentials to prevent in infinite loop
|
|
|
|
credentials.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef QT_NO_OPENSSL
|
|
|
|
/*!
|
|
|
|
* \brief Handles the SSL errors signal emitted by the network reply.
|
|
|
|
*/
|
|
|
|
void HttpDownload::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &sslErrors)
|
|
|
|
{
|
|
|
|
if(m_replies.contains(reply)) {
|
|
|
|
bool ok;
|
|
|
|
auto optionIndex = reply->property("optionindex").toUInt(&ok);
|
|
|
|
if(ok) {
|
|
|
|
reportSslErrors(optionIndex, reply, sslErrors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles the finished signal emitted by the network reply.
|
|
|
|
*/
|
|
|
|
void HttpDownload::slotFinished()
|
|
|
|
{
|
|
|
|
bool ok;
|
|
|
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
|
|
auto optionIndex = reply->property("optionindex").toUInt(&ok);
|
|
|
|
if(ok) {
|
|
|
|
if(reply->bytesAvailable()) {
|
|
|
|
reportNewDataToBeWritten(optionIndex, reply);
|
|
|
|
}
|
|
|
|
reportDownloadComplete(optionIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles the ready read signal emitted by the network reply.
|
|
|
|
*/
|
|
|
|
void HttpDownload::slotReadyRead()
|
|
|
|
{
|
|
|
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
|
|
if(!reply->property("headerread").toBool()) {
|
|
|
|
QVariant title = reply->header(QNetworkRequest::ContentDispositionHeader);
|
|
|
|
if(title.isValid()) {
|
|
|
|
ContentDispositionParser contentDisposition(title.toString());
|
|
|
|
contentDisposition.pharse();
|
|
|
|
QString fileName = contentDisposition.fileName();
|
|
|
|
if(!fileName.isEmpty()) {
|
|
|
|
setTitleFromFilename(fileName);
|
|
|
|
reply->setProperty("headerread", true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_setCookie = reply->header(QNetworkRequest::SetCookieHeader);
|
|
|
|
}
|
|
|
|
bool ok;
|
|
|
|
auto optionIndex = reply->property("optionindex").toUInt(&ok);
|
|
|
|
if(ok) {
|
|
|
|
reportNewDataToBeWritten(optionIndex, reply);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles the download progress signal emitted by the network reply.
|
|
|
|
*/
|
|
|
|
void HttpDownload::slotDownloadProgress(qint64 bytesReceived, qint64 bytesToReceive)
|
|
|
|
{
|
|
|
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
|
|
bool ok;
|
|
|
|
auto optionIndex = reply->property("optionindex").toUInt(&ok);
|
|
|
|
if(ok) {
|
|
|
|
reportDownloadProgressUpdate(optionIndex, bytesReceived, bytesToReceive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|