cutelyst  4.6.0
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("text/html; charset=utf-8"_qba);
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("Set-Cookie"_qba, cookie.toRawForm());
100  }
101 }
102 
104 {
105  Response *response = context->response();
106  Headers &headers = response->headers();
107 
108  // Set content length if we have a valid one
109  const qint64 size = response->size();
110  if (size >= 0) {
112  }
113 
114  finalizeCookies();
115 
116  // Done
117  status |= EngineRequest::FinalizedHeaders;
118  return writeHeaders(response->status(), headers);
119 }
120 
121 qint64 EngineRequest::write(const char *data, qint64 len)
122 {
123  if (!(status & EngineRequest::Chunked)) {
124  return doWrite(data, len);
125  } else if (!(status & EngineRequest::ChunkedDone)) {
126  const QByteArray chunkSize = QByteArray::number(len, 16).toUpper();
127  QByteArray chunk;
128  chunk.reserve(int(len + chunkSize.size() + 4));
129  chunk.append(chunkSize).append("\r\n", 2).append(data, int(len)).append("\r\n", 2);
130 
131  qint64 retWrite = doWrite(chunk.data(), chunk.size());
132 
133  // Flag if we wrote an empty chunk
134  if (!len) {
135  status |= EngineRequest::ChunkedDone;
136  }
137 
138  return retWrite == chunk.size() ? len : -1;
139  }
140  return -1;
141 }
142 
143 bool EngineRequest::webSocketHandshake(const QByteArray &key,
144  const QByteArray &origin,
145  const QByteArray &protocol)
146 {
147  if (status & EngineRequest::FinalizedHeaders) {
148  return false;
149  }
150 
151  if (webSocketHandshakeDo(key, origin, protocol)) {
152  status |= EngineRequest::FinalizedHeaders | EngineRequest::Async | EngineRequest::IOWrite;
153 
154  context->finalize();
155 
156  return true;
157  }
158 
159  return false;
160 }
161 
162 bool EngineRequest::webSocketSendTextMessage(const QString &message)
163 {
164  Q_UNUSED(message)
165  return false;
166 }
167 
168 bool EngineRequest::webSocketSendBinaryMessage(const QByteArray &message)
169 {
170  Q_UNUSED(message)
171  return false;
172 }
173 
174 bool EngineRequest::webSocketSendPing(const QByteArray &payload)
175 {
176  Q_UNUSED(payload)
177  return false;
178 }
179 
180 bool EngineRequest::webSocketClose(quint16 code, const QString &reason)
181 {
182  Q_UNUSED(code)
183  Q_UNUSED(reason)
184  return false;
185 }
186 
188 {
189 }
190 
191 bool EngineRequest::webSocketHandshakeDo(const QByteArray &key,
192  const QByteArray &origin,
193  const QByteArray &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 = u"/"_qs;
205  return;
206  }
207 
208  char *data = rawPath;
209  const char *inputPtr = data;
210 
211  bool lastSlash = false;
212  bool skipUtf8 = true;
213  int outlen = 0;
214  for (int i = 0; i < len; ++i, ++outlen) {
215  const char c = inputPtr[i];
216  if (c == '%' && i + 2 < len) {
217  int a = inputPtr[++i];
218  int b = inputPtr[++i];
219 
220  if (a >= '0' && a <= '9')
221  a -= '0';
222  else if (a >= 'a' && a <= 'f')
223  a = a - 'a' + 10;
224  else if (a >= 'A' && a <= 'F')
225  a = a - 'A' + 10;
226 
227  if (b >= '0' && b <= '9')
228  b -= '0';
229  else if (b >= 'a' && b <= 'f')
230  b = b - 'a' + 10;
231  else if (b >= 'A' && b <= 'F')
232  b = b - 'A' + 10;
233 
234  *data++ = char((a << 4) | b);
235  skipUtf8 = false;
236  } else if (c == '+') {
237  *data++ = ' ';
238  } else if (c == '/') {
239  // Remove duplicated slashes
240  if (!lastSlash) {
241  *data++ = '/';
242  } else {
243  --outlen;
244  }
245  lastSlash = true;
246  continue;
247  } else {
248  *data++ = c;
249  }
250  lastSlash = false;
251  }
252 
253  if (skipUtf8) {
254  path = QString::fromLatin1(rawPath, outlen);
255  } else {
256  path = QString::fromUtf8(rawPath, outlen);
257  }
258 
259  if (!path.startsWith(u'/')) {
260  path.prepend(u'/');
261  }
262 }
263 
264 #include "moc_enginerequest.cpp"
virtual bool atEnd() const const
bool error() const noexcept
Definition: context.cpp:50
void reserve(qsizetype size)
virtual bool seek(qint64 pos)
Headers & headers() noexcept
QString & prepend(QChar ch)
QByteArray toUpper() const const
Container for HTTP headers.
Definition: headers.h:23
qint64 write(const char *data, qint64 len)
QString join(QChar separator) const const
QString fromUtf8(QByteArrayView str)
qint64 size() const noexcept override
QIODevice * bodyDevice() const noexcept
Definition: response.cpp:97
virtual void finalizeError()
virtual bool isSequential() const const
void setContentType(const QByteArray &type)
Definition: response.h:238
QByteArray read(qint64 maxSize)
A Cutelyst response.
Definition: response.h:28
QByteArray number(double n, char format, int precision)
virtual qint64 doWrite(const char *data, qint64 len)=0
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QList< QNetworkCookie > cookies() const
Definition: response.cpp:206
const char * constData() const const
The Cutelyst namespace holds all public Cutelyst API.
quint16 status() const noexcept
Definition: response.cpp:66
void setPath(char *rawPath, const int len)
QByteArray & append(QByteArrayView data)
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:460
QString fromLatin1(QByteArrayView str)
virtual bool finalizeHeaders()
QByteArray & body()
Definition: response.cpp:85
char * data()
void setBody(QIODevice *body)
Definition: response.cpp:103
virtual void finalizeBody()
void setContentLength(qint64 value)
Definition: headers.cpp:172
qsizetype size() const const
Response * response() const noexcept
Definition: context.cpp:97
virtual bool writeHeaders(quint16 status, const Headers &headers)=0
virtual void finalizeCookies()
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
virtual void processingFinished()
QStringList errors() const noexcept
Definition: context.cpp:67
QByteArray toUtf8() const const