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