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.
Combined button and popup list for selecting options.