cutelyst  3.9.1
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 <QtCore/QJsonDocument>
14 
15 using namespace Cutelyst;
16 
17 Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
18  : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
19 {
21 }
22 
23 qint64 Response::readData(char *data, qint64 maxlen)
24 {
25  Q_UNUSED(data)
26  Q_UNUSED(maxlen)
27  return -1;
28 }
29 
30 qint64 Response::writeData(const char *data, qint64 len)
31 {
32  Q_D(Response);
33 
34  if (len <= 0) {
35  return len;
36  }
37 
38  // Finalize headers if someone manually writes output
39  if (!(d->engineRequest->status & EngineRequest::FinalizedHeaders)) {
40  if (d->headers.header(QStringLiteral("TRANSFER_ENCODING")).compare(u"chunked") == 0) {
41  d->engineRequest->status |= EngineRequest::IOWrite | EngineRequest::Chunked;
42  } else {
43  // When chunked encoding is not set the client can only know
44  // that data is finished if we close the connection
45  d->headers.setHeader(QStringLiteral("CONNECTION"), QStringLiteral("close"));
46  d->engineRequest->status |= EngineRequest::IOWrite;
47  }
48  delete d->bodyIODevice;
49  d->bodyIODevice = nullptr;
50  d->bodyData = QByteArray();
51 
52  d->engineRequest->finalizeHeaders();
53  }
54 
55  return d->engineRequest->write(data, len);
56 }
57 
58 Response::~Response()
59 {
60  delete d_ptr->bodyIODevice;
61  delete d_ptr;
62 }
63 
64 quint16 Response::status() const noexcept
65 {
66  Q_D(const Response);
67  return d->status;
68 }
69 
70 void Response::setStatus(quint16 status) noexcept
71 {
72  Q_D(Response);
73  d->status = status;
74 }
75 
76 bool Response::hasBody() const noexcept
77 {
78  Q_D(const Response);
79  return !d->bodyData.isEmpty() || d->bodyIODevice ||
80  d->engineRequest->status & EngineRequest::IOWrite;
81 }
82 
84 {
85  Q_D(Response);
86  if (d->bodyIODevice) {
87  delete d->bodyIODevice;
88  d->bodyIODevice = nullptr;
89  }
90 
91  return d->bodyData;
92 }
93 
95 {
96  Q_D(const Response);
97  return d->bodyIODevice;
98 }
99 
101 {
102  Q_D(Response);
103  Q_ASSERT(body && body->isOpen() && body->isReadable());
104 
105  if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
106  d->bodyData = QByteArray();
107  if (d->bodyIODevice) {
108  delete d->bodyIODevice;
109  }
110  d->bodyIODevice = body;
111  }
112 }
113 
114 void Response::setBody(const QByteArray &body)
115 {
116  Q_D(Response);
117  d->setBodyData(body);
118 }
119 
120 void Response::setJsonBody(const QJsonDocument &documment)
121 {
122  Q_D(Response);
123  const QByteArray body = documment.toJson(QJsonDocument::Compact);
124  d->setBodyData(body);
125  d->headers.setContentType(QStringLiteral("application/json"));
126 }
127 
129 {
130  Q_D(Response);
131  d->setBodyData(json.toUtf8());
132  d->headers.setContentType(QStringLiteral("application/json"));
133 }
134 
136 {
137  Q_D(Response);
138  d->setBodyData(json);
139  d->headers.setContentType(QStringLiteral("application/json"));
140 }
141 
143 {
144  Q_D(Response);
146  d->setBodyData(body);
147  d->headers.setContentType(QStringLiteral("application/json"));
148 }
149 
151 {
152  Q_D(Response);
154  d->setBodyData(body);
155  d->headers.setContentType(QStringLiteral("application/json"));
156 }
157 
159 {
160  Q_D(const Response);
161  return d->headers.contentEncoding();
162 }
163 
165 {
166  Q_D(Response);
167  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
168  "setContentEncoding",
169  "setting a header value after finalize_headers and the response callback has been "
170  "called. Not what you want.");
171 
172  d->headers.setContentEncoding(encoding);
173 }
174 
176 {
177  Q_D(const Response);
178  return d->headers.contentLength();
179 }
180 
181 void Response::setContentLength(qint64 length)
182 {
183  Q_D(Response);
184  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
185  "setContentLength",
186  "setting a header value after finalize_headers and the response callback has been "
187  "called. Not what you want.");
188 
189  d->headers.setContentLength(length);
190 }
191 
193 {
194  Q_D(const Response);
195  return d->headers.contentType();
196 }
197 
199 {
200  Q_D(const Response);
201  return d->headers.contentTypeCharset();
202 }
203 
205 {
206  Q_D(const Response);
207  return QVariant::fromValue(d->cookies.value(name));
208 }
209 
210 #if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
211 QVariant Response::cuteCookie(const QByteArray &name) const
212 {
213  Q_D(const Response);
214  return QVariant::fromValue(d->cuteCookies.value(name));
215 }
216 #endif
217 
219 {
220  Q_D(const Response);
221  return d->cookies.values();
222 }
223 
224 #if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
225 QList<Cookie> Response::cuteCookies() const
226 {
227  Q_D(const Response);
228  return d->cuteCookies.values();
229 }
230 #endif
231 
233 {
234  Q_D(Response);
235  d->cookies.insert(cookie.name(), cookie);
236 }
237 
238 #if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
239 void Response::setCuteCookie(const Cookie &cookie)
240 {
241  Q_D(Response);
242  d->cuteCookies.insert(cookie.name(), cookie);
243 }
244 #endif
245 
247 {
248  Q_D(Response);
249  for (const QNetworkCookie &cookie : cookies) {
250  d->cookies.insert(cookie.name(), cookie);
251  }
252 }
253 
254 #if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
255 void Response::setCuteCookies(const QList<Cookie> &cookies)
256 {
257  Q_D(Response);
258  for (const Cookie &cookie : cookies) {
259  d->cuteCookies.insert(cookie.name(), cookie);
260  }
261 }
262 #endif
263 
265 {
266  Q_D(Response);
267  return d->cookies.remove(name);
268 }
269 
270 #if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0))
271 int Response::removeCuteCookies(const QByteArray &name)
272 {
273  Q_D(Response);
274  return d->cuteCookies.remove(name);
275 }
276 #endif
277 
278 void Response::redirect(const QUrl &url, quint16 status)
279 {
280  Q_D(Response);
281  d->location = url;
282  d->status = status;
283 
284  if (url.isValid()) {
286  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
287 
288  d->headers.setHeader(QStringLiteral("LOCATION"), location);
289  d->headers.setContentType(QStringLiteral("text/html; charset=utf-8"));
290 
291  const QString buf = QLatin1String(R"V0G0N(<!DOCTYPE html>
292 <html xmlns="http://www.w3.org/1999/xhtml">
293  <head>
294  <title>Moved</title>
295  </head>
296  <body>
297  <p>This item has moved <a href=")V0G0N") +
298  location + QLatin1String(R"V0G0N(">here</a>.</p>
299  </body>
300 </html>
301 )V0G0N");
302  setBody(buf.toLatin1());
303  } else {
304  d->headers.removeHeader(QStringLiteral("LOCATION"));
305  qCDebug(CUTELYST_ENGINE) << "Invalid redirect removing header" << url << status;
306  }
307 }
308 
309 void Response::redirect(const QString &url, quint16 status)
310 {
311  redirect(QUrl(url), status);
312 }
313 
314 void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
315 {
316  Q_D(const Response);
317  if (url.matches(d->engineRequest->context->req()->uri(),
319  redirect(url);
320  } else {
321  redirect(fallback);
322  }
323 }
324 
325 QUrl Response::location() const noexcept
326 {
327  Q_D(const Response);
328  return d->location;
329 }
330 
331 QString Response::header(const QString &field) const
332 {
333  Q_D(const Response);
334  return d->headers.header(field);
335 }
336 
337 void Response::setHeader(const QString &field, const QString &value)
338 {
339  Q_D(Response);
340  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
341  "setHeader",
342  "setting a header value after finalize_headers and the response callback has been "
343  "called. Not what you want.");
344 
345  d->headers.setHeader(field, value);
346 }
347 
348 Headers &Response::headers() noexcept
349 {
350  Q_D(Response);
351  return d->headers;
352 }
353 
354 bool Response::isFinalizedHeaders() const noexcept
355 {
356  Q_D(const Response);
357  return d->engineRequest->status & EngineRequest::FinalizedHeaders;
358 }
359 
360 bool Response::isSequential() const noexcept
361 {
362  return true;
363 }
364 
365 qint64 Response::size() const noexcept
366 {
367  Q_D(const Response);
368  if (d->engineRequest->status & EngineRequest::IOWrite) {
369  return -1;
370  } else if (d->bodyIODevice) {
371  return d->bodyIODevice->size();
372  } else {
373  return d->bodyData.size();
374  }
375 }
376 
377 bool Response::webSocketHandshake(const QString &key,
378  const QString &origin,
379  const QString &protocol)
380 {
381  Q_D(Response);
382  return d->engineRequest->webSocketHandshake(key, origin, protocol);
383 }
384 
385 bool Response::webSocketTextMessage(const QString &message)
386 {
387  Q_D(Response);
388  return d->engineRequest->webSocketSendTextMessage(message);
389 }
390 
391 bool Response::webSocketBinaryMessage(const QByteArray &message)
392 {
393  Q_D(Response);
394  return d->engineRequest->webSocketSendBinaryMessage(message);
395 }
396 
397 bool Response::webSocketPing(const QByteArray &payload)
398 {
399  Q_D(Response);
400  return d->engineRequest->webSocketSendPing(payload);
401 }
402 
403 bool Response::webSocketClose(quint16 code, const QString &reason)
404 {
405  Q_D(Response);
406  return d->engineRequest->webSocketClose(code, reason);
407 }
408 
409 void ResponsePrivate::setBodyData(const QByteArray &body)
410 {
411  if (!(engineRequest->status & EngineRequest::IOWrite)) {
412  if (bodyIODevice) {
413  delete bodyIODevice;
414  bodyIODevice = nullptr;
415  }
416  bodyData = body;
417  headers.setContentLength(body.size());
418  }
419 }
420 
421 #include "moc_response.cpp"
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:232
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:204
FullyEncoded
QByteArray toJson() const const
void setHeader(const QString &field, const QString &value)
Headers & headers() noexcept
void setContentLength(qint64 length)
Definition: response.cpp:181
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:17
bool webSocketPing(const QByteArray &payload={})
Sends a WebSocket ping with an optional payload limited to 125 bytes, which will be truncated if larg...
void setJsonBody(const QJsonDocument &documment)
Definition: response.cpp:120
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
Sends a WebSocket close frame, with both optional close code and a string reason. ...
virtual bool open(QIODevice::OpenMode mode)
virtual bool isSequential() const noexcept override
bool webSocketTextMessage(const QString &message)
Sends a WebSocket text message.
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:23
void setContentEncoding(const QString &encoding)
Definition: response.cpp:164
bool hasBody() const noexcept
Definition: response.cpp:76
QString contentEncoding() const
Definition: response.cpp:158
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:278
int removeCookies(const QByteArray &name)
Definition: response.cpp:264
QList< QNetworkCookie > cookies() const
Definition: response.cpp:218
QUrl location() const noexcept
bool webSocketBinaryMessage(const QByteArray &message)
Sends a WebSocket binary message.
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
QString header(const QString &field) const
virtual qint64 size() const noexcept override
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:246
quint16 status() const noexcept
Definition: response.cpp:64
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:30
QVariant fromValue(const T &value)
QString contentType() const
Definition: response.cpp:192
bool isValid() const const
QByteArray toLatin1() const const
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:150
bool webSocketHandshake(const QString &key={}, const QString &origin={}, const QString &protocol={})
Sends the websocket handshake, if no parameters are defined it will use header data.
Q_REQUIRED_RESULT QByteArray & body()
Definition: response.cpp:83
QString fromLatin1(const char *str, int size)
bool matches(const QUrl &url, QUrl::FormattingOptions options) const const
QString contentTypeCharset() const
Definition: response.cpp:198
void setBody(QIODevice *body)
Definition: response.cpp:100
bool isFinalizedHeaders() const noexcept
int size() const const
void redirectSafe(const QUrl &url, const QUrl &fallback)
void setStatus(quint16 status) noexcept
Definition: response.cpp:70
The Cutelyst Cookie.
Definition: cookie.h:28
QByteArray toEncoded(QUrl::FormattingOptions options) const const
QIODevice * bodyDevice() const
Definition: response.cpp:94
qint64 contentLength() const
Definition: response.cpp:175
void setJsonObjectBody(const QJsonObject &object)
Definition: response.cpp:142
QByteArray toUtf8() const const