Cutelyst  1.11.0
response.cpp
1 /*
2  * Copyright (C) 2013-2017 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(Context *c, Engine *engine, const Cutelyst::Headers &defaultHeaders) : QIODevice(c)
33  , d_ptr(new ResponsePrivate(c, engine, defaultHeaders))
34 {
35  open(QIODevice::WriteOnly);
36 }
37 
38 Response::Response(Context *c, EngineRequest *engineRequest, const Headers &defaultHeaders) : QIODevice(c)
39  , d_ptr(new ResponsePrivate(c, engineRequest, defaultHeaders))
40 {
41  open(QIODevice::WriteOnly);
42 }
43 
44 qint64 Response::readData(char *data, qint64 maxlen)
45 {
46  Q_UNUSED(data)
47  Q_UNUSED(maxlen)
48  return -1;
49 }
50 
51 qint64 Response::writeData(const char *data, qint64 len)
52 {
53  Q_D(Response);
54 
55  if (len <= 0) {
56  return len;
57  }
58 
59  // Finalize headers if someone manually writes output
60  if (!(d->engineRequest->status & EngineRequest::FinalizedHeaders)) {
61  if (d->headers.header(QStringLiteral("TRANSFER_ENCODING")) == QLatin1String("chunked")) {
62  d->engineRequest->status |= EngineRequest::IOWrite | EngineRequest::Chunked;
63  } else {
64  // When chunked encoding is not set the client can only know
65  // that data is finished if we close the connection
66  d->headers.setHeader(QStringLiteral("CONNECTION"), QStringLiteral("close"));
67  d->engineRequest->status |= EngineRequest::IOWrite;
68  }
69  delete d->bodyIODevice;
70  d->bodyIODevice = nullptr;
71  d->bodyData = QByteArray();
72 
73  d->engineRequest->finalizeHeaders(d->context);
74  }
75 
76  return d->engineRequest->write(data, len);
77 }
78 
79 Response::~Response()
80 {
81  delete d_ptr;
82 }
83 
84 quint16 Response::status() const
85 {
86  Q_D(const Response);
87  return d->status;
88 }
89 
91 {
92  Q_D(Response);
93  d->status = status;
94 }
95 
96 bool Response::hasBody() const
97 {
98  Q_D(const Response);
99  return !d->bodyData.isEmpty() || d->bodyIODevice || d->engineRequest->status & EngineRequest::IOWrite;
100 }
101 
102 QByteArray &Response::body()
103 {
104  Q_D(Response);
105  if (d->bodyIODevice) {
106  delete d->bodyIODevice;
107  d->bodyIODevice = nullptr;
108  }
109 
110  return d->bodyData;
111 }
112 
113 QIODevice *Response::bodyDevice() const
114 {
115  Q_D(const Response);
116  return d->bodyIODevice;
117 }
118 
119 void Response::setBody(QIODevice *body)
120 {
121  Q_D(Response);
122  Q_ASSERT(body && body->isOpen() && body->isReadable());
123 
124  if (!(d->engineRequest->status & EngineRequest::IOWrite)) {
125  body->setParent(d->context);
126 
127  d->bodyData = QByteArray();
128  d->bodyIODevice = body;
129  }
130 }
131 
132 void Response::setBody(const QByteArray &body)
133 {
134  Q_D(Response);
135  d->setBodyData(body);
136 }
137 
138 void Response::setJsonBody(const QJsonDocument &documment)
139 {
140  Q_D(Response);
141  const QByteArray body = documment.toJson(QJsonDocument::Compact);
142  d->setBodyData(body);
143  d->headers.setContentType(QStringLiteral("application/json"));
144 }
145 
146 void Response::setJsonBody(const QJsonObject &object)
147 {
148  Q_D(Response);
149  const QByteArray body = QJsonDocument(object).toJson(QJsonDocument::Compact);
150  d->setBodyData(body);
151  d->headers.setContentType(QStringLiteral("application/json"));
152 }
153 
154 void Response::setJsonBody(const QJsonArray &array)
155 {
156  Q_D(Response);
157  const QByteArray body = QJsonDocument(array).toJson(QJsonDocument::Compact);
158  d->setBodyData(body);
159  d->headers.setContentType(QStringLiteral("application/json"));
160 }
161 
163 {
164  Q_D(const Response);
165  return d->headers.contentEncoding();
166 }
167 
168 void Cutelyst::Response::setContentEncoding(const QString &encoding)
169 {
170  Q_D(Response);
171  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
172  "setContentEncoding",
173  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
174 
175  d->headers.setContentEncoding(encoding);
176 }
177 
179 {
180  Q_D(const Response);
181  return d->headers.contentLength();
182 }
183 
184 void Response::setContentLength(qint64 length)
185 {
186  Q_D(Response);
187  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
188  "setContentLength",
189  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
190 
191  d->headers.setContentLength(length);
192 }
193 
194 QString Response::contentType() const
195 {
196  Q_D(const Response);
197  return d->headers.contentType();
198 }
199 
201 {
202  Q_D(const Response);
203  return d->headers.contentTypeCharset();
204 }
205 
206 QVariant Response::cookie(const QByteArray &name) const
207 {
208  Q_D(const Response);
209  return QVariant::fromValue(d->cookies.value(name));
210 }
211 
212 QList<QNetworkCookie> Response::cookies() const
213 {
214  Q_D(const Response);
215  return d->cookies.values();
216 }
217 
218 void Response::setCookie(const QNetworkCookie &cookie)
219 {
220  Q_D(Response);
221  d->cookies.insert(cookie.name(), cookie);
222 }
223 
224 void Response::setCookies(const QList<QNetworkCookie> &cookies)
225 {
226  Q_D(Response);
227  for (const QNetworkCookie &cookie : cookies) {
228  d->cookies.insert(cookie.name(), cookie);
229  }
230 }
231 
232 int Response::removeCookies(const QByteArray &name)
233 {
234  Q_D(Response);
235  return d->cookies.remove(name);
236 }
237 
238 void Response::redirect(const QUrl &url, quint16 status)
239 {
240  Q_D(Response);
241  d->location = url;
242  d->status = status;
243 
244  if (url.isValid()) {
245  const QString location = QString::fromLatin1(url.toEncoded(QUrl::FullyEncoded));
246  qCDebug(CUTELYST_RESPONSE) << "Redirecting to" << location << status;
247 
248  d->headers.setHeader(QStringLiteral("LOCATION"), location);
249  d->headers.setContentType(QStringLiteral("text/html; charset=utf-8"));
250 
251  const QString buf = QStringLiteral(
252  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0"
253  "Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
254  "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
255  " <head>\n"
256  " <title>Moved</title>\n"
257  " </head>\n"
258  " <body>\n"
259  " <p>This item has moved <a href=") + location +
260  QStringLiteral(">here</a>.</p>\n"
261  " </body>\n"
262  "</html>\n");
263  setBody(buf);
264  } else {
265  d->headers.removeHeader(QStringLiteral("LOCATION"));
266  qCDebug(CUTELYST_ENGINE) << "Invalid redirect removing header" << url << status;
267  }
268 }
269 
270 void Response::redirect(const QString &url, quint16 status)
271 {
272  redirect(QUrl(url), status);
273 }
274 
275 QUrl Response::location() const
276 {
277  Q_D(const Response);
278  return d->location;
279 }
280 
281 QString Response::header(const QString &field) const
282 {
283  Q_D(const Response);
284  return d->headers.header(field);
285 }
286 
287 void Response::setHeader(const QString &field, const QString &value)
288 {
289  Q_D(Response);
290  Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
291  "setHeader",
292  "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
293 
294  d->headers.setHeader(field, value);
295 }
296 
298 {
299  Q_D(Response);
300  return d->headers;
301 }
302 
304 {
305  return true;
306 }
307 
308 qint64 Response::size() const
309 {
310  Q_D(const Response);
311  if (d->engineRequest->status & EngineRequest::IOWrite) {
312  return -1;
313  } else if (d->bodyIODevice) {
314  return d->bodyIODevice->size();
315  } else {
316  return d->bodyData.size();
317  }
318 }
319 
320 bool Response::webSocketHandshake(const QString &key, const QString &origin, const QString &protocol)
321 {
322  Q_D(Response);
323  return d->engineRequest->webSocketHandshake(d->context, key, origin, protocol);
324 }
325 
326 bool Response::webSocketTextMessage(const QString &message)
327 {
328  Q_D(Response);
329  return d->engineRequest->webSocketSendTextMessage(message);
330 }
331 
332 bool Response::webSocketBinaryMessage(const QByteArray &message)
333 {
334  Q_D(Response);
335  return d->engineRequest->webSocketSendBinaryMessage(message);
336 }
337 
338 bool Response::webSocketPing(const QByteArray &payload)
339 {
340  Q_D(Response);
341  return d->engineRequest->webSocketSendPing(payload);
342 }
343 
344 bool Response::webSocketClose(quint16 code, const QString &reason)
345 {
346  Q_D(Response);
347  return d->engineRequest->webSocketClose(code, reason);
348 }
349 
350 void ResponsePrivate::setBodyData(const QByteArray &body)
351 {
352  if (!(engineRequest->status & EngineRequest::IOWrite)) {
353  if (bodyIODevice) {
354  delete bodyIODevice;
355  bodyIODevice = nullptr;
356  }
357  bodyData = body;
358  headers.setContentLength(body.size());
359  }
360 }
361 
362 #include "moc_response.cpp"
quint16 status() const
Definition: response.cpp:84
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:344
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:218
QUrl location() const
Definition: response.cpp:275
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:338
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:320
void setHeader(const QString &field, const QString &value)
Definition: response.cpp:287
Response(Context *c, Engine *engine, const Headers &defaultHeaders)
Definition: response.cpp:32
void setContentLength(qint64 length)
Definition: response.cpp:184
QString contentTypeCharset() const
Definition: response.cpp:200
void setJsonBody(const QJsonDocument &documment)
Definition: response.cpp:138
virtual qint64 size() const override
Definition: response.cpp:308
void setStatus(quint16 status)
Definition: response.cpp:90
qint64 contentLength() const
Definition: response.cpp:178
QList< QNetworkCookie > cookies() const
Definition: response.cpp:212
bool hasBody() const
Definition: response.cpp:96
virtual qint64 readData(char *data, qint64 maxlen) override
Definition: response.cpp:44
void setContentEncoding(const QString &encoding)
Definition: response.cpp:168
The Cutelyst Context.
Definition: context.h:50
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:238
int removeCookies(const QByteArray &name)
Definition: response.cpp:232
QString contentEncoding() const
Definition: response.cpp:162
bool webSocketBinaryMessage(const QByteArray &message)
Sends a WebSocket binary message.
Definition: response.cpp:332
Headers & headers()
Definition: response.cpp:297
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setCookies(const QList< QNetworkCookie > &cookies)
Definition: response.cpp:224
virtual qint64 writeData(const char *data, qint64 len) override
Definition: response.cpp:51
QString contentType() const
Definition: response.cpp:194
QString header(const QString &field) const
Definition: response.cpp:281
Q_REQUIRED_RESULT QByteArray & body()
Definition: response.cpp:102
Status status
Connection status.
QIODevice * bodyDevice() const
Definition: response.cpp:113
virtual bool isSequential() const override
Definition: response.cpp:303
QVariant cookie(const QByteArray &name) const
Definition: response.cpp:206
bool webSocketTextMessage(const QString &message)
Sends a WebSocket text message.
Definition: response.cpp:326
void setBody(QIODevice *body)
Definition: response.cpp:119
void setContentLength(qint64 value)
Definition: headers.cpp:166
The Cutelyst Engine.
Definition: engine.h:33