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 "qnetworkreplyfileimpl_p.h"
41
42#include "QtCore/qdatetime.h"
43#include "qnetworkaccessmanager_p.h"
44#include <QtCore/QCoreApplication>
45#include <QtCore/QFileInfo>
46#include <QtCore/QThread>
47#include "qnetworkfile_p.h"
48#include "qnetworkrequest.h"
49
50QT_BEGIN_NAMESPACE
51
52QNetworkReplyFileImplPrivate::QNetworkReplyFileImplPrivate()
53 : QNetworkReplyPrivate(), managerPrivate(nullptr), realFile(nullptr)
54{
55 qRegisterMetaType<QNetworkRequest::KnownHeaders>();
56 qRegisterMetaType<QNetworkReply::NetworkError>();
57}
58
59QNetworkReplyFileImpl::~QNetworkReplyFileImpl()
60{
61 QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
62 if (d->realFile) {
63 if (d->realFile->thread() == QThread::currentThread())
64 delete d->realFile;
65 else
66 QMetaObject::invokeMethod(d->realFile, "deleteLater", Qt::QueuedConnection);
67 }
68}
69
70QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
71 : QNetworkReply(*new QNetworkReplyFileImplPrivate(), manager)
72{
73 setRequest(req);
74 setUrl(req.url());
75 setOperation(op);
76 QNetworkReply::open(QIODevice::ReadOnly);
77
78 QNetworkReplyFileImplPrivate *d = (QNetworkReplyFileImplPrivate*) d_func();
79
80 d->managerPrivate = manager->d_func();
81
82 QUrl url = req.url();
83 if (url.host() == QLatin1String("localhost"))
84 url.setHost(QString());
85
86#if !defined(Q_OS_WIN)
87 // do not allow UNC paths on Unix
88 if (!url.host().isEmpty()) {
89 // we handle only local files
90 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Request for opening non-local file %1").arg(url.toString());
91 setError(QNetworkReply::ProtocolInvalidOperationError, msg);
92 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
93 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError));
94 fileOpenFinished(false);
95 return;
96 }
97#endif
98 if (url.path().isEmpty())
99 url.setPath(QLatin1String("/"));
100 setUrl(url);
101
102 QString fileName = url.toLocalFile();
103 if (fileName.isEmpty()) {
104 const QString scheme = url.scheme();
105 if (scheme == QLatin1String("qrc")) {
106 fileName = QLatin1Char(':') + url.path();
107 } else {
108#if defined(Q_OS_ANDROID)
109 if (scheme == QLatin1String("assets"))
110 fileName = QLatin1String("assets:") + url.path();
111 else
112#endif
113 fileName = url.toString(QUrl::RemoveAuthority | QUrl::RemoveFragment | QUrl::RemoveQuery);
114 }
115 }
116
117 if (req.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open
118 auto realFile = new QNetworkFile(fileName);
119 connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setHeader,
120 Qt::QueuedConnection);
121 connect(realFile, &QNetworkFile::error, this, &QNetworkReplyFileImpl::setError,
122 Qt::QueuedConnection);
123 connect(realFile, SIGNAL(finished(bool)), SLOT(fileOpenFinished(bool)),
124 Qt::QueuedConnection);
125
126 realFile->moveToThread(d->managerPrivate->createThread());
127 QMetaObject::invokeMethod(realFile, "open", Qt::QueuedConnection);
128
129 d->realFile = realFile;
130 } else { // Synch open
131 setFinished(true);
132
133 QFileInfo fi(fileName);
134 if (fi.isDir()) {
135 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Cannot open %1: Path is a directory").arg(url.toString());
136 setError(QNetworkReply::ContentOperationNotPermittedError, msg);
137 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
138 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentOperationNotPermittedError));
139 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
140 return;
141 }
142 d->realFile = new QFile(fileName, this);
143 bool opened = d->realFile->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
144
145 // could we open the file?
146 if (!opened) {
147 QString msg = QCoreApplication::translate("QNetworkAccessFileBackend", "Error opening %1: %2")
148 .arg(d->realFile->fileName(), d->realFile->errorString());
149
150 if (fi.exists()) {
151 setError(QNetworkReply::ContentAccessDenied, msg);
152 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
153 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentAccessDenied));
154 } else {
155 setError(QNetworkReply::ContentNotFoundError, msg);
156 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
157 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
158 }
159 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
160 return;
161 }
162 setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
163 setHeader(QNetworkRequest::ContentLengthHeader, fi.size());
164
165 QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
166 QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
167 Q_ARG(qint64, fi.size()), Q_ARG(qint64, fi.size()));
168 QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
169 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
170 }
171}
172
173void QNetworkReplyFileImpl::close()
174{
175 Q_D(QNetworkReplyFileImpl);
176 QNetworkReply::close();
177 if (d->realFile) {
178 if (d->realFile->thread() == thread())
179 d->realFile->close();
180 else
181 QMetaObject::invokeMethod(d->realFile, "close", Qt::QueuedConnection);
182 }
183}
184
185void QNetworkReplyFileImpl::abort()
186{
187 close();
188}
189
190qint64 QNetworkReplyFileImpl::bytesAvailable() const
191{
192 Q_D(const QNetworkReplyFileImpl);
193 if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
194 return QNetworkReply::bytesAvailable();
195 return QNetworkReply::bytesAvailable() + d->realFile->bytesAvailable();
196}
197
198bool QNetworkReplyFileImpl::isSequential () const
199{
200 return true;
201}
202
203qint64 QNetworkReplyFileImpl::size() const
204{
205 bool ok;
206 int size = header(QNetworkRequest::ContentLengthHeader).toInt(&ok);
207 return ok ? size : 0;
208}
209
210/*!
211 \internal
212*/
213qint64 QNetworkReplyFileImpl::readData(char *data, qint64 maxlen)
214{
215 Q_D(QNetworkReplyFileImpl);
216 if (!d->isFinished || !d->realFile || !d->realFile->isOpen())
217 return -1;
218 qint64 ret = d->realFile->read(data, maxlen);
219 if (bytesAvailable() == 0)
220 d->realFile->close();
221 if (ret == 0 && bytesAvailable() == 0)
222 return -1;
223 else {
224 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
225 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, QLatin1String("OK"));
226 return ret;
227 }
228}
229
230void QNetworkReplyFileImpl::fileOpenFinished(bool isOpen)
231{
232 setFinished(true);
233 if (isOpen) {
234 const auto fileSize = size();
235 Q_EMIT metaDataChanged();
236 Q_EMIT downloadProgress(fileSize, fileSize);
237 Q_EMIT readyRead();
238 }
239 Q_EMIT finished();
240}
241
242QT_END_NAMESPACE
243
244#include "moc_qnetworkreplyfileimpl_p.cpp"
245
246