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