1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qnetworkaccessfilebackend_p.h"
41#include "qfileinfo.h"
42#if QT_CONFIG(ftp)
43#include "qurlinfo_p.h"
44#endif
45#include "qdir.h"
46#include "private/qnoncontiguousbytedevice_p.h"
47
48#include <QtCore/QCoreApplication>
49#include <QtCore/QDateTime>
50
51QT_BEGIN_NAMESPACE
52
53QStringList QNetworkAccessFileBackendFactory::supportedSchemes() const
54{
55 QStringList schemes;
56 schemes << QStringLiteral("file")
57 << QStringLiteral("qrc");
58#if defined(Q_OS_ANDROID)
59 schemes << QStringLiteral("assets");
60#endif
61 return schemes;
62}
63
64QNetworkAccessBackend *
65QNetworkAccessFileBackendFactory::create(QNetworkAccessManager::Operation op,
66 const QNetworkRequest &request) const
67{
68 // is it an operation we know of?
69 switch (op) {
70 case QNetworkAccessManager::GetOperation:
71 case QNetworkAccessManager::PutOperation:
72 break;
73
74 default:
75 // no, we can't handle this operation
76 return nullptr;
77 }
78
79 QUrl url = request.url();
80 if (url.scheme().compare(other: QLatin1String("qrc"), cs: Qt::CaseInsensitive) == 0
81#if defined(Q_OS_ANDROID)
82 || url.scheme().compare(QLatin1String("assets"), Qt::CaseInsensitive) == 0
83#endif
84 || url.isLocalFile()) {
85 return new QNetworkAccessFileBackend;
86 } else if (!url.scheme().isEmpty() && url.authority().isEmpty() && (url.scheme().length() > 1)) {
87 // check if QFile could, in theory, open this URL via the file engines
88 // it has to be in the format:
89 // prefix:path/to/file
90 // or prefix:/path/to/file
91 //
92 // this construct here must match the one below in open()
93 QFileInfo fi(url.toString(options: QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery));
94 if (fi.exists() || (op == QNetworkAccessManager::PutOperation && fi.dir().exists()))
95 return new QNetworkAccessFileBackend;
96 }
97
98 return nullptr;
99}
100
101QNetworkAccessFileBackend::QNetworkAccessFileBackend()
102 : totalBytes(0), hasUploadFinished(false)
103{
104}
105
106QNetworkAccessFileBackend::~QNetworkAccessFileBackend()
107{
108}
109
110void QNetworkAccessFileBackend::open()
111{
112 QUrl url = this->url();
113
114 if (url.host() == QLatin1String("localhost"))
115 url.setHost(host: QString());
116#if !defined(Q_OS_WIN)
117 // do not allow UNC paths on Unix
118 if (!url.host().isEmpty()) {
119 // we handle only local files
120 error(code: QNetworkReply::ProtocolInvalidOperationError,
121 errorString: QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Request for opening non-local file %1").arg(a: url.toString()));
122 finished();
123 return;
124 }
125#endif // !defined(Q_OS_WIN)
126 if (url.path().isEmpty())
127 url.setPath(path: QLatin1String("/"));
128 setUrl(url);
129
130 QString fileName = url.toLocalFile();
131 if (fileName.isEmpty()) {
132 if (url.scheme() == QLatin1String("qrc")) {
133 fileName = QLatin1Char(':') + url.path();
134 } else {
135#if defined(Q_OS_ANDROID)
136 if (url.scheme() == QLatin1String("assets"))
137 fileName = QLatin1String("assets:") + url.path();
138 else
139#endif
140 fileName = url.toString(options: QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
141 }
142 }
143 file.setFileName(fileName);
144
145 if (operation() == QNetworkAccessManager::GetOperation) {
146 if (!loadFileInfo())
147 return;
148 }
149
150 QIODevice::OpenMode mode;
151 switch (operation()) {
152 case QNetworkAccessManager::GetOperation:
153 mode = QIODevice::ReadOnly;
154 break;
155 case QNetworkAccessManager::PutOperation:
156 mode = QIODevice::WriteOnly | QIODevice::Truncate;
157 createUploadByteDevice();
158 QObject::connect(sender: uploadByteDevice.data(), SIGNAL(readyRead()), receiver: this, SLOT(uploadReadyReadSlot()));
159 QMetaObject::invokeMethod(obj: this, member: "uploadReadyReadSlot", type: Qt::QueuedConnection);
160 break;
161 default:
162 Q_ASSERT_X(false, "QNetworkAccessFileBackend::open",
163 "Got a request operation I cannot handle!!");
164 return;
165 }
166
167 mode |= QIODevice::Unbuffered;
168 bool opened = file.open(flags: mode);
169
170 // could we open the file?
171 if (!opened) {
172 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Error opening %1: %2")
173 .arg(args: this->url().toString(), args: file.errorString());
174
175 // why couldn't we open the file?
176 // if we're opening for reading, either it doesn't exist, or it's access denied
177 // if we're opening for writing, not existing means it's access denied too
178 if (file.exists() || operation() == QNetworkAccessManager::PutOperation)
179 error(code: QNetworkReply::ContentAccessDenied, errorString: msg);
180 else
181 error(code: QNetworkReply::ContentNotFoundError, errorString: msg);
182 finished();
183 }
184}
185
186void QNetworkAccessFileBackend::uploadReadyReadSlot()
187{
188 if (hasUploadFinished)
189 return;
190
191 forever {
192 qint64 haveRead;
193 const char *readPointer = uploadByteDevice->readPointer(maximumLength: -1, len&: haveRead);
194 if (haveRead == -1) {
195 // EOF
196 hasUploadFinished = true;
197 file.flush();
198 file.close();
199 finished();
200 break;
201 } else if (haveRead == 0 || readPointer == nullptr) {
202 // nothing to read right now, we will be called again later
203 break;
204 } else {
205 qint64 haveWritten;
206 haveWritten = file.write(data: readPointer, len: haveRead);
207
208 if (haveWritten < 0) {
209 // write error!
210 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Write error writing to %1: %2")
211 .arg(args: url().toString(), args: file.errorString());
212 error(code: QNetworkReply::ProtocolFailure, errorString: msg);
213
214 finished();
215 return;
216 } else {
217 uploadByteDevice->advanceReadPointer(amount: haveWritten);
218 }
219
220
221 file.flush();
222 }
223 }
224}
225
226void QNetworkAccessFileBackend::closeDownstreamChannel()
227{
228 if (operation() == QNetworkAccessManager::GetOperation) {
229 file.close();
230 }
231}
232
233void QNetworkAccessFileBackend::downstreamReadyWrite()
234{
235 Q_ASSERT_X(operation() == QNetworkAccessManager::GetOperation, "QNetworkAccessFileBackend",
236 "We're being told to download data but operation isn't GET!");
237
238 readMoreFromFile();
239}
240
241bool QNetworkAccessFileBackend::loadFileInfo()
242{
243 QFileInfo fi(file);
244 setHeader(header: QNetworkRequest::LastModifiedHeader, value: fi.lastModified());
245 setHeader(header: QNetworkRequest::ContentLengthHeader, value: fi.size());
246
247 // signal we're open
248 metaDataChanged();
249
250 if (fi.isDir()) {
251 error(code: QNetworkReply::ContentOperationNotPermittedError,
252 errorString: QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Cannot open %1: Path is a directory").arg(a: url().toString()));
253 finished();
254 return false;
255 }
256
257 return true;
258}
259
260bool QNetworkAccessFileBackend::readMoreFromFile()
261{
262 qint64 wantToRead;
263 while ((wantToRead = nextDownstreamBlockSize()) > 0) {
264 // ### FIXME!!
265 // Obtain a pointer from the ringbuffer!
266 // Avoid extra copy
267 QByteArray data;
268 data.reserve(asize: wantToRead);
269 qint64 actuallyRead = file.read(data: data.data(), maxlen: wantToRead);
270 if (actuallyRead <= 0) {
271 // EOF or error
272 if (file.error() != QFile::NoError) {
273 QString msg = QCoreApplication::translate(context: "QNetworkAccessFileBackend", key: "Read error reading from %1: %2")
274 .arg(args: url().toString(), args: file.errorString());
275 error(code: QNetworkReply::ProtocolFailure, errorString: msg);
276
277 finished();
278 return false;
279 }
280
281 finished();
282 return true;
283 }
284
285 data.resize(size: actuallyRead);
286 totalBytes += actuallyRead;
287
288 QByteDataBuffer list;
289 list.append(bd: data);
290 data.clear(); // important because of implicit sharing!
291 writeDownstreamData(list);
292 }
293 return true;
294}
295
296QT_END_NAMESPACE
297

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