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