cutelyst  5.0.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 <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").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 
68 quint16 Response::status() const noexcept
69 {
70  Q_D(const Response);
71  return d->status;
72 }
73 
74 void Response::setStatus(quint16 status) noexcept
75 {
76  Q_D(Response);
77  d->status = status;
78 }
79 
80 bool 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 
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 
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 &cookieItem : cookies) {
212  d->cookies.insert(cookieItem.name(), cookieItem);
213  }
214 }
215 
217 {
218  Q_D(Response);
219  return d->cookies.remove(name);
220 }
221 
222 void 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 locationRedirect = url.toEncoded(QUrl::FullyEncoded);
230  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << locationRedirect << status;
231 
232  d->headers.setHeader("Location"_ba, locationRedirect);
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  locationRedirect + 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 
253 void Response::redirect(const QString &url, quint16 status)
254 {
255  redirect(QUrl(url), status);
256 }
257 
258 void 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 
269 QUrl Response::location() const noexcept
270 {
271  Q_D(const Response);
272  return d->location;
273 }
274 
275 QByteArray Response::header(const QByteArray &field) const noexcept
276 {
277  Q_D(const Response);
278  return d->headers.header(field);
279 }
280 
281 void 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 
292 Headers &Response::headers() noexcept
293 {
294  Q_D(Response);
295  return d->headers;
296 }
297 
298 bool Response::isFinalized() const noexcept
299 {
300  Q_D(const Response);
301  return d->engineRequest->status & EngineRequest::Finalized;
302 }
303 
304 bool Response::isFinalizedHeaders() const noexcept
305 {
306  Q_D(const Response);
307  return d->engineRequest->status & EngineRequest::FinalizedHeaders;
308 }
309 
310 bool Response::isSequential() const noexcept
311 {
312  return true;
313 }
314 
315 qint64 Response::size() const noexcept
316 {
317  Q_D(const Response);
318  if (d->engineRequest->status & EngineRequest::IOWrite) {
319  return -1;
320  } else if (d->bodyIODevice) {
321  return d->bodyIODevice->size();
322  } else {
323  return d->bodyData.size();
324  }
325 }
326 
328  const QByteArray &origin,
329  const QByteArray &protocol)
330 {
331  Q_D(Response);
332  return d->engineRequest->webSocketHandshake(key, origin, protocol);
333 }
334 
335 bool Response::webSocketTextMessage(const QString &message)
336 {
337  Q_D(Response);
338  return d->engineRequest->webSocketSendTextMessage(message);
339 }
340 
341 bool Response::webSocketBinaryMessage(const QByteArray &message)
342 {
343  Q_D(Response);
344  return d->engineRequest->webSocketSendBinaryMessage(message);
345 }
346 
347 bool Response::webSocketPing(const QByteArray &payload)
348 {
349  Q_D(Response);
350  return d->engineRequest->webSocketSendPing(payload);
351 }
352 
353 bool Response::webSocketClose(quint16 code, const QString &reason)
354 {
355  Q_D(Response);
356  return d->engineRequest->webSocketClose(code, reason);
357 }
358 
359 void ResponsePrivate::setBodyData(const QByteArray &body)
360 {
361  if (!(engineRequest->status & EngineRequest::IOWrite)) {
362  delete bodyIODevice;
363  bodyIODevice = nullptr;
364  bodyData = body;
365  // Content-Length is set at finalizeHeaders()
366  // because & ::body() reference might change it
367  }
368 }
369 
370 #include "moc_response.cpp"
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:202
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:190
FullyEncoded
Headers & headers() noexcept
QByteArray contentTypeCharset() const
Definition: response.cpp:184
QVariant fromValue(T &&value)
void setJsonBody(QStringView json)
Definition: response.h:438
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:99
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:80
QByteArray header(const QByteArray &field) const noexcept
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:222
int removeCookies(const QByteArray &name)
Definition: response.cpp:216
bool isFinalized() const noexcept
virtual ~Response() override
Definition: response.cpp:62
void setJsonObjectBody(const QJsonObject &obj)
Definition: response.cpp:145
QList< QNetworkCookie > cookies() const
Definition: response.cpp:196
QByteArray toCbor(EncodingOptions opt) const const
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:208
quint16 status() const noexcept
Definition: response.cpp:68
QByteArray contentType() const
Definition: response.cpp:178
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:87
bool matches(const QUrl &url, FormattingOptions options) const const
void setBody(QIODevice *body)
Definition: response.cpp:105
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:74
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