cutelyst 4.3.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
response.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "common.h"
6#include "context_p.h"
7#include "engine.h"
8#include "enginerequest.h"
9#include "response_p.h"
10
11#include <QCryptographicHash>
12#include <QEventLoop>
13#include <QJsonArray>
14#include <QJsonObject>
15#include <QtCore/QJsonDocument>
16
17using namespace Cutelyst;
18
19Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
20 : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
21{
23}
24
25qint64 Response::readData(char *data, qint64 maxlen)
26{
27 Q_UNUSED(data)
28 Q_UNUSED(maxlen)
29 return -1;
30}
31
32qint64 Response::writeData(const char *data, qint64 len)
33{
34 Q_D(Response);
35
36 if (len <= 0) {
37 return len;
38 }
39
40 // Finalize headers if someone manually writes output
41 if (!(d->engineRequest->status & EngineRequest::FinalizedHeaders)) {
42 if (d->headers.header("Transfer-Encoding"_qba).compare("chunked") == 0) {
43 d->engineRequest->status |= EngineRequest::IOWrite | EngineRequest::Chunked;
44 } else {
45 // When chunked encoding is not set the client can only know
46 // that data is finished if we close the connection
47 d->headers.setHeader("Connection"_qba, "Close"_qba);
48 d->engineRequest->status |= EngineRequest::IOWrite;
49 }
50 delete d->bodyIODevice;
51 d->bodyIODevice = nullptr;
52 d->bodyData = QByteArray();
53
54 d->engineRequest->finalizeHeaders();
55 }
56
57 return d->engineRequest->write(data, len);
58}
59
61{
62 delete d_ptr->bodyIODevice;
63 delete d_ptr;
64}
65
66quint16 Response::status() const noexcept
67{
68 Q_D(const Response);
69 return d->status;
70}
71
72void Response::setStatus(quint16 status) noexcept
73{
74 Q_D(Response);
75 d->status = status;
76}
77
78bool Response::hasBody() const noexcept
79{
80 Q_D(const Response);
81 return !d->bodyData.isEmpty() || d->bodyIODevice ||
82 d->engineRequest->status & EngineRequest::IOWrite;
83}
84
86{
87 Q_D(Response);
88 if (d->bodyIODevice) {
89 delete d->bodyIODevice;
90 d->bodyIODevice = nullptr;
91 }
92 // Content-Length is set at finalizeHeaders() as we can't know it here
93
94 return d->bodyData;
95}
96
98{
99 Q_D(const Response);
100 return d->bodyIODevice;
101}
102
104{
105 Q_D(Response);
106 Q_ASSERT(body && body->isOpen() && body->isReadable());
107
108 if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
109 d->bodyData = QByteArray();
110 if (d->bodyIODevice) {
111 delete d->bodyIODevice;
112 }
113 d->bodyIODevice = body;
114 // Content-Length is set at finalizeHeaders()
115 // because & ::body() reference might change it
116 }
117}
118
120{
121 Q_D(Response);
122 d->setBodyData(body);
123}
124
126{
127 Q_D(Response);
128 d->setBodyData(cbor);
129 d->headers.setContentType("application/cbor"_qba);
130}
131
133{
134 setCborBody(value.toCbor());
135}
136
138{
139 Q_D(Response);
140 d->setBodyData(json);
141 d->headers.setContentType("application/json"_qba);
142}
143
148
153
155{
156 Q_D(const Response);
157 return d->headers.contentEncoding();
158}
159
161{
162 Q_D(Response);
163 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
164 "setContentEncoding",
165 "setting a header value after finalize_headers and the response callback has been "
166 "called. Not what you want.");
167
168 d->headers.setContentEncoding(encoding);
169}
170
172{
173 Q_D(const Response);
174 return d->headers.contentLength();
175}
176
177void Response::setContentLength(qint64 length)
178{
179 Q_D(Response);
180 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
181 "setContentLength",
182 "setting a header value after finalize_headers and the response callback has been "
183 "called. Not what you want.");
184
185 d->headers.setContentLength(length);
186}
187
189{
190 Q_D(const Response);
191 return d->headers.contentType();
192}
193
195{
196 Q_D(const Response);
197 return d->headers.contentTypeCharset();
198}
199
201{
202 Q_D(const Response);
203 return QVariant::fromValue(d->cookies.value(name));
204}
205
207{
208 Q_D(const Response);
209 return d->cookies.values();
210}
211
213{
214 Q_D(Response);
215 d->cookies.insert(cookie.name(), cookie);
216}
217
219{
220 Q_D(Response);
221 for (const QNetworkCookie &cookie : cookies) {
222 d->cookies.insert(cookie.name(), cookie);
223 }
224}
225
227{
228 Q_D(Response);
229 return d->cookies.remove(name);
230}
231
232void Response::redirect(const QUrl &url, quint16 status)
233{
234 Q_D(Response);
235 d->location = url;
236 d->status = status;
237
238 if (url.isValid()) {
239 const auto location = url.toEncoded(QUrl::FullyEncoded);
240 qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
241
242 d->headers.setHeader("Location"_qba, location);
243 d->headers.setContentType("text/html; charset=utf-8"_qba);
244
245 const QByteArray buf = R"V0G0N(<!DOCTYPE html>
246<html xmlns="http://www.w3.org/1999/xhtml">
247 <head>
248 <title>Moved</title>
249 </head>
250 <body>
251 <p>This item has moved <a href=")V0G0N" +
252 location + R"V0G0N(">here</a>.</p>
253 </body>
254</html>
255)V0G0N";
256 setBody(buf);
257 } else {
258 d->headers.removeHeader("Location"_qba);
259 qCDebug(CUTELYST_RESPONSE) << "Invalid redirect removing header" << url << status;
260 }
261}
262
263void Response::redirect(const QString &url, quint16 status)
264{
265 redirect(QUrl(url), status);
266}
267
268void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
269{
270 Q_D(const Response);
271 if (url.matches(d->engineRequest->context->req()->uri(),
273 redirect(url);
274 } else {
275 redirect(fallback);
276 }
277}
278
279QUrl Response::location() const noexcept
280{
281 Q_D(const Response);
282 return d->location;
283}
284
285QByteArray Response::header(const QByteArray &field) const noexcept
286{
287 Q_D(const Response);
288 return d->headers.header(field);
289}
290
291void Response::setHeader(const QByteArray &key, const QByteArray &value)
292{
293 Q_D(Response);
294 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
295 "setHeader",
296 "setting a header value after finalize_headers and the response callback has been "
297 "called. Not what you want.");
298
299 d->headers.setHeader(key, value);
300}
301
302Headers &Response::headers() noexcept
303{
304 Q_D(Response);
305 return d->headers;
306}
307
308bool Response::isFinalizedHeaders() const noexcept
309{
310 Q_D(const Response);
311 return d->engineRequest->status & EngineRequest::FinalizedHeaders;
312}
313
314bool Response::isSequential() const noexcept
315{
316 return true;
317}
318
319qint64 Response::size() const noexcept
320{
321 Q_D(const Response);
322 if (d->engineRequest->status & EngineRequest::IOWrite) {
323 return -1;
324 } else if (d->bodyIODevice) {
325 return d->bodyIODevice->size();
326 } else {
327 return d->bodyData.size();
328 }
329}
330
332 const QByteArray &origin,
333 const QByteArray &protocol)
334{
335 Q_D(Response);
336 return d->engineRequest->webSocketHandshake(key, origin, protocol);
337}
338
339bool Response::webSocketTextMessage(const QString &message)
340{
341 Q_D(Response);
342 return d->engineRequest->webSocketSendTextMessage(message);
343}
344
346{
347 Q_D(Response);
348 return d->engineRequest->webSocketSendBinaryMessage(message);
349}
350
351bool Response::webSocketPing(const QByteArray &payload)
352{
353 Q_D(Response);
354 return d->engineRequest->webSocketSendPing(payload);
355}
356
357bool Response::webSocketClose(quint16 code, const QString &reason)
358{
359 Q_D(Response);
360 return d->engineRequest->webSocketClose(code, reason);
361}
362
363void ResponsePrivate::setBodyData(const QByteArray &body)
364{
365 if (!(engineRequest->status & EngineRequest::IOWrite)) {
366 if (bodyIODevice) {
367 delete bodyIODevice;
368 bodyIODevice = nullptr;
369 }
370 bodyData = body;
371 // Content-Length is set at finalizeHeaders()
372 // because & ::body() reference might change it
373 }
374}
375
376#include "moc_response.cpp"
Container for HTTP headers.
Definition headers.h:24
A Cutelyst response.
Definition response.h:29
qint64 size() const noexcept override
QByteArray header(const QByteArray &field) const noexcept
void redirect(const QUrl &url, quint16 status=Found)
Definition response.cpp:232
QVariant cookie(const QByteArray &name) const
Definition response.cpp:200
qint64 contentLength() const
Definition response.cpp:171
bool webSocketHandshake(const QByteArray &key={}, const QByteArray &origin={}, const QByteArray &protocol={})
bool hasBody() const noexcept
Definition response.cpp:78
bool webSocketTextMessage(const QString &message)
void setStatus(quint16 status) noexcept
Definition response.cpp:72
bool webSocketPing(const QByteArray &payload={})
void setHeader(const QByteArray &key, const QByteArray &value)
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition response.cpp:19
QByteArray contentEncoding() const noexcept
Definition response.cpp:154
QByteArray contentTypeCharset() const
Definition response.cpp:194
void setBody(QIODevice *body)
Definition response.cpp:103
void setJsonArrayBody(const QJsonArray &array)
Definition response.cpp:149
void redirectSafe(const QUrl &url, const QUrl &fallback)
Headers & headers() noexcept
bool isSequential() const noexcept override
void setCookies(const QList< QNetworkCookie > &cookies)
Definition response.cpp:218
virtual qint64 writeData(const char *data, qint64 len) override
Definition response.cpp:32
QList< QNetworkCookie > cookies() const
Definition response.cpp:206
bool webSocketBinaryMessage(const QByteArray &message)
bool isFinalizedHeaders() const noexcept
void setCborValueBody(const QCborValue &value)
Definition response.cpp:132
void setContentLength(qint64 length)
Definition response.cpp:177
int removeCookies(const QByteArray &name)
Definition response.cpp:226
void setJsonObjectBody(const QJsonObject &obj)
Definition response.cpp:144
virtual qint64 readData(char *data, qint64 maxlen) override
Definition response.cpp:25
QIODevice * bodyDevice() const noexcept
Definition response.cpp:97
void setCborBody(const QByteArray &cbor)
Definition response.cpp:125
QByteArray & body()
Definition response.cpp:85
void setJsonBody(QStringView json)
Definition response.h:440
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
quint16 status() const noexcept
Definition response.cpp:66
void setCookie(const QNetworkCookie &cookie)
Definition response.cpp:212
QUrl location() const noexcept
void setContentEncoding(const QByteArray &encoding)
Definition response.cpp:160
QByteArray contentType() const
Definition response.cpp:188
virtual ~Response() override
Definition response.cpp:60
The Cutelyst namespace holds all public Cutelyst API.
virtual bool open(QIODeviceBase::OpenMode mode)
FullyEncoded
bool isValid() const const
bool matches(const QUrl &url, QUrl::FormattingOptions options) const const
QByteArray toEncoded(QUrl::FormattingOptions options) const const
QVariant fromValue(const T &value)