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