cutelyst 5.0.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").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
52 delete d->bodyIODevice;
53 d->bodyIODevice = nullptr;
54 d->bodyData = QByteArray();
55
56 d->engineRequest->finalizeHeaders();
57 }
58
59 return d->engineRequest->write(data, len);
60}
61
63{
64 delete d_ptr->bodyIODevice;
65 delete d_ptr;
66}
67
68quint16 Response::status() const noexcept
69{
70 Q_D(const Response);
71 return d->status;
72}
73
74void Response::setStatus(quint16 status) noexcept
75{
76 Q_D(Response);
77 d->status = status;
78}
79
80bool Response::hasBody() const noexcept
81{
82 Q_D(const Response);
83 return !d->bodyData.isEmpty() || d->bodyIODevice ||
84 d->engineRequest->status & EngineRequest::IOWrite;
85}
86
88{
89 Q_D(Response);
90
91 delete d->bodyIODevice;
92 d->bodyIODevice = nullptr;
93
94 // Content-Length is set at finalizeHeaders() as we can't know it here
95
96 return d->bodyData;
97}
98
100{
101 Q_D(const Response);
102 return d->bodyIODevice;
103}
104
106{
107 Q_D(Response);
108 Q_ASSERT(body && body->isOpen() && body->isReadable());
109
110 if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
111 d->bodyData = {};
112
113 delete d->bodyIODevice;
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
179{
180 Q_D(const Response);
181 return d->headers.contentType();
182}
183
185{
186 Q_D(const Response);
187 return d->headers.contentTypeCharset();
188}
189
191{
192 Q_D(const Response);
193 return QVariant::fromValue(d->cookies.value(name));
194}
195
197{
198 Q_D(const Response);
199 return d->cookies.values();
200}
201
203{
204 Q_D(Response);
205 d->cookies.insert(cookie.name(), cookie);
206}
207
209{
210 Q_D(Response);
211 for (const QNetworkCookie &cookie : cookies) {
212 d->cookies.insert(cookie.name(), cookie);
213 }
214}
215
217{
218 Q_D(Response);
219 return d->cookies.remove(name);
220}
221
222void Response::redirect(const QUrl &url, quint16 status)
223{
224 Q_D(Response);
225 d->location = url;
226 d->status = status;
227
228 if (url.isValid()) {
229 const auto location = url.toEncoded(QUrl::FullyEncoded);
230 qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
231
232 d->headers.setHeader("Location"_ba, location);
233 d->headers.setContentType("text/html; charset=utf-8"_ba);
234
235 const QByteArray buf = R"V0G0N(<!DOCTYPE html>
236<html xmlns="http://www.w3.org/1999/xhtml">
237 <head>
238 <title>Moved</title>
239 </head>
240 <body>
241 <p>This item has moved <a href=")V0G0N" +
242 location + R"V0G0N(">here</a>.</p>
243 </body>
244</html>
245)V0G0N";
246 setBody(buf);
247 } else {
248 d->headers.removeHeader("Location"_ba);
249 qCDebug(CUTELYST_RESPONSE) << "Invalid redirect removing header" << url << status;
250 }
251}
252
253void Response::redirect(const QString &url, quint16 status)
254{
255 redirect(QUrl(url), status);
256}
257
258void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
259{
260 Q_D(const Response);
261 if (url.matches(d->engineRequest->context->req()->uri(),
263 redirect(url);
264 } else {
265 redirect(fallback);
266 }
267}
268
269QUrl Response::location() const noexcept
270{
271 Q_D(const Response);
272 return d->location;
273}
274
275QByteArray Response::header(const QByteArray &field) const noexcept
276{
277 Q_D(const Response);
278 return d->headers.header(field);
279}
280
281void Response::setHeader(const QByteArray &key, const QByteArray &value)
282{
283 Q_D(Response);
284 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
285 "setHeader",
286 "setting a header value after finalize_headers and the response callback has been "
287 "called. Not what you want.");
288
289 d->headers.setHeader(key, value);
290}
291
292Headers &Response::headers() noexcept
293{
294 Q_D(Response);
295 return d->headers;
296}
297
298bool Response::isFinalizedHeaders() const noexcept
299{
300 Q_D(const Response);
301 return d->engineRequest->status & EngineRequest::FinalizedHeaders;
302}
303
304bool Response::isSequential() const noexcept
305{
306 return true;
307}
308
309qint64 Response::size() const noexcept
310{
311 Q_D(const Response);
312 if (d->engineRequest->status & EngineRequest::IOWrite) {
313 return -1;
314 } else if (d->bodyIODevice) {
315 return d->bodyIODevice->size();
316 } else {
317 return d->bodyData.size();
318 }
319}
320
322 const QByteArray &origin,
323 const QByteArray &protocol)
324{
325 Q_D(Response);
326 return d->engineRequest->webSocketHandshake(key, origin, protocol);
327}
328
329bool Response::webSocketTextMessage(const QString &message)
330{
331 Q_D(Response);
332 return d->engineRequest->webSocketSendTextMessage(message);
333}
334
336{
337 Q_D(Response);
338 return d->engineRequest->webSocketSendBinaryMessage(message);
339}
340
341bool Response::webSocketPing(const QByteArray &payload)
342{
343 Q_D(Response);
344 return d->engineRequest->webSocketSendPing(payload);
345}
346
347bool Response::webSocketClose(quint16 code, const QString &reason)
348{
349 Q_D(Response);
350 return d->engineRequest->webSocketClose(code, reason);
351}
352
353void ResponsePrivate::setBodyData(const QByteArray &body)
354{
355 if (!(engineRequest->status & EngineRequest::IOWrite)) {
356 delete bodyIODevice;
357 bodyIODevice = nullptr;
358 bodyData = body;
359 // Content-Length is set at finalizeHeaders()
360 // because & ::body() reference might change it
361 }
362}
363
364#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:222
QVariant cookie(const QByteArray &name) const
Definition response.cpp:190
qint64 contentLength() const
Definition response.cpp:172
bool webSocketHandshake(const QByteArray &key={}, const QByteArray &origin={}, const QByteArray &protocol={})
bool hasBody() const noexcept
Definition response.cpp:80
bool webSocketTextMessage(const QString &message)
void setStatus(quint16 status) noexcept
Definition response.cpp:74
bool webSocketPing(const QByteArray &payload={})
void setHeader(const QByteArray &key, const QByteArray &value)
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:184
void setBody(QIODevice *body)
Definition response.cpp:105
void setJsonArrayBody(const QJsonArray &array)
Definition response.cpp:150
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:208
virtual qint64 writeData(const char *data, qint64 len) override
Definition response.cpp:33
QList< QNetworkCookie > cookies() const
Definition response.cpp:196
bool webSocketBinaryMessage(const QByteArray &message)
bool isFinalizedHeaders() const noexcept
void setCborValueBody(const QCborValue &value)
Definition response.cpp:133
int removeCookies(const QByteArray &name)
Definition response.cpp:216
void setJsonObjectBody(const QJsonObject &obj)
Definition response.cpp:145
virtual qint64 readData(char *data, qint64 maxlen) override
Definition response.cpp:26
QIODevice * bodyDevice() const noexcept
Definition response.cpp:99
void setCborBody(const QByteArray &cbor)
Definition response.cpp:126
QByteArray & body()
Definition response.cpp:87
void setJsonBody(QStringView json)
Definition response.h:432
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
quint16 status() const noexcept
Definition response.cpp:68
void setCookie(const QNetworkCookie &cookie)
Definition response.cpp:202
QUrl location() const noexcept
void setContentEncoding(const QByteArray &encoding)
Definition response.cpp:161
QByteArray contentType() const
Definition response.cpp:178
virtual ~Response() override
Definition response.cpp:62
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)