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
Response(const Headers & defaultHeaders,EngineRequest * engineRequest)32 Response::Response(const Headers &defaultHeaders, EngineRequest *engineRequest)
33 : d_ptr(new ResponsePrivate(defaultHeaders, engineRequest))
34 {
35 open(QIODevice::WriteOnly);
36 }
37
readData(char * data,qint64 maxlen)38 qint64 Response::readData(char *data, qint64 maxlen)
39 {
40 Q_UNUSED(data)
41 Q_UNUSED(maxlen)
42 return -1;
43 }
44
writeData(const char * data,qint64 len)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
~Response()73 Response::~Response()
74 {
75 delete d_ptr->bodyIODevice;
76 delete d_ptr;
77 }
78
status() const79 quint16 Response::status() const
80 {
81 Q_D(const Response);
82 return d->status;
83 }
84
setStatus(quint16 status)85 void Response::setStatus(quint16 status)
86 {
87 Q_D(Response);
88 d->status = status;
89 }
90
hasBody() const91 bool Response::hasBody() const
92 {
93 Q_D(const Response);
94 return !d->bodyData.isEmpty() || d->bodyIODevice || d->engineRequest->status & EngineRequest::IOWrite;
95 }
96
body()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
bodyDevice() const108 QIODevice *Response::bodyDevice() const
109 {
110 Q_D(const Response);
111 return d->bodyIODevice;
112 }
113
setBody(QIODevice * body)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
setBody(const QByteArray & body)128 void Response::setBody(const QByteArray &body)
129 {
130 Q_D(Response);
131 d->setBodyData(body);
132 }
133
setJsonBody(const QJsonDocument & documment)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
setJsonBody(const QString & json)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
setJsonBody(const QByteArray & json)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
setJsonObjectBody(const QJsonObject & object)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
setJsonArrayBody(const QJsonArray & array)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
contentEncoding() const172 QString Response::contentEncoding() const
173 {
174 Q_D(const Response);
175 return d->headers.contentEncoding();
176 }
177
setContentEncoding(const QString & encoding)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
contentLength() const188 qint64 Response::contentLength() const
189 {
190 Q_D(const Response);
191 return d->headers.contentLength();
192 }
193
setContentLength(qint64 length)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
contentType() const204 QString Response::contentType() const
205 {
206 Q_D(const Response);
207 return d->headers.contentType();
208 }
209
contentTypeCharset() const210 QString Response::contentTypeCharset() const
211 {
212 Q_D(const Response);
213 return d->headers.contentTypeCharset();
214 }
215
cookie(const QByteArray & name) const216 QVariant Response::cookie(const QByteArray &name) const
217 {
218 Q_D(const Response);
219 return QVariant::fromValue(d->cookies.value(name));
220 }
221
cookies() const222 QList<QNetworkCookie> Response::cookies() const
223 {
224 Q_D(const Response);
225 return d->cookies.values();
226 }
227
setCookie(const QNetworkCookie & cookie)228 void Response::setCookie(const QNetworkCookie &cookie)
229 {
230 Q_D(Response);
231 d->cookies.insert(cookie.name(), cookie);
232 }
233
setCookies(const QList<QNetworkCookie> & cookies)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
removeCookies(const QByteArray & name)242 int Response::removeCookies(const QByteArray &name)
243 {
244 Q_D(Response);
245 return d->cookies.remove(name);
246 }
247
redirect(const QUrl & url,quint16 status)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 auto 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 = QLatin1String(R"V0G0N(<!DOCTYPE html>
262 <html xmlns="http://www.w3.org/1999/xhtml">
263 <head>
264 <title>Moved</title>
265 </head>
266 <body>
267 <p>This item has moved <a href=")V0G0N") + location + QLatin1String(R"V0G0N(">here</a>.</p>
268 </body>
269 </html>
270 )V0G0N");
271 setBody(buf.toLatin1());
272 } else {
273 d->headers.removeHeader(QStringLiteral("LOCATION"));
274 qCDebug(CUTELYST_ENGINE) << "Invalid redirect removing header" << url << status;
275 }
276 }
277
278 void Response::redirect(const QString &url, quint16 status)
279 {
280 redirect(QUrl(url), status);
281 }
282
283 void Response::redirectSafe(const QUrl &url, const QUrl &fallback)
284 {
285 Q_D(const Response);
286 if (url.matches(d->engineRequest->context->req()->uri(), QUrl::RemovePath | QUrl::RemoveQuery)) {
287 redirect(url);
288 } else {
289 redirect(fallback);
290 }
291 }
292
293 QUrl Response::location() const
294 {
295 Q_D(const Response);
296 return d->location;
297 }
298
299 QString Response::header(const QString &field) const
300 {
301 Q_D(const Response);
302 return d->headers.header(field);
303 }
304
305 void Response::setHeader(const QString &field, const QString &value)
306 {
307 Q_D(Response);
308 Q_ASSERT_X(!(d->engineRequest->status & EngineRequest::FinalizedHeaders),
309 "setHeader",
310 "setting a header value after finalize_headers and the response callback has been called. Not what you want.");
311
312 d->headers.setHeader(field, value);
313 }
314
315 Headers &Response::headers()
316 {
317 Q_D(Response);
318 return d->headers;
319 }
320
321 bool Response::isFinalizedHeaders() const
322 {
323 Q_D(const Response);
324 return d->engineRequest->status & EngineRequest::FinalizedHeaders;
325 }
326
327 bool Response::isSequential() const
328 {
329 return true;
330 }
331
332 qint64 Response::size() const
333 {
334 Q_D(const Response);
335 if (d->engineRequest->status & EngineRequest::IOWrite) {
336 return -1;
337 } else if (d->bodyIODevice) {
338 return d->bodyIODevice->size();
339 } else {
340 return d->bodyData.size();
341 }
342 }
343
344 bool Response::webSocketHandshake(const QString &key, const QString &origin, const QString &protocol)
345 {
346 Q_D(Response);
347 return d->engineRequest->webSocketHandshake(key, origin, protocol);
348 }
349
350 bool Response::webSocketTextMessage(const QString &message)
351 {
352 Q_D(Response);
353 return d->engineRequest->webSocketSendTextMessage(message);
354 }
355
356 bool Response::webSocketBinaryMessage(const QByteArray &message)
357 {
358 Q_D(Response);
359 return d->engineRequest->webSocketSendBinaryMessage(message);
360 }
361
362 bool Response::webSocketPing(const QByteArray &payload)
363 {
364 Q_D(Response);
365 return d->engineRequest->webSocketSendPing(payload);
366 }
367
368 bool Response::webSocketClose(quint16 code, const QString &reason)
369 {
370 Q_D(Response);
371 return d->engineRequest->webSocketClose(code, reason);
372 }
373
374 void ResponsePrivate::setBodyData(const QByteArray &body)
375 {
376 if (!(engineRequest->status & EngineRequest::IOWrite)) {
377 if (bodyIODevice) {
378 delete bodyIODevice;
379 bodyIODevice = nullptr;
380 }
381 bodyData = body;
382 headers.setContentLength(body.size());
383 }
384 }
385
386 #include "moc_response.cpp"
387