Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkaccessfilebackend.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
6#include "qfileinfo.h"
7#include "qdir.h"
8#include "private/qnoncontiguousbytedevice_p.h"
9
10#include <QtCore/QCoreApplication>
11#include <QtCore/QDateTime>
12
14
15using namespace Qt::StringLiterals;
16
18{
19 QStringList schemes;
20 schemes << QStringLiteral("file")
21 << QStringLiteral("qrc");
22#if defined(Q_OS_ANDROID)
23 schemes << QStringLiteral("assets");
24#endif
25 return schemes;
26}
27
29QNetworkAccessFileBackendFactory::create(QNetworkAccessManager::Operation op,
30 const QNetworkRequest &request) const
31{
32 // is it an operation we know of?
33 switch (op) {
34 case QNetworkAccessManager::GetOperation:
35 case QNetworkAccessManager::PutOperation:
36 break;
37
38 default:
39 // no, we can't handle this operation
40 return nullptr;
41 }
42
43 QUrl url = request.url();
44 if (url.scheme().compare("qrc"_L1, Qt::CaseInsensitive) == 0
45#if defined(Q_OS_ANDROID)
46 || url.scheme().compare("assets"_L1, Qt::CaseInsensitive) == 0
47#endif
48 || url.isLocalFile()) {
49 return new QNetworkAccessFileBackend;
50 } else if (!url.scheme().isEmpty() && url.authority().isEmpty() && (url.scheme().size() > 1)) {
51 // check if QFile could, in theory, open this URL via the file engines
52 // it has to be in the format:
53 // prefix:path/to/file
54 // or prefix:/path/to/file
55 //
56 // this construct here must match the one below in open()
57 QFileInfo fi(url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery));
58 if (fi.exists() || (op == QNetworkAccessManager::PutOperation && fi.dir().exists()))
59 return new QNetworkAccessFileBackend;
60 }
61
62 return nullptr;
63}
64
65// We pass TargetType::Local even though it's kind of Networked but we're using a QFile to access
66// the resource so it cannot use proxies anyway
67QNetworkAccessFileBackend::QNetworkAccessFileBackend()
68 : QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Local),
69 totalBytes(0),
70 hasUploadFinished(false)
71{
72}
73
74QNetworkAccessFileBackend::~QNetworkAccessFileBackend()
75{
76}
77
78void QNetworkAccessFileBackend::open()
79{
80 QUrl url = this->url();
81
82 if (url.host() == "localhost"_L1)
83 url.setHost(QString());
84#if !defined(Q_OS_WIN)
85 // do not allow UNC paths on Unix
86 if (!url.host().isEmpty()) {
87 // we handle only local files
88 error(QNetworkReply::ProtocolInvalidOperationError,
89 QCoreApplication::translate("QNetworkAccessFileBackend", "Request for opening non-local file %1").arg(url.toString()));
90 finished();
91 return;
92 }
93#endif // !defined(Q_OS_WIN)
94 if (url.path().isEmpty())
95 url.setPath("/"_L1);
96 setUrl(url);
97
98 QString fileName = url.toLocalFile();
99 if (fileName.isEmpty()) {
100 if (url.scheme() == "qrc"_L1) {
101 fileName = u':' + url.path();
102 } else {
103#if defined(Q_OS_ANDROID)
104 if (url.scheme() == "assets"_L1)
105 fileName = "assets:"_L1 + url.path();
106 else
107#endif
108 fileName = url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
109 }
110 }
111 file.setFileName(fileName);
112
113 if (operation() == QNetworkAccessManager::GetOperation) {
114 if (!loadFileInfo())
115 return;
116 }
117
118 QIODevice::OpenMode mode;
119 switch (operation()) {
120 case QNetworkAccessManager::GetOperation:
121 mode = QIODevice::ReadOnly;
122 break;
123 case QNetworkAccessManager::PutOperation:
124 mode = QIODevice::WriteOnly | QIODevice::Truncate;
125 createUploadByteDevice();
126 QObject::connect(uploadByteDevice(), SIGNAL(readyRead()), this, SLOT(uploadReadyReadSlot()));
127 QMetaObject::invokeMethod(this, "uploadReadyReadSlot", Qt::QueuedConnection);
128 break;
129 default:
130 Q_ASSERT_X(false, "QNetworkAccessFileBackend::open",
131 "Got a request operation I cannot handle!!");
132 return;
133 }
134
135 mode |= QIODevice::Unbuffered;
136 bool opened = file.open(mode);
137 if (file.isSequential())
138 connect(&file, &QIODevice::readChannelFinished, this, [this]() { finished(); });
139
140 // could we open the file?
141 if (!opened) {
142 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2")
143 .arg(this->url().toString(), file.errorString());
144
145 // why couldn't we open the file?
146 // if we're opening for reading, either it doesn't exist, or it's access denied
147 // if we're opening for writing, not existing means it's access denied too
148 if (file.exists() || operation() == QNetworkAccessManager::PutOperation)
149 error(QNetworkReply::ContentAccessDenied, msg);
150 else
151 error(QNetworkReply::ContentNotFoundError, msg);
152 finished();
153 }
154}
155
156void QNetworkAccessFileBackend::uploadReadyReadSlot()
157{
158 if (hasUploadFinished)
159 return;
160
161 forever {
162 QByteArray data(16 * 1024, Qt::Uninitialized);
163 qint64 haveRead = uploadByteDevice()->peek(data.data(), data.size());
164 if (haveRead == -1) {
165 // EOF
166 hasUploadFinished = true;
167 file.flush();
168 file.close();
169 finished();
170 break;
171 } else if (haveRead == 0) {
172 // nothing to read right now, we will be called again later
173 break;
174 } else {
175 qint64 haveWritten;
176 data.truncate(haveRead);
177 haveWritten = file.write(data);
178
179 if (haveWritten < 0) {
180 // write error!
181 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Write error writing to %1: %2")
182 .arg(url().toString(), file.errorString());
183 error(QNetworkReply::ProtocolFailure, msg);
184
185 finished();
186 return;
187 } else {
188 uploadByteDevice()->skip(haveWritten);
189 }
190
191
192 file.flush();
193 }
194 }
195}
196
197void QNetworkAccessFileBackend::close()
198{
199 if (operation() == QNetworkAccessManager::GetOperation) {
200 file.close();
201 }
202}
203
204bool QNetworkAccessFileBackend::loadFileInfo()
205{
206 QFileInfo fi(file);
207 setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
208 setHeader(QNetworkRequest::ContentLengthHeader, fi.size());
209
210 // signal we're open
211 metaDataChanged();
212
213 if (fi.isDir()) {
214 error(QNetworkReply::ContentOperationNotPermittedError,
215 QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url().toString()));
216 finished();
217 return false;
218 }
219
220 return true;
221}
222
223qint64 QNetworkAccessFileBackend::bytesAvailable() const
224{
225 if (operation() != QNetworkAccessManager::GetOperation)
226 return 0;
227 return file.bytesAvailable();
228}
229
230qint64 QNetworkAccessFileBackend::read(char *data, qint64 maxlen)
231{
232 if (operation() != QNetworkAccessManager::GetOperation)
233 return 0;
234 qint64 actuallyRead = file.read(data, maxlen);
235 if (actuallyRead <= 0) {
236 // EOF or error
237 if (file.error() != QFile::NoError) {
238 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Read error reading from %1: %2")
239 .arg(url().toString(), file.errorString());
240 error(QNetworkReply::ProtocolFailure, msg);
241
242 finished();
243 return -1;
244 }
245
246 finished();
247 return actuallyRead;
248 }
249 if (!file.isSequential() && file.atEnd())
250 finished();
251 totalBytes += actuallyRead;
252 return actuallyRead;
253}
254
255QT_END_NAMESPACE
256
257#include "moc_qnetworkaccessfilebackend_p.cpp"
virtual QNetworkAccessBackend * create(QNetworkAccessManager::Operation op, const QNetworkRequest &request) const override
Override this method in your own class and return a heap-allocated instance of your class derived fro...
virtual QStringList supportedSchemes() const override
Override this method in your own derived class to let Qt know what schemes your class can handle.