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 
17 using namespace Cutelyst;
18 using namespace Qt::Literals::StringLiterals;
19 
20 Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
21  : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
22 {
24 }
25 
26 qint64 Response::readData(char *data, qint64 maxlen)
27 {
28  Q_UNUSED(data)
29  Q_UNUSED(maxlen)
30  return -1;
31 }
32 
33 qint64 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 
67 quint16 Response::status() const noexcept
68 {
69  Q_D(const Response);
70  return d->status;
71 }
72 
73 void Response::setStatus(quint16 status) noexcept
74 {
75  Q_D(Response);
76  d->status = status;
77 }
78 
79 bool 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 
120 void Response::setBody(const QByteArray &body)
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 
146 {
148 }
149 
151 {
153 }
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 
178 void 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 
233 void 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 
264 void Response::redirect(const QString &url, quint16 status)
265 {
266  redirect(QUrl(url), status);
267 }
268 
269 void 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);
277  }
278 }
279 
280 QUrl Response::location() const noexcept
281 {
282  Q_D(const Response);
283  return d->location;
284 }
285 
286 QByteArray Response::header(const QByteArray &field) const noexcept
287 {
288  Q_D(const Response);
289  return d->headers.header(field);
290 }
291 
292 void Response::setHeader(const QByteArray &key, const QByteArray &value)
293 {
294  Q_D(Response);
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 
303 Headers &Response::headers() noexcept
304 {
305  Q_D(Response);
306  return d->headers;
307 }
308 
309 bool Response::isFinalizedHeaders() const noexcept
310 {
311  Q_D(const Response);
312  return d->engineRequest->status & EngineRequest::FinalizedHeaders;
313 }
314 
315 bool Response::isSequential() const noexcept
316 {
317  return true;
318 }
319 
320 qint64 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 }
331 
333  const QByteArray &origin,
334  const QByteArray &protocol)
335 {
336  Q_D(Response);
337  return d->engineRequest->webSocketHandshake(key, origin, protocol);
338 }
339 
340 bool Response::webSocketTextMessage(const QString &message)
341 {
342  Q_D(Response);
343  return d->engineRequest->webSocketSendTextMessage(message);
344 }
345 
346 bool Response::webSocketBinaryMessage(const QByteArray &message)
347 {
348  Q_D(Response);
349  return d->engineRequest->webSocketSendBinaryMessage(message);
350 }
351 
352 bool Response::webSocketPing(const QByteArray &payload)
353 {
354  Q_D(Response);
355  return d->engineRequest->webSocketSendPing(payload);
356 }
357 
358 bool Response::webSocketClose(quint16 code, const QString &reason)
359 {
360  Q_D(Response);
361  return d->engineRequest->webSocketClose(code, reason);
362 }
363 
364 void 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"
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:213
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:201
FullyEncoded
Headers & headers() noexcept
void setContentLength(qint64 length)
Definition: response.cpp:178
QByteArray contentTypeCharset() const
Definition: response.cpp:195
QVariant fromValue(T &&value)
void setJsonBody(QStringView json)
Definition: response.h:440
Container for HTTP headers.
Definition: headers.h:23
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:20
bool webSocketPing(const QByteArray &payload={})
void setCborValueBody(const QCborValue &value)
Definition: response.cpp:133
qint64 size() const noexcept override
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
virtual bool open(QIODeviceBase::OpenMode mode)
QIODevice * bodyDevice() const noexcept
Definition: response.cpp:98
A Cutelyst response.
Definition: response.h:28
bool webSocketTextMessage(const QString &message)
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:26
bool hasBody() const noexcept
Definition: response.cpp:79
QByteArray header(const QByteArray &field) const noexcept
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:233
int removeCookies(const QByteArray &name)
Definition: response.cpp:227
virtual ~Response() override
Definition: response.cpp:61
void setJsonObjectBody(const QJsonObject &obj)
Definition: response.cpp:145
QList< QNetworkCookie > cookies() const
Definition: response.cpp:207
QUrl location() const noexcept
bool webSocketBinaryMessage(const QByteArray &message)
The Cutelyst namespace holds all public Cutelyst API.
void setCborBody(const QByteArray &cbor)
Definition: response.cpp:126
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:219
quint16 status() const noexcept
Definition: response.cpp:67
QByteArray contentType() const
Definition: response.cpp:189
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:33
bool isSequential() const noexcept override
bool webSocketHandshake(const QByteArray &key={}, const QByteArray &origin={}, const QByteArray &protocol={})
bool isValid() const const
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:150
QByteArray & body()
Definition: response.cpp:86
bool matches(const QUrl &url, FormattingOptions options) const const
void setBody(QIODevice *body)
Definition: response.cpp:104
bool isFinalizedHeaders() const noexcept
void redirectSafe(const QUrl &url, const QUrl &fallback)
void setContentEncoding(const QByteArray &encoding)
Definition: response.cpp:161
void setStatus(quint16 status) noexcept
Definition: response.cpp:73
QByteArray toEncoded(FormattingOptions options) const const
void setHeader(const QByteArray &key, const QByteArray &value)
qint64 contentLength() const
Definition: response.cpp:172
QByteArray contentEncoding() const noexcept
Definition: response.cpp:155