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