Cutelyst  2.5.0
enginerequest.cpp
1 /*
2  * Copyright (C) 2017-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 "enginerequest.h"
19 
20 #include "common.h"
21 
22 #include <Cutelyst/response_p.h>
23 #include <Cutelyst/Context>
24 
25 #include <QLoggingCategory>
26 Q_LOGGING_CATEGORY(CUTELYST_ENGINEREQUEST, "cutelyst.engine_request", QtWarningMsg)
27 
28 using namespace Cutelyst;
29 
30 EngineRequest::EngineRequest()
31 {
32 
33 }
34 
35 EngineRequest::~EngineRequest()
36 {
37  delete body;
38  delete context;
39 }
40 
42 {
43  if (!(status & EngineRequest::Chunked)) {
44  Response *response = context->response();
45  QIODevice *body = response->bodyDevice();
46 
47  if (body) {
48  body->seek(0);
49  char block[64 * 1024];
50  while (!body->atEnd()) {
51  qint64 in = body->read(block, sizeof(block));
52  if (in <= 0) {
53  break;
54  }
55 
56  if (write(block, in) != in) {
57  qCWarning(CUTELYST_ENGINEREQUEST) << "Failed to write body";
58  break;
59  }
60  }
61  } else {
62  const QByteArray bodyByteArray = response->body();
63  write(bodyByteArray.constData(), bodyByteArray.size());
64  }
65  } else if (!(status & EngineRequest::ChunkedDone)) {
66  // Write the final '0' chunk
67  doWrite("0\r\n\r\n", 5);
68  }
69 }
70 
72 {
73  Response *res = context->response();
74 
75  res->setContentType(QStringLiteral("text/html; charset=utf-8"));
76 
77  QByteArray body;
78 
79  // Trick IE. Old versions of IE would display their own error page instead
80  // of ours if we'd give it less than 512 bytes.
81  body.reserve(512);
82 
83  body.append(context->errors().join(QLatin1Char('\n')).toUtf8());
84 
85  res->setBody(body);
86 
87  // Return 500
88  res->setStatus(Response::InternalServerError);
89 }
90 
92 {
93  if (context->error()) {
94  finalizeError();
95  }
96 
97  if (!(status & EngineRequest::FinalizedHeaders) && !finalizeHeaders()) {
98  return;
99  }
100 
101  finalizeBody();
102 }
103 
105 {
106  Response *res = context->response();
107  Headers &headers = res->headers();
108  const auto cookies = res->cookies();
109  for (const QNetworkCookie &cookie : cookies) {
110  headers.pushHeader(QStringLiteral("SET_COOKIE"), QString::fromLatin1(cookie.toRawForm()));
111  }
112 }
113 
115 {
116  Response *response = context->response();
117  Headers &headers = response->headers();
118 
119  // Fix missing content length
120  if (headers.contentLength() < 0) {
121  qint64 size = response->size();
122  if (size >= 0) {
123  headers.setContentLength(size);
124  }
125  }
126 
127  finalizeCookies();
128 
129  // Done
130  status |= EngineRequest::FinalizedHeaders;
131  return writeHeaders(response->status(), headers);
132 }
133 
134 qint64 EngineRequest::write(const char *data, qint64 len)
135 {
136  if (!(status & EngineRequest::Chunked)) {
137  return doWrite(data, len);
138  } else if (!(status & EngineRequest::ChunkedDone)) {
139  const QByteArray chunkSize = QByteArray::number(len, 16).toUpper();
140  QByteArray chunk;
141  chunk.reserve(len + chunkSize.size() + 4);
142  chunk.append(chunkSize).append("\r\n", 2)
143  .append(data, len).append("\r\n", 2);
144 
145  qint64 retWrite = doWrite(chunk.data(), chunk.size());
146 
147  // Flag if we wrote an empty chunk
148  if (!len) {
149  status |= EngineRequest::ChunkedDone;
150  }
151 
152  return retWrite == chunk.size() ? len : -1;
153  }
154  return -1;
155 }
156 
157 bool EngineRequest::webSocketHandshake(const QString &key, const QString &origin, const QString &protocol)
158 {
159  if (status & EngineRequest::FinalizedHeaders) {
160  return false;
161  }
162 
163  if (webSocketHandshakeDo(key, origin, protocol)) {
164  status |= EngineRequest::FinalizedHeaders;
165  return true;
166  }
167 
168  return false;
169 }
170 
171 bool EngineRequest::webSocketSendTextMessage(const QString &message)
172 {
173  Q_UNUSED(message)
174  return false;
175 }
176 
177 bool EngineRequest::webSocketSendBinaryMessage(const QByteArray &message)
178 {
179  Q_UNUSED(message)
180  return false;
181 }
182 
183 bool EngineRequest::webSocketSendPing(const QByteArray &payload)
184 {
185  Q_UNUSED(payload)
186  return false;
187 }
188 
189 bool EngineRequest::webSocketClose(quint16 code, const QString &reason)
190 {
191  Q_UNUSED(code)
192  Q_UNUSED(reason)
193  return false;
194 }
195 
197 {
198 }
199 
200 bool EngineRequest::webSocketHandshakeDo(const QString &key, const QString &origin, const QString &protocol)
201 {
202  Q_UNUSED(key)
203  Q_UNUSED(origin)
204  Q_UNUSED(protocol)
205  return false;
206 }
207 
208 void EngineRequest::setPath(char *rawPath, const int len)
209 {
210  if (len == 0) {
211  return;
212  }
213 
214  char *data = rawPath;
215  const char *inputPtr = data;
216 
217  bool skipUtf8 = true;
218  int outlen = 0;
219  for (int i = 0; i < len; ++i, ++outlen) {
220  const char c = inputPtr[i];
221  if (c == '%' && i + 2 < len) {
222  int a = inputPtr[++i];
223  int b = inputPtr[++i];
224 
225  if (a >= '0' && a <= '9') a -= '0';
226  else if (a >= 'a' && a <= 'f') a = a - 'a' + 10;
227  else if (a >= 'A' && a <= 'F') a = a - 'A' + 10;
228 
229  if (b >= '0' && b <= '9') b -= '0';
230  else if (b >= 'a' && b <= 'f') b = b - 'a' + 10;
231  else if (b >= 'A' && b <= 'F') b = b - 'A' + 10;
232 
233  *data++ = (char)((a << 4) | b);
234  skipUtf8 = false;
235  } else if (c == '+') {
236  *data++ = ' ';
237  } else {
238  *data++ = c;
239  }
240  }
241 
242  if (skipUtf8) {
243  path = QString::fromLatin1(rawPath, outlen);
244  } else {
245  path = QString::fromUtf8(rawPath, outlen);
246  }
247 }
248 
249 #include "moc_enginerequest.cpp"
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:363
QString path
Call setPath() instead.
void setContentType(const QString &type)
Definition: response.h:206
QString protocol
The protocol requested by the user agent &#39;HTTP1/1&#39;.
quint16 status() const
Definition: response.cpp:79
QIODevice * body
The QIODevice containing the body (if any) of the request.
qint64 write(const char *data, qint64 len)
Called by Response to manually write data.
virtual qint64 size() const override
Definition: response.cpp:301
void setStatus(quint16 status)
Definition: response.cpp:85
virtual void finalizeError()
Engines should overwrite this if they want to to make custom error messages.
virtual qint64 doWrite(const char *data, qint64 len)=0
Reimplement this to do the RAW writing to the client.
QList< QNetworkCookie > cookies() const
Definition: response.cpp:205
QStringList errors() const
Returns a list of errors that were defined.
Definition: context.cpp:80
Context * context
The Cutelyst::Context of this request.
Headers & headers()
Definition: response.cpp:290
Response * response() const
Definition: context.cpp:110
Headers headers
The request headers.
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setPath(char *rawPath, const int len)
This method sets the path and already does the decoding so that it is done a single time...
virtual bool finalizeHeaders()
Finalize the headers, and call doWriteHeader(), reimplemententions must call this first...
qint64 contentLength() const
Definition: headers.cpp:159
Q_REQUIRED_RESULT QByteArray & body()
Definition: response.cpp:97
Status status
Connection status.
void setBody(QIODevice *body)
Definition: response.cpp:114
virtual void finalizeBody()
Engines must reimplement this to write the response body back to the caller.
void setContentLength(qint64 value)
Definition: headers.cpp:168
virtual bool writeHeaders(quint16 status, const Headers &headers)=0
Reimplement this to write the headers back to the client.
virtual void finalizeCookies()
Reimplement if you need a custom way to Set-Cookie, the default implementation writes them to c->res(...
void finalize()
Called by Application to deal with finalizing cookies, headers and body.
virtual void processingFinished()
This is called when the Application chain is finished processing this request, here the request can s...
bool error() const
Returns true if an error was set.
Definition: context.cpp:63
QIODevice * bodyDevice() const
Definition: response.cpp:108