cutelyst  4.4.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 
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("Transfer-Encoding"_qba).compare("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("Connection"_qba, "Close"_qba);
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 
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 ||
82  d->engineRequest->status & EngineRequest::IOWrite;
83 }
84 
86 {
87  Q_D(Response);
88  if (d->bodyIODevice) {
89  delete d->bodyIODevice;
90  d->bodyIODevice = nullptr;
91  }
92  // Content-Length is set at finalizeHeaders() as we can't know it here
93 
94  return d->bodyData;
95 }
96 
98 {
99  Q_D(const Response);
100  return d->bodyIODevice;
101 }
102 
104 {
105  Q_D(Response);
106  Q_ASSERT(body && body->isOpen() && body->isReadable());
107 
108  if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
109  d->bodyData = QByteArray();
110  if (d->bodyIODevice) {
111  delete d->bodyIODevice;
112  }
113  d->bodyIODevice = body;
114  // Content-Length is set at finalizeHeaders()
115  // because & ::body() reference might change it
116  }
117 }
118 
119 void Response::setBody(const QByteArray &body)
120 {
121  Q_D(Response);
122  d->setBodyData(body);
123 }
124 
126 {
127  Q_D(Response);
128  d->setBodyData(cbor);
129  d->headers.setContentType("application/cbor"_qba);
130 }
131 
133 {
134  setCborBody(value.toCbor());
135 }
136 
138 {
139  Q_D(Response);
140  d->setBodyData(json);
141  d->headers.setContentType("application/json"_qba);
142 }
143 
145 {
147 }
148 
150 {
152 }
153 
155 {
156  Q_D(const Response);
157  return d->headers.contentEncoding();
158 }
159 
161 {
162  Q_D(Response);
163  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
164  "setContentEncoding",
165  "setting a header value after finalize_headers and the response callback has been "
166  "called. Not what you want.");
167 
168  d->headers.setContentEncoding(encoding);
169 }
170 
172 {
173  Q_D(const Response);
174  return d->headers.contentLength();
175 }
176 
177 void Response::setContentLength(qint64 length)
178 {
179  Q_D(Response);
180  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
181  "setContentLength",
182  "setting a header value after finalize_headers and the response callback has been "
183  "called. Not what you want.");
184 
185  d->headers.setContentLength(length);
186 }
187 
189 {
190  Q_D(const Response);
191  return d->headers.contentType();
192 }
193 
195 {
196  Q_D(const Response);
197  return d->headers.contentTypeCharset();
198 }
199 
201 {
202  Q_D(const Response);
203  return QVariant::fromValue(d->cookies.value(name));
204 }
205 
207 {
208  Q_D(const Response);
209  return d->cookies.values();
210 }
211 
213 {
214  Q_D(Response);
215  d->cookies.insert(cookie.name(), cookie);
216 }
217 
219 {
220  Q_D(Response);
221  for (const QNetworkCookie &cookie : cookies) {
222  d->cookies.insert(cookie.name(), cookie);
223  }
224 }
225 
227 {
228  Q_D(Response);
229  return d->cookies.remove(name);
230 }
231 
232 void Response::redirect(const QUrl &url, quint16 status)
233 {
234  Q_D(Response);
235  d->location = url;
236  d->status = status;
237 
238  if (url.isValid()) {
239  const auto location = url.toEncoded(QUrl::FullyEncoded);
240  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
241 
242  d->headers.setHeader("Location"_qba, location);
243  d->headers.setContentType("text/html; charset=utf-8"_qba);
244 
245  const QByteArray buf = R"V0G0N(<!DOCTYPE html>
246 <html xmlns="http://www.w3.org/1999/xhtml">
247  <head>
248  <title>Moved</title>
249  </head>
250  <body>
251  <p>This item has moved <a href=")V0G0N" +
252  location + R"V0G0N(">here</a>.</p>
253  </body>
254 </html>
255 )V0G0N";
256  setBody(buf);
257  } else {
258  d->headers.removeHeader("Location"_qba);
259  qCDebug(CUTELYST_RESPONSE) << "Invalid redirect removing header" << url << status;
260  }
261 }
262 
263 void Response::redirect(const QString &url, quint16 status)
264 {
265  redirect(QUrl(url), status);
266 }
267 
268 void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
269 {
270  Q_D(const Response);
271  if (url.matches(d->engineRequest->context->req()->uri(),
273  redirect(url);
274  } else {
275  redirect(fallback);
276  }
277 }
278 
279 QUrl Response::location() const noexcept
280 {
281  Q_D(const Response);
282  return d->location;
283 }
284 
285 QByteArray Response::header(const QByteArray &field) const noexcept
286 {
287  Q_D(const Response);
288  return d->headers.header(field);
289 }
290 
291 void Response::setHeader(const QByteArray &key, const QByteArray &value)
292 {
293  Q_D(Response);
294  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
295  "setHeader",
296  "setting a header value after finalize_headers and the response callback has been "
297  "called. Not what you want.");
298 
299  d->headers.setHeader(key, value);
300 }
301 
302 Headers &Response::headers() noexcept
303 {
304  Q_D(Response);
305  return d->headers;
306 }
307 
308 bool Response::isFinalizedHeaders() const noexcept
309 {
310  Q_D(const Response);
311  return d->engineRequest->status & EngineRequest::FinalizedHeaders;
312 }
313 
314 bool Response::isSequential() const noexcept
315 {
316  return true;
317 }
318 
319 qint64 Response::size() const noexcept
320 {
321  Q_D(const Response);
322  if (d->engineRequest->status & EngineRequest::IOWrite) {
323  return -1;
324  } else if (d->bodyIODevice) {
325  return d->bodyIODevice->size();
326  } else {
327  return d->bodyData.size();
328  }
329 }
330 
332  const QByteArray &origin,
333  const QByteArray &protocol)
334 {
335  Q_D(Response);
336  return d->engineRequest->webSocketHandshake(key, origin, protocol);
337 }
338 
339 bool Response::webSocketTextMessage(const QString &message)
340 {
341  Q_D(Response);
342  return d->engineRequest->webSocketSendTextMessage(message);
343 }
344 
345 bool Response::webSocketBinaryMessage(const QByteArray &message)
346 {
347  Q_D(Response);
348  return d->engineRequest->webSocketSendBinaryMessage(message);
349 }
350 
351 bool Response::webSocketPing(const QByteArray &payload)
352 {
353  Q_D(Response);
354  return d->engineRequest->webSocketSendPing(payload);
355 }
356 
357 bool Response::webSocketClose(quint16 code, const QString &reason)
358 {
359  Q_D(Response);
360  return d->engineRequest->webSocketClose(code, reason);
361 }
362 
363 void ResponsePrivate::setBodyData(const QByteArray &body)
364 {
365  if (!(engineRequest->status & EngineRequest::IOWrite)) {
366  if (bodyIODevice) {
367  delete bodyIODevice;
368  bodyIODevice = nullptr;
369  }
370  bodyData = body;
371  // Content-Length is set at finalizeHeaders()
372  // because & ::body() reference might change it
373  }
374 }
375 
376 #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:232
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:200
qint64 contentLength() const
Definition: response.cpp:171
bool webSocketHandshake(const QByteArray &key={}, const QByteArray &origin={}, const QByteArray &protocol={})
bool hasBody() const noexcept
Definition: response.cpp:78
bool webSocketTextMessage(const QString &message)
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
bool webSocketPing(const QByteArray &payload={})
void setHeader(const QByteArray &key, const QByteArray &value)
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:19
Headers & headers() noexcept
QByteArray contentEncoding() const noexcept
Definition: response.cpp:154
QByteArray contentTypeCharset() const
Definition: response.cpp:194
void setBody(QIODevice *body)
Definition: response.cpp:103
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:149
void redirectSafe(const QUrl &url, const QUrl &fallback)
bool isSequential() const noexcept override
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:218
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:32
QList< QNetworkCookie > cookies() const
Definition: response.cpp:206
bool webSocketBinaryMessage(const QByteArray &message)
bool isFinalizedHeaders() const noexcept
void setCborValueBody(const QCborValue &value)
Definition: response.cpp:132
void setContentLength(qint64 length)
Definition: response.cpp:177
int removeCookies(const QByteArray &name)
Definition: response.cpp:226
void setJsonObjectBody(const QJsonObject &obj)
Definition: response.cpp:144
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:25
QIODevice * bodyDevice() const noexcept
Definition: response.cpp:97
void setCborBody(const QByteArray &cbor)
Definition: response.cpp:125
QByteArray & body()
Definition: response.cpp:85
void setJsonBody(QStringView json)
Definition: response.h:440
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason={})
quint16 status() const noexcept
Definition: response.cpp:66
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:212
QUrl location() const noexcept
void setContentEncoding(const QByteArray &encoding)
Definition: response.cpp:160
QByteArray contentType() const
Definition: response.cpp:188
virtual ~Response() override
Definition: response.cpp:60
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)