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