Cutelyst  2.13.0
response.cpp
1 /*
2  * Copyright (C) 2013-2018 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "response_p.h"
19 
20 #include "context_p.h"
21 #include "engine.h"
22 #include "enginerequest.h"
23 #include "common.h"
24 
25 #include <QtCore/QJsonDocument>
26 
27 #include <QCryptographicHash>
28 #include <QEventLoop>
29 
30 using namespace Cutelyst;
31 
32 Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
33  : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
34 {
35  open(QIODevice::WriteOnly);
36 }
37 
38 qint64 Response::readData(char *data, qint64 maxlen)
39 {
40  Q_UNUSED(data)
41  Q_UNUSED(maxlen)
42  return -1;
43 }
44 
45 qint64 Response::writeData(const char *data, qint64 len)
46 {
47  Q_D(Response);
48 
49  if (len <= 0) {
50  return len;
51  }
52 
53  // Finalize headers if someone manually writes output
54  if (!(d->engineRequest->status & EngineRequest::FinalizedHeaders)) {
55  if (d->headers.header(QStringLiteral("TRANSFER_ENCODING")) == QLatin1String("chunked")) {
56  d->engineRequest->status |= EngineRequest::IOWrite | EngineRequest::Chunked;
57  } else {
58  // When chunked encoding is not set the client can only know
59  // that data is finished if we close the connection
60  d->headers.setHeader(QStringLiteral("CONNECTION"), QStringLiteral("close"));
61  d->engineRequest->status |= EngineRequest::IOWrite;
62  }
63  delete d->bodyIODevice;
64  d->bodyIODevice = nullptr;
65  d->bodyData = QByteArray();
66 
67  d->engineRequest->finalizeHeaders();
68  }
69 
70  return d->engineRequest->write(data, len);
71 }
72 
73 Response::~Response()
74 {
75  delete d_ptr->bodyIODevice;
76  delete d_ptr;
77 }
78 
79 quint16 Response::status() const
80 {
81  Q_D(const Response);
82  return d->status;
83 }
84 
85 void Response::setStatus(quint16 status)
86 {
87  Q_D(Response);
88  d->status = status;
89 }
90 
91 bool Response::hasBody() const
92 {
93  Q_D(const Response);
94  return !d->bodyData.isEmpty() || d->bodyIODevice || d->engineRequest->status & EngineRequest::IOWrite;
95 }
96 
97 QByteArray &Response::body()
98 {
99  Q_D(Response);
100  if (d->bodyIODevice) {
101  delete d->bodyIODevice;
102  d->bodyIODevice = nullptr;
103  }
104 
105  return d->bodyData;
106 }
107 
108 QIODevice *Response::bodyDevice() const
109 {
110  Q_D(const Response);
111  return d->bodyIODevice;
112 }
113 
114 void Response::setBody(QIODevice *body)
115 {
116  Q_D(Response);
117  Q_ASSERT(body && body->isOpen() && body->isReadable());
118 
119  if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
120  d->bodyData = QByteArray();
121  if (d->bodyIODevice) {
122  delete d->bodyIODevice;
123  }
124  d->bodyIODevice = body;
125  }
126 }
127 
128 void Response::setBody(const QByteArray &body)
129 {
130  Q_D(Response);
131  d->setBodyData(body);
132 }
133 
134 void Response::setJsonBody(const QJsonDocument &documment)
135 {
136  Q_D(Response);
137  const QByteArray body = documment.toJson(QJsonDocument::Compact);
138  d->setBodyData(body);
139  d->headers.setContentType(QStringLiteral("application/json"));
140 }
141 
142 void Response::setJsonBody(const QString &json)
143 {
144  Q_D(Response);
145  d->setBodyData(json.toUtf8());
146  d->headers.setContentType(QStringLiteral("application/json"));
147 }
148 
149 void Response::setJsonBody(const QByteArray &json)
150 {
151  Q_D(Response);
152  d->setBodyData(json);
153  d->headers.setContentType(QStringLiteral("application/json"));
154 }
155 
156 void Response::setJsonObjectBody(const QJsonObject &object)
157 {
158  Q_D(Response);
159  const QByteArray body = QJsonDocument(object).toJson(QJsonDocument::Compact);
160  d->setBodyData(body);
161  d->headers.setContentType(QStringLiteral("application/json"));
162 }
163 
164 void Response::setJsonArrayBody(const QJsonArray &array)
165 {
166  Q_D(Response);
167  const QByteArray body = QJsonDocument(array).toJson(QJsonDocument::Compact);
168  d->setBodyData(body);
169  d->headers.setContentType(QStringLiteral("application/json"));
170 }
171 
173 {
174  Q_D(const Response);
175  return d->headers.contentEncoding();
176 }
177 
178 void Cutelyst::Response::setContentEncoding(const QString &encoding)
179 {
180  Q_D(Response);
181  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
182  "setContentEncoding",
183  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
184 
185  d->headers.setContentEncoding(encoding);
186 }
187 
189 {
190  Q_D(const Response);
191  return d->headers.contentLength();
192 }
193 
194 void Response::setContentLength(qint64 length)
195 {
196  Q_D(Response);
197  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
198  "setContentLength",
199  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
200 
201  d->headers.setContentLength(length);
202 }
203 
204 QString Response::contentType() const
205 {
206  Q_D(const Response);
207  return d->headers.contentType();
208 }
209 
211 {
212  Q_D(const Response);
213  return d->headers.contentTypeCharset();
214 }
215 
216 QVariant Response::cookie(const QByteArray &name) const
217 {
218  Q_D(const Response);
219  return QVariant::fromValue(d->cookies.value(name));
220 }
221 
222 QList<QNetworkCookie> Response::cookies() const
223 {
224  Q_D(const Response);
225  return d->cookies.values();
226 }
227 
228 void Response::setCookie(const QNetworkCookie &cookie)
229 {
230  Q_D(Response);
231  d->cookies.insert(cookie.name(), cookie);
232 }
233 
234 void Response::setCookies(const QList<QNetworkCookie> &cookies)
235 {
236  Q_D(Response);
237  for (const QNetworkCookie &cookie : cookies) {
238  d->cookies.insert(cookie.name(), cookie);
239  }
240 }
241 
242 int Response::removeCookies(const QByteArray &name)
243 {
244  Q_D(Response);
245  return d->cookies.remove(name);
246 }
247 
248 void Response::redirect(const QUrl &url, quint16 status)
249 {
250  Q_D(Response);
251  d->location = url;
252  d->status = status;
253 
254  if (url.isValid()) {
255  const QString location = QString::fromLatin1(url.toEncoded(QUrl::FullyEncoded));
256  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
257 
258  d->headers.setHeader(QStringLiteral("LOCATION"), location);
259  d->headers.setContentType(QStringLiteral("text/html; charset=utf-8"));
260 
261  const QString buf = QStringLiteral(
262  "<!DOCTYPE html>\n"
263  "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
264  " <head>\n"
265  " <title>Moved</title>\n"
266  " </head>\n"
267  " <body>\n"
268  " <p>This item has moved <a href=\"") + location +
269  QStringLiteral("\">here</a>.</p>\n"
270  " </body>\n"
271  "</html>\n");
272  setBody(buf);
273  } else {
274  d->headers.removeHeader(QStringLiteral("LOCATION"));
275  qCDebug(CUTELYST_ENGINE) << "Invalid redirect removing header" << url << status;
276  }
277 }
278 
279 void Response::redirect(const QString &url, quint16 status)
280 {
281  redirect(QUrl(url), status);
282 }
283 
284 void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
285 {
286  Q_D(const Response);
287  if (url.matches(d->engineRequest->context->req()->uri(), QUrl::RemovePath | QUrl::RemoveQuery)) {
288  redirect(url);
289  } else {
290  redirect(fallback);
291  }
292 }
293 
294 QUrl Response::location() const
295 {
296  Q_D(const Response);
297  return d->location;
298 }
299 
300 QString Response::header(const QString &field) const
301 {
302  Q_D(const Response);
303  return d->headers.header(field);
304 }
305 
306 void Response::setHeader(const QString &field, const QString &value)
307 {
308  Q_D(Response);
309  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
310  "setHeader",
311  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
312 
313  d->headers.setHeader(field, value);
314 }
315 
317 {
318  Q_D(Response);
319  return d->headers;
320 }
321 
323 {
324  Q_D(const Response);
325  return d->engineRequest->status & EngineRequest::FinalizedHeaders;
326 }
327 
329 {
330  return true;
331 }
332 
333 qint64 Response::size() const
334 {
335  Q_D(const Response);
336  if (d->engineRequest->status & EngineRequest::IOWrite) {
337  return -1;
338  } else if (d->bodyIODevice) {
339  return d->bodyIODevice->size();
340  } else {
341  return d->bodyData.size();
342  }
343 }
344 
345 bool Response::webSocketHandshake(const QString &key, const QString &origin, const QString &protocol)
346 {
347  Q_D(Response);
348  return d->engineRequest->webSocketHandshake(key, origin, protocol);
349 }
350 
351 bool Response::webSocketTextMessage(const QString &message)
352 {
353  Q_D(Response);
354  return d->engineRequest->webSocketSendTextMessage(message);
355 }
356 
357 bool Response::webSocketBinaryMessage(const QByteArray &message)
358 {
359  Q_D(Response);
360  return d->engineRequest->webSocketSendBinaryMessage(message);
361 }
362 
363 bool Response::webSocketPing(const QByteArray &payload)
364 {
365  Q_D(Response);
366  return d->engineRequest->webSocketSendPing(payload);
367 }
368 
369 bool Response::webSocketClose(quint16 code, const QString &reason)
370 {
371  Q_D(Response);
372  return d->engineRequest->webSocketClose(code, reason);
373 }
374 
375 void ResponsePrivate::setBodyData(const QByteArray &body)
376 {
377  if (!(engineRequest->status & EngineRequest::IOWrite)) {
378  if (bodyIODevice) {
379  delete bodyIODevice;
380  bodyIODevice = nullptr;
381  }
382  bodyData = body;
383  headers.setContentLength(body.size());
384  }
385 }
386 
387 #include "moc_response.cpp"
Cutelyst::Response::redirect
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:248
Cutelyst::Response::size
virtual qint64 size() const override
Definition: response.cpp:333
Cutelyst::Response::setStatus
void setStatus(quint16 status)
Definition: response.cpp:85
Cutelyst::Response::cookies
QList< QNetworkCookie > cookies() const
Definition: response.cpp:222
Cutelyst::Response::webSocketHandshake
bool webSocketHandshake(const QString &key=QString(), const QString &origin=QString(), const QString &protocol=QString())
Sends the websocket handshake, if no parameters are defined it will use header data.
Definition: response.cpp:345
Cutelyst::Response::setCookie
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:228
Cutelyst::Response::webSocketBinaryMessage
bool webSocketBinaryMessage(const QByteArray &message)
Sends a WebSocket binary message.
Definition: response.cpp:357
Cutelyst::EngineRequest
Definition: enginerequest.h:31
Cutelyst::Response::Response
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:32
Cutelyst::Response::redirectSafe
void redirectSafe(const QUrl &url, const QUrl &fallback)
Definition: response.cpp:284
Cutelyst::Response::header
QString header(const QString &field) const
Definition: response.cpp:300
Cutelyst::Response::setBody
void setBody(QIODevice *body)
Definition: response.cpp:114
Cutelyst::Response::setHeader
void setHeader(const QString &field, const QString &value)
Definition: response.cpp:306
Cutelyst::Response::isSequential
virtual bool isSequential() const override
Definition: response.cpp:328
Cutelyst::Headers
Definition: headers.h:29
Cutelyst::Response::setContentLength
void setContentLength(qint64 length)
Definition: response.cpp:194
Cutelyst::Response::removeCookies
int removeCookies(const QByteArray &name)
Definition: response.cpp:242
Cutelyst::Response::contentTypeCharset
QString contentTypeCharset() const
Definition: response.cpp:210
Cutelyst::Response::hasBody
bool hasBody() const
Definition: response.cpp:91
Cutelyst::Response::setJsonBody
void setJsonBody(const QJsonDocument &documment)
Definition: response.cpp:134
Cutelyst::Response::webSocketClose
bool webSocketClose(quint16 code=Response::CloseCodeNormal, const QString &reason=QString())
Sends a WebSocket close frame, with both optional close code and a string reason.
Definition: response.cpp:369
Cutelyst::Response::webSocketPing
bool webSocketPing(const QByteArray &payload=QByteArray())
Sends a WebSocket ping with an optional payload limited to 125 bytes, which will be truncated if larg...
Definition: response.cpp:363
Cutelyst::Response::bodyDevice
QIODevice * bodyDevice() const
Definition: response.cpp:108
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::Response::setJsonObjectBody
void setJsonObjectBody(const QJsonObject &object)
Definition: response.cpp:156
Cutelyst::Response::setCookies
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:234
Cutelyst::Response::contentLength
qint64 contentLength() const
Definition: response.cpp:188
Cutelyst::Response::writeData
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:45
Cutelyst::Response::readData
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:38
Cutelyst::Response::setContentEncoding
void setContentEncoding(const QString &encoding)
Definition: response.cpp:178
Cutelyst::Response::cookie
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:216
Cutelyst::Response::contentEncoding
QString contentEncoding() const
Definition: response.cpp:172
Cutelyst::Response::redirect
void redirect(const QString &url, quint16 status=Found)
Definition: response.cpp:279
Cutelyst::Response::status
quint16 status() const
Definition: response.cpp:79
Cutelyst::Response::headers
Headers & headers()
Definition: response.cpp:316
Cutelyst::Response::location
QUrl location() const
Definition: response.cpp:294
Cutelyst::Response::contentType
QString contentType() const
Definition: response.cpp:204
Cutelyst::Response::body
Q_REQUIRED_RESULT QByteArray & body()
Definition: response.cpp:97
Cutelyst::Response::isFinalizedHeaders
bool isFinalizedHeaders() const
Definition: response.cpp:322
Cutelyst::Response
Definition: response.h:34
Cutelyst::Response::webSocketTextMessage
bool webSocketTextMessage(const QString &message)
Sends a WebSocket text message.
Definition: response.cpp:351
Cutelyst::Response::setJsonArrayBody
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:164