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
qrestreply.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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:critical reason:data-parser
4
5#include "qrestreply.h"
6#include "qrestreply_p.h"
7
8#include <QtNetwork/private/qnetworkreply_p.h>
9#include <QtNetwork/private/qrestaccessmanager_p.h>
10#include <QtNetwork/private/qtcontenttypeparser_p.h>
11
12#include <QtCore/qbytearrayview.h>
13#include <QtCore/qjsondocument.h>
14#include <QtCore/qlatin1stringmatcher.h>
15#include <QtCore/qlatin1stringview.h>
16#include <QtCore/qloggingcategory.h>
17#include <QtCore/qstringconverter.h>
18
19#include <QtCore/qxpfunctional.h>
20
22
23using namespace Qt::StringLiterals;
24
25/*!
26 \class QRestReply
27 \since 6.7
28 \brief QRestReply is a convenience wrapper for QNetworkReply.
29
30 \reentrant
31 \ingroup network
32 \inmodule QtNetwork
33
34 QRestReply wraps a QNetworkReply and provides convenience methods for data
35 and status handling. The methods provide convenience for typical RESTful
36 client applications.
37
38 QRestReply doesn't take ownership of the wrapped QNetworkReply, and the
39 lifetime and ownership of the reply is as defined by QNetworkAccessManager
40 documentation.
41
42 QRestReply object is not copyable, but is movable.
43
44 \sa QRestAccessManager, QNetworkReply, QNetworkAccessManager,
45 QNetworkAccessManager::setAutoDeleteReplies()
46*/
47
48/*!
49 Creates a QRestReply and initializes the wrapped QNetworkReply to \a reply.
50*/
51QRestReply::QRestReply(QNetworkReply *reply)
52 : wrapped(reply)
53{
54 if (!wrapped)
55 qCWarning(lcQrest, "QRestReply: QNetworkReply is nullptr");
56}
57
58/*!
59 Destroys this QRestReply object.
60*/
62{
63 delete d;
64}
65
66/*!
67 \fn QRestReply::QRestReply(QRestReply &&other) noexcept
68
69 Move-constructs the reply from \a other.
70
71 \note The moved-from object \a other is placed in a
72 partially-formed state, in which the only valid operations are
73 destruction and assignment of a new value.
74*/
75
76/*!
77 \fn QRestReply &QRestReply::operator=(QRestReply &&other) noexcept
78
79 Move-assigns \a other and returns a reference to this reply.
80
81 \note The moved-from object \a other is placed in a
82 partially-formed state, in which the only valid operations are
83 destruction and assignment of a new value.
84*/
85
86/*!
87 Returns a pointer to the underlying QNetworkReply wrapped by this object.
88*/
89QNetworkReply *QRestReply::networkReply() const
90{
91 return wrapped;
92}
93
94/*!
95 Returns the received data as a QJsonDocument.
96
97 The returned value is wrapped in \c std::optional. If the conversion
98 from the received data fails (empty data or JSON parsing error),
99 \c std::nullopt is returned, and \a error is filled with details.
100
101 Calling this function consumes the received data, and any further calls
102 to get response data will return empty.
103
104 This function returns \c {std::nullopt} and will not consume
105 any data if the reply is not finished. If \a error is passed, it will be
106 set to QJsonParseError::NoError to distinguish this case from an actual
107 error.
108
109 \sa readBody(), readText()
110*/
111std::optional<QJsonDocument> QRestReply::readJson(QJsonParseError *error)
112{
113 if (!wrapped) {
114 if (error)
115 *error = {0, QJsonParseError::ParseError::NoError};
116 return std::nullopt;
117 }
118
119 if (!wrapped->isFinished()) {
120 qCWarning(lcQrest, "readJson() called on an unfinished reply, ignoring");
121 if (error)
122 *error = {0, QJsonParseError::ParseError::NoError};
123 return std::nullopt;
124 }
125 QJsonParseError parseError;
126 const QByteArray data = wrapped->readAll();
127 const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
128 if (error)
129 *error = parseError;
130 if (parseError.error)
131 return std::nullopt;
132 return doc;
133}
134
135/*!
136 Returns the received data as a QByteArray.
137
138 Calling this function consumes the data received so far, and any further
139 calls to get response data will return empty until further data has been
140 received.
141
142 \sa readJson(), readText(), QNetworkReply::bytesAvailable(),
143 QNetworkReply::readyRead()
144*/
146{
147 return wrapped ? wrapped->readAll() : QByteArray{};
148}
149
150/*!
151 Returns the received data as a QString.
152
153 The received data is decoded into a QString (UTF-16). If available, the decoding
154 uses the \e Content-Type header's \e charset parameter to determine the
155 source encoding. If the encoding information is not available or not supported
156 by \l QStringConverter, UTF-8 is used by default.
157
158 Calling this function consumes the data received so far. Returns
159 a default constructed value if no new data is available, or if the
160 decoding is not supported by \l QStringConverter, or if the decoding
161 has errors (for example invalid characters).
162
163 \sa readJson(), readBody(), QNetworkReply::readyRead()
164*/
165QString QRestReply::readText()
166{
167 QString result;
168 if (!wrapped)
169 return result;
170
171 QByteArray data = wrapped->readAll();
172 if (data.isEmpty())
173 return result;
174
175 // Text decoding needs to persist decoding state across calls to this function,
176 // so allocate decoder if not yet allocated.
177 if (!d)
178 d = new QRestReplyPrivate;
179
180 if (!d->decoder) {
181 const QByteArray charset = QRestReplyPrivate::contentCharset(wrapped);
182 d->decoder.emplace(charset.constData());
183 if (!d->decoder->isValid()) { // the decoder may not support the mimetype's charset
184 qCWarning(lcQrest, "readText(): Charset \"%s\" is not supported", charset.constData());
185 return result;
186 }
187 }
188 // Check if the decoder already had an error, or has errors after decoding current data chunk
189 if (d->decoder->hasError() || (result = (*d->decoder)(data), d->decoder->hasError())) {
190 qCWarning(lcQrest, "readText(): Decoding error occurred");
191 return {};
192 }
193 return result;
194}
195
196/*!
197 Returns the HTTP status received in the server response.
198 The value is \e 0 if not available (the status line has not been received,
199 yet).
200
201 \note The HTTP status is reported as indicated by the received HTTP
202 response. An error() may occur after receiving the status, for instance
203 due to network disconnection while receiving a long response.
204 These potential subsequent errors are not represented by the reported
205 HTTP status.
206
207 \sa isSuccess(), hasError(), error()
208*/
210{
211 return wrapped ? wrapped->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : 0;
212}
213
214/*!
215 \fn bool QRestReply::isSuccess() const
216
217 Returns whether the HTTP status is between 200..299 and no
218 further errors have occurred while receiving the response (for example,
219 abrupt disconnection while receiving the body data). This function
220 is a convenient way to check whether the response is considered successful.
221
222 \sa httpStatus(), hasError(), error()
223*/
224
225/*!
226 Returns whether the HTTP status is between 200..299.
227
228 \sa isSuccess(), httpStatus(), hasError(), error()
229*/
231{
232 const int status = httpStatus();
233 return status >= 200 && status < 300;
234}
235
236/*!
237 Returns whether an error has occurred. This includes errors such as
238 network and protocol errors, but excludes cases where the server
239 successfully responded with an HTTP error status (for example
240 \c {500 Internal Server Error}). Use \l httpStatus() or
241 \l isHttpStatusSuccess() to get the HTTP status information.
242
243 \sa httpStatus(), isSuccess(), error(), errorString()
244*/
245bool QRestReply::hasError() const
246{
247 if (!wrapped)
248 return false;
249
250 const int status = httpStatus();
251 if (status > 0) {
252 // The HTTP status is set upon receiving the response headers, but the
253 // connection might still fail later while receiving the body data.
254 return wrapped->error() == QNetworkReply::RemoteHostClosedError;
255 }
256 return wrapped->error() != QNetworkReply::NoError;
257}
258
259/*!
260 Returns the last error, if any. The errors include
261 errors such as network and protocol errors, but exclude
262 cases when the server successfully responded with an HTTP status.
263
264 \sa httpStatus(), isSuccess(), hasError(), errorString()
265*/
266QNetworkReply::NetworkError QRestReply::error() const
267{
268 if (!hasError())
269 return QNetworkReply::NetworkError::NoError;
270 return wrapped->error();
271}
272
273/*!
274 Returns a human-readable description of the last network error.
275
276 \sa httpStatus(), isSuccess(), hasError(), error()
277*/
278QString QRestReply::errorString() const
279{
280 if (hasError())
281 return wrapped->errorString();
282 return {};
283}
284
286 = default;
287
289 = default;
290
291#ifndef QT_NO_DEBUG_STREAM
292static QLatin1StringView operationName(QNetworkAccessManager::Operation operation)
293{
294 switch (operation) {
295 case QNetworkAccessManager::Operation::GetOperation:
296 return "GET"_L1;
297 case QNetworkAccessManager::Operation::HeadOperation:
298 return "HEAD"_L1;
299 case QNetworkAccessManager::Operation::PostOperation:
300 return "POST"_L1;
301 case QNetworkAccessManager::Operation::PutOperation:
302 return "PUT"_L1;
303 case QNetworkAccessManager::Operation::DeleteOperation:
304 return "DELETE"_L1;
305 case QNetworkAccessManager::Operation::CustomOperation:
306 return "CUSTOM"_L1;
307 case QNetworkAccessManager::Operation::UnknownOperation:
308 return "UNKNOWN"_L1;
309 }
310 Q_UNREACHABLE_RETURN({});
311}
312
313/*!
314 \fn QDebug QRestReply::operator<<(QDebug debug, const QRestReply &reply)
315
316 Writes the \a reply into the \a debug object for debugging purposes.
317
318 \sa {Debugging Techniques}
319*/
320QDebug operator<<(QDebug debug, const QRestReply &reply)
321{
322 const QDebugStateSaver saver(debug);
323 debug.resetFormat().nospace();
324 if (!reply.networkReply()) {
325 debug << "QRestReply(no network reply)";
326 return debug;
327 }
328 debug << "QRestReply(isSuccess = " << reply.isSuccess()
329 << ", httpStatus = " << reply.httpStatus()
330 << ", isHttpStatusSuccess = " << reply.isHttpStatusSuccess()
331 << ", hasError = " << reply.hasError()
332 << ", errorString = " << reply.errorString()
333 << ", error = " << reply.error()
334 << ", isFinished = " << reply.networkReply()->isFinished()
335 << ", bytesAvailable = " << reply.networkReply()->bytesAvailable()
336 << ", url " << reply.networkReply()->url()
337 << ", operation = " << operationName(reply.networkReply()->operation())
338 << ", reply headers = " << reply.networkReply()->headers()
339 << ")";
340 return debug;
341}
342#endif // QT_NO_DEBUG_STREAM
343
344QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply)
345{
346 // Content-type consists of mimetype and optional parameters, of which one may be 'charset'
347 // Example values and their combinations below are all valid, see RFC 7231 section 3.1.1.5
348 // and RFC 2045 section 5.1
349 //
350 // text/plain; charset=utf-8
351 // text/plain; charset=utf-8;version=1.7
352 // text/plain; charset = utf-8
353 // text/plain; charset ="utf-8"
354
355 const QByteArray contentTypeValue =
356 reply->headers().value(QHttpHeaders::WellKnownHeader::ContentType).toByteArray();
357
358 const auto r = QtContentTypeParser::parse_content_type(contentTypeValue);
359 if (r && !r.charset.empty())
360 return QByteArrayView(r.charset).toByteArray();
361 else
362 return "UTF-8"_ba; // Default to the most commonly used UTF-8.
363}
364
365QT_END_NAMESPACE
static QByteArray contentCharset(const QNetworkReply *reply)
QRestReply is a convenience wrapper for QNetworkReply.
Definition qrestreply.h:25
Q_NETWORK_EXPORT int httpStatus() const
Returns the HTTP status received in the server response.
Q_NETWORK_EXPORT bool isHttpStatusSuccess() const
Returns whether the HTTP status is between 200..299.
Q_NETWORK_EXPORT bool hasError() const
Returns whether an error has occurred.
Q_NETWORK_EXPORT ~QRestReply()
Destroys this QRestReply object.
static QLatin1StringView operationName(QNetworkAccessManager::Operation operation)