1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qnetworkreplyfileimpl_p.h"
5
6#include "QtCore/qdatetime.h"
7#include "qnetworkaccessmanager_p.h"
8#include <QtCore/QCoreApplication>
9#include <QtCore/QFileInfo>
10#include <QtCore/QThread>
11#include "qnetworkfile_p.h"
12#include "qnetworkrequest.h"
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::StringLiterals;
17
18QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::KnownHeaders, QNetworkRequest__KnownHeaders)
19
20QNetworkReplyFileImplPrivate::QNetworkReplyFileImplPrivate()
21 : QNetworkReplyPrivate(), managerPrivate(nullptr), realFile(nullptr)
22{
23 qRegisterMetaType<QNetworkRequest::KnownHeaders>();
24 qRegisterMetaType<QNetworkReply::NetworkError>();
25}
26
27QNetworkReplyFileImpl::~QNetworkReplyFileImpl()
28{
29 QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
30 if (d->realFile) {
31 if (d->realFile->thread() == QThread::currentThread())
32 delete d->realFile;
33 else
34 QMetaObject::invokeMethod(obj: d->realFile, member: "deleteLater", c: Qt::QueuedConnection);
35 }
36}
37
38QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
39 : QNetworkReply(*new QNetworkReplyFileImplPrivate(), manager)
40{
41 setRequest(req);
42 setUrl(req.url());
43 setOperation(op);
44 QNetworkReply::open(mode: QIODevice::ReadOnly);
45
46 QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
47
48 d->managerPrivate = manager->d_func();
49
50 QUrl url = req.url();
51 if (url.host() == "localhost"_L1)
52 url.setHost(host: QString());
53
54#if !defined(Q_OS_WIN)
55 // do not allow UNC paths on Unix
56 if (!url.host().isEmpty()) {
57 // we handle only local files
58 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Request for opening non-local file %1").arg(a: url.toString());
59 setError(errorCode: QNetworkReply::ProtocolInvalidOperationError, errorString: msg);
60 setFinished(true); // We're finished, will emit finished() after ctor is done.
61 QMetaObject::invokeMethod(obj: this, member: "errorOccurred", c: Qt::QueuedConnection,
62 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError));
63 QMetaObject::invokeMethod(object: this, function: [this](){ fileOpenFinished(isOpen: false); }, type: Qt::QueuedConnection);
64 return;
65 }
66#endif
67 if (url.path().isEmpty())
68 url.setPath(path: "/"_L1);
69 setUrl(url);
70
71 QString fileName = url.toLocalFile();
72 if (fileName.isEmpty()) {
73 const QString scheme = url.scheme();
74 if (scheme == "qrc"_L1) {
75 fileName = u':' + url.path();
76 } else {
77#if defined(Q_OS_ANDROID)
78 if (scheme == "assets"_L1)
79 fileName = "assets:"_L1 + url.path();
80 else
81#endif
82 fileName = url.toString(options: QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
83 }
84 }
85
86 if (req.attribute(code: QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open
87 auto realFile = new QNetworkFile(fileName);
88 connect(sender: realFile, signal: &QNetworkFile::headerRead, context: this, slot: &QNetworkReplyFileImpl::setHeader,
89 type: Qt::QueuedConnection);
90 connect(sender: realFile, signal: &QNetworkFile::error, context: this, slot: &QNetworkReplyFileImpl::setError,
91 type: Qt::QueuedConnection);
92 connect(asender: realFile, SIGNAL(finished(bool)), SLOT(fileOpenFinished(bool)),
93 atype: Qt::QueuedConnection);
94
95 realFile->moveToThread(thread: d->managerPrivate->createThread());
96 QMetaObject::invokeMethod(obj: realFile, member: "open", c: Qt::QueuedConnection);
97
98 d->realFile = realFile;
99 } else { // Synch open
100 setFinished(true);
101
102 QFileInfo fi(fileName);
103 if (fi.isDir()) {
104 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Cannot open %1: Path is a directory").arg(a: url.toString());
105 setError(errorCode: QNetworkReply::ContentOperationNotPermittedError, errorString: msg);
106 QMetaObject::invokeMethod(obj: this, member: "errorOccurred", c: Qt::QueuedConnection,
107 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError));
108 QMetaObject::invokeMethod(obj: this, member: "finished", c: Qt::QueuedConnection);
109 return;
110 }
111 d->realFile = new QFile(fileName, this);
112 bool opened = d->realFile->open(flags: QIODevice::ReadOnly | QIODevice::Unbuffered);
113
114 // could we open the file?
115 if (!opened) {
116 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Error opening %1: %2")
117 .arg(args: d->realFile->fileName(), args: d->realFile->errorString());
118
119 if (fi.exists()) {
120 setError(errorCode: QNetworkReply::ContentAccessDenied, errorString: msg);
121 QMetaObject::invokeMethod(obj: this, member: "errorOccurred", c: Qt::QueuedConnection,
122 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied));
123 } else {
124 setError(errorCode: QNetworkReply::ContentNotFoundError, errorString: msg);
125 QMetaObject::invokeMethod(obj: this, member: "errorOccurred", c: Qt::QueuedConnection,
126 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
127 }
128 QMetaObject::invokeMethod(obj: this, member: "finished", c: Qt::QueuedConnection);
129 return;
130 }
131 setHeader(header: QNetworkRequest::LastModifiedHeader, value: fi.lastModified());
132 setHeader(header: QNetworkRequest::ContentLengthHeader, value: fi.size());
133
134 QMetaObject::invokeMethod(obj: this, member: "metaDataChanged", c: Qt::QueuedConnection);
135 QMetaObject::invokeMethod(obj: this, member: "downloadProgress", c: Qt::QueuedConnection,
136 Q_ARG(qint64, fi.size()), Q_ARG(qint64, fi.size()));
137 QMetaObject::invokeMethod(obj: this, member: "readyRead", c: Qt::QueuedConnection);
138 QMetaObject::invokeMethod(obj: this, member: "finished", c: Qt::QueuedConnection);
139 }
140}
141
142void QNetworkReplyFileImpl::close()
143{
144 Q_D(QNetworkReplyFileImpl);
145 QNetworkReply::close();
146 if (d->realFile) {
147 if (d->realFile->thread() == thread())
148 d->realFile->close();
149 else
150 QMetaObject::invokeMethod(obj: d->realFile, member: "close", c: Qt::QueuedConnection);
151 }
152}
153
154void QNetworkReplyFileImpl::abort()
155{
156 close();
157}
158
159qint64 QNetworkReplyFileImpl::bytesAvailable() const
160{
161 Q_D(const QNetworkReplyFileImpl);
162 if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
163 return QNetworkReply::bytesAvailable();
164 return QNetworkReply::bytesAvailable() + d->realFile->bytesAvailable();
165}
166
167bool QNetworkReplyFileImpl::isSequential () const
168{
169 return true;
170}
171
172qint64 QNetworkReplyFileImpl::size() const
173{
174 bool ok;
175 int size = header(header: QNetworkRequest::ContentLengthHeader).toInt(ok: &ok);
176 return ok ? size : 0;
177}
178
179/*!
180 \internal
181*/
182qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen)
183{
184 Q_D(QNetworkReplyFileImpl);
185 if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
186 return -1;
187 qint64 ret = d->realFile->read(data, maxlen);
188 if (bytesAvailable() == 0)
189 d->realFile->close();
190 if (ret == 0 && bytesAvailable() == 0)
191 return -1;
192 else {
193 setAttribute(code: QNetworkRequest::HttpStatusCodeAttribute, value: 200);
194 setAttribute(code: QNetworkRequest::HttpReasonPhraseAttribute, value: "OK"_L1);
195 return ret;
196 }
197}
198
199void QNetworkReplyFileImpl::fileOpenFinished(bool isOpen)
200{
201 setFinished(true);
202 if (isOpen) {
203 const auto fileSize = size();
204 Q_EMIT metaDataChanged();
205 Q_EMIT downloadProgress(bytesReceived: fileSize, bytesTotal: fileSize);
206 Q_EMIT readyRead();
207 }
208 Q_EMIT finished();
209}
210
211QT_END_NAMESPACE
212
213#include "moc_qnetworkreplyfileimpl_p.cpp"
214
215

source code of qtbase/src/network/access/qnetworkreplyfileimpl.cpp