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(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:218
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: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(char ch)
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()
QString fromLatin1(const char *str, int size)
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
int 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