cutelyst  4.4.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"
QStringList errors() const noexcept
Definition: context.cpp:67
bool error() const noexcept
Definition: context.cpp:50
Response * response() const noexcept
Definition: context.cpp:97
virtual qint64 doWrite(const char *data, qint64 len)=0
virtual void finalizeBody()
virtual void finalizeError()
qint64 write(const char *data, qint64 len)
void setPath(char *rawPath, const int len)
virtual bool writeHeaders(quint16 status, const Headers &headers)=0
virtual bool finalizeHeaders()
virtual void finalizeCookies()
virtual void processingFinished()
Container for HTTP headers.
Definition: headers.h:24
void setContentLength(qint64 value)
Definition: headers.cpp:172
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:460
A Cutelyst response.
Definition: response.h:29
qint64 size() const noexcept override
void setContentType(const QByteArray &type)
Definition: response.h:238
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
Headers & headers() noexcept
void setBody(QIODevice *body)
Definition: response.cpp:103
QList< QNetworkCookie > cookies() const
Definition: response.cpp:206
QIODevice * bodyDevice() const noexcept
Definition: response.cpp:97
QByteArray & body()
Definition: response.cpp:85
quint16 status() const noexcept
Definition: response.cpp:66
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
const char * constData() const const
char * data()
QByteArray number(double n, char format, int precision)
void reserve(qsizetype size)
qsizetype size() const const
QByteArray toUpper() const const
virtual bool atEnd() const const
virtual bool isSequential() const const
QByteArray read(qint64 maxSize)
virtual bool seek(qint64 pos)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString & prepend(QChar ch)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const