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

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