Cutelyst  2.5.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 
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  d->bodyIODevice = body;
122  }
123 }
124 
125 void Response::setBody(const QByteArray &body)
126 {
127  Q_D(Response);
128  d->setBodyData(body);
129 }
130 
131 void Response::setJsonBody(const QJsonDocument &documment)
132 {
133  Q_D(Response);
134  const QByteArray body = documment.toJson(QJsonDocument::Compact);
135  d->setBodyData(body);
136  d->headers.setContentType(QStringLiteral("application/json"));
137 }
138 
139 void Response::setJsonObjectBody(const QJsonObject &object)
140 {
141  Q_D(Response);
142  const QByteArray body = QJsonDocument(object).toJson(QJsonDocument::Compact);
143  d->setBodyData(body);
144  d->headers.setContentType(QStringLiteral("application/json"));
145 }
146 
147 void Response::setJsonArrayBody(const QJsonArray &array)
148 {
149  Q_D(Response);
150  const QByteArray body = QJsonDocument(array).toJson(QJsonDocument::Compact);
151  d->setBodyData(body);
152  d->headers.setContentType(QStringLiteral("application/json"));
153 }
154 
156 {
157  Q_D(const Response);
158  return d->headers.contentEncoding();
159 }
160 
161 void Cutelyst::Response::setContentEncoding(const QString &encoding)
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 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 called. Not what you want.");
183 
184  d->headers.setContentLength(length);
185 }
186 
187 QString Response::contentType() const
188 {
189  Q_D(const Response);
190  return d->headers.contentType();
191 }
192 
194 {
195  Q_D(const Response);
196  return d->headers.contentTypeCharset();
197 }
198 
199 QVariant Response::cookie(const QByteArray &name) const
200 {
201  Q_D(const Response);
202  return QVariant::fromValue(d->cookies.value(name));
203 }
204 
205 QList<QNetworkCookie> Response::cookies() const
206 {
207  Q_D(const Response);
208  return d->cookies.values();
209 }
210 
211 void Response::setCookie(const QNetworkCookie &cookie)
212 {
213  Q_D(Response);
214  d->cookies.insert(cookie.name(), cookie);
215 }
216 
217 void Response::setCookies(const QList<QNetworkCookie> &cookies)
218 {
219  Q_D(Response);
220  for (const QNetworkCookie &cookie : cookies) {
221  d->cookies.insert(cookie.name(), cookie);
222  }
223 }
224 
225 int Response::removeCookies(const QByteArray &name)
226 {
227  Q_D(Response);
228  return d->cookies.remove(name);
229 }
230 
231 void Response::redirect(const QUrl &url, quint16 status)
232 {
233  Q_D(Response);
234  d->location = url;
235  d->status = status;
236 
237  if (url.isValid()) {
238  const QString location = QString::fromLatin1(url.toEncoded(QUrl::FullyEncoded));
239  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
240 
241  d->headers.setHeader(QStringLiteral("LOCATION"), location);
242  d->headers.setContentType(QStringLiteral("text/html; charset=utf-8"));
243 
244  const QString buf = QStringLiteral(
245  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0"
246  "Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
247  "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
248  " <head>\n"
249  " <title>Moved</title>\n"
250  " </head>\n"
251  " <body>\n"
252  " <p>This item has moved <a href=") + location +
253  QStringLiteral(">here</a>.</p>\n"
254  " </body>\n"
255  "</html>\n");
256  setBody(buf);
257  } else {
258  d->headers.removeHeader(QStringLiteral("LOCATION"));
259  qCDebug(CUTELYST_ENGINE) << "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 QUrl Response::location() const
269 {
270  Q_D(const Response);
271  return d->location;
272 }
273 
274 QString Response::header(const QString &field) const
275 {
276  Q_D(const Response);
277  return d->headers.header(field);
278 }
279 
280 void Response::setHeader(const QString &field, const QString &value)
281 {
282  Q_D(Response);
283  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
284  "setHeader",
285  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
286 
287  d->headers.setHeader(field, value);
288 }
289 
291 {
292  Q_D(Response);
293  return d->headers;
294 }
295 
297 {
298  return true;
299 }
300 
301 qint64 Response::size() const
302 {
303  Q_D(const Response);
304  if (d->engineRequest->status & EngineRequest::IOWrite) {
305  return -1;
306  } else if (d->bodyIODevice) {
307  return d->bodyIODevice->size();
308  } else {
309  return d->bodyData.size();
310  }
311 }
312 
313 bool Response::webSocketHandshake(const QString &key, const QString &origin, const QString &protocol)
314 {
315  Q_D(Response);
316  return d->engineRequest->webSocketHandshake(key, origin, protocol);
317 }
318 
319 bool Response::webSocketTextMessage(const QString &message)
320 {
321  Q_D(Response);
322  return d->engineRequest->webSocketSendTextMessage(message);
323 }
324 
325 bool Response::webSocketBinaryMessage(const QByteArray &message)
326 {
327  Q_D(Response);
328  return d->engineRequest->webSocketSendBinaryMessage(message);
329 }
330 
331 bool Response::webSocketPing(const QByteArray &payload)
332 {
333  Q_D(Response);
334  return d->engineRequest->webSocketSendPing(payload);
335 }
336 
337 bool Response::webSocketClose(quint16 code, const QString &reason)
338 {
339  Q_D(Response);
340  return d->engineRequest->webSocketClose(code, reason);
341 }
342 
343 void ResponsePrivate::setBodyData(const QByteArray &body)
344 {
345  if (!(engineRequest->status & EngineRequest::IOWrite)) {
346  if (bodyIODevice) {
347  delete bodyIODevice;
348  bodyIODevice = nullptr;
349  }
350  bodyData = body;
351  headers.setContentLength(body.size());
352  }
353 }
354 
355 #include "moc_response.cpp"
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:337
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:211
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:199
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:331
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:313
void setHeader(const QString &field, const QString &value)
Definition: response.cpp:280
quint16 status() const
Definition: response.cpp:79
void setContentLength(qint64 length)
Definition: response.cpp:177
QString header(const QString &field) const
Definition: response.cpp:274
Response(const Headers &defaultHeaders, EngineRequest *conn=nullptr)
Definition: response.cpp:32
void setJsonBody(const QJsonDocument &documment)
Definition: response.cpp:131
virtual qint64 size() const override
Definition: response.cpp:301
void setStatus(quint16 status)
Definition: response.cpp:85
bool hasBody() const
Definition: response.cpp:91
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:38
void setContentEncoding(const QString &encoding)
Definition: response.cpp:161
QString contentEncoding() const
Definition: response.cpp:155
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:231
int removeCookies(const QByteArray &name)
Definition: response.cpp:225
QList< QNetworkCookie > cookies() const
Definition: response.cpp:205
bool webSocketBinaryMessage(const QByteArray &message)
Sends a WebSocket binary message.
Definition: response.cpp:325
Headers & headers()
Definition: response.cpp:290
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:217
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:45
QString contentType() const
Definition: response.cpp:187
QUrl location() const
Definition: response.cpp:268
void setJsonArrayBody(const QJsonArray &array)
Definition: response.cpp:147
Q_REQUIRED_RESULT QByteArray & body()
Definition: response.cpp:97
virtual bool isSequential() const override
Definition: response.cpp:296
bool webSocketTextMessage(const QString &message)
Sends a WebSocket text message.
Definition: response.cpp:319
QString contentTypeCharset() const
Definition: response.cpp:193
void setBody(QIODevice *body)
Definition: response.cpp:114
void setContentLength(qint64 value)
Definition: headers.cpp:168
QIODevice * bodyDevice() const
Definition: response.cpp:108
qint64 contentLength() const
Definition: response.cpp:171
void setJsonObjectBody(const QJsonObject &object)
Definition: response.cpp:139