Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
http_response.h
1#pragma once
2#include <string>
3#include <unordered_map>
4#include <ios>
5#include <fstream>
6#include <sstream>
7// S_ISREG is not defined for windows
8// This defines it like suggested in https://stackoverflow.com/a/62371749
9#if defined(_MSC_VER)
10#define _CRT_INTERNAL_NONSTDC_NAMES 1
11#endif
12#include <sys/stat.h>
13#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
14#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
15#endif
16
17#include "crow/http_request.h"
18#include "crow/ci_map.h"
19#include "crow/socket_adaptors.h"
20#include "crow/logging.h"
21#include "crow/mime_types.h"
22#include "crow/returnable.h"
23
24
25namespace crow
26{
27 template<typename Adaptor, typename Handler, typename... Middlewares>
28 class Connection;
29
30 namespace websocket
31 {
32 template<typename Adaptor, typename Handler>
33 class Connection;
34 }
35
36 class Router;
37
38 /// HTTP response
39 struct response
40 {
41 template<typename Adaptor, typename Handler, typename... Middlewares>
42 friend class crow::Connection;
43
44 template<typename Adaptor, typename Handler>
45 friend class websocket::Connection;
46
47 friend class Router;
48
49 int code{200}; ///< The Status code for the response.
50 std::string body; ///< The actual payload containing the response data.
51 ci_map headers; ///< HTTP headers.
52
53#ifdef CROW_ENABLE_COMPRESSION
54 bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
55#endif
56 bool skip_body = false; ///< Whether this is a response to a HEAD request.
57 bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
58
59 /// Set the value of an existing header in the response.
60 void set_header(std::string key, std::string value)
61 {
64 headers.erase(key);
65 headers.emplace(std::move(key), std::move(value));
66 }
67
68 /// Add a new header to the response.
69 void add_header(std::string key, std::string value)
70 {
73 headers.emplace(std::move(key), std::move(value));
74 }
75
76 const std::string& get_header_value(const std::string& key)
77 {
79 }
80
81 // naive validation of a mime-type string
82 static bool validate_mime_type(const std::string& candidate) noexcept
83 {
84 // Here we simply check that the candidate type starts with
85 // a valid parent type, and has at least one character afterwards.
86 std::array<std::string, 10> valid_parent_types = {
87 "application/", "audio/", "font/", "example/",
88 "image/", "message/", "model/", "multipart/",
89 "text/", "video/"};
90 for (const std::string& parent : valid_parent_types)
91 {
92 // ensure the candidate is *longer* than the parent,
93 // to avoid unnecessary string comparison and to
94 // reject zero-length subtypes.
95 if (candidate.size() <= parent.size())
96 {
97 continue;
98 }
99 // strncmp is used rather than substr to avoid allocation,
100 // but a string_view approach would be better if Crow
101 // migrates to C++17.
102 if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
103 {
104 return true;
105 }
106 }
107 return false;
108 }
109
110 // Find the mime type from the content type either by lookup,
111 // or by the content type itself, if it is a valid a mime type.
112 // Defaults to text/plain.
113 static std::string get_mime_type(const std::string& contentType)
114 {
115 const auto mimeTypeIterator = mime_types.find(contentType);
116 if (mimeTypeIterator != mime_types.end())
117 {
118 return mimeTypeIterator->second;
119 }
120 else if (validate_mime_type(contentType))
121 {
122 return contentType;
123 }
124 else
125 {
126 CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
127 return "text/plain";
128 }
129 }
130
131
132 // clang-format off
133 response() {}
134 explicit response(int code_) : code(code_) {}
135 response(std::string body_) : body(std::move(body_)) {}
136 response(int code_, std::string body_) : code(code_), body(std::move(body_)) {}
137 // clang-format on
138 response(returnable&& value)
139 {
140 body = value.dump();
141 set_header("Content-Type", value.content_type);
142 }
143 response(returnable& value)
144 {
145 body = value.dump();
146 set_header("Content-Type", value.content_type);
147 }
148 response(int code_, returnable& value):
149 code(code_)
150 {
151 body = value.dump();
152 set_header("Content-Type", value.content_type);
153 }
154 response(int code_, returnable&& value):
155 code(code_), body(value.dump())
156 {
157 set_header("Content-Type", std::move(value.content_type));
158 }
159
160 response(response&& r)
161 {
162 *this = std::move(r);
163 }
164
165 response(std::string contentType, std::string body_):
166 body(std::move(body_))
167 {
168 set_header("Content-Type", get_mime_type(contentType));
169 }
170
171 response(int code_, std::string contentType, std::string body_):
172 code(code_), body(std::move(body_))
173 {
174 set_header("Content-Type", get_mime_type(contentType));
175 }
176
177 response& operator=(const response& r) = delete;
178
179 response& operator=(response&& r) noexcept
180 {
181 body = std::move(r.body);
182 code = r.code;
183 headers = std::move(r.headers);
184 completed_ = r.completed_;
185 file_info = std::move(r.file_info);
186 return *this;
187 }
188
189 /// Check if the response has completed (whether response.end() has been called)
190 bool is_completed() const noexcept
191 {
192 return completed_;
193 }
194
195 void clear()
196 {
197 body.clear();
198 code = 200;
199 headers.clear();
200 completed_ = false;
201 file_info = static_file_info{};
202 }
203
204 /// Return a "Temporary Redirect" response.
205
206 ///
207 /// Location can either be a route or a full URL.
208 void redirect(const std::string& location)
209 {
210 code = 307;
211 set_header("Location", location);
212 }
213
214 /// Return a "Permanent Redirect" response.
215
216 ///
217 /// Location can either be a route or a full URL.
218 void redirect_perm(const std::string& location)
219 {
220 code = 308;
221 set_header("Location", location);
222 }
223
224 /// Return a "Found (Moved Temporarily)" response.
225
226 ///
227 /// Location can either be a route or a full URL.
228 void moved(const std::string& location)
229 {
230 code = 302;
231 set_header("Location", location);
232 }
233
234 /// Return a "Moved Permanently" response.
235
236 ///
237 /// Location can either be a route or a full URL.
238 void moved_perm(const std::string& location)
239 {
240 code = 301;
241 set_header("Location", location);
242 }
243
244 void write(const std::string& body_part)
245 {
246 body += body_part;
247 }
248
249 /// Set the response completion flag and call the handler (to send the response).
250 void end()
251 {
252 if (!completed_)
253 {
254 completed_ = true;
255 if (skip_body)
256 {
257 set_header("Content-Length", std::to_string(body.size()));
258 body = "";
260 }
261 if (complete_request_handler_)
262 {
263 complete_request_handler_();
264 manual_length_header = false;
265 skip_body = false;
266 }
267 }
268 }
269
270 /// Same as end() except it adds a body part right before ending.
271 void end(const std::string& body_part)
272 {
273 body += body_part;
274 end();
275 }
276
277 /// Check if the connection is still alive (usually by checking the socket status).
278 bool is_alive()
279 {
280 return is_alive_helper_ && is_alive_helper_();
281 }
282
283 /// Check whether the response has a static file defined.
285 {
286 return file_info.path.size();
287 }
288
289 /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
290
291 ///
292 /// Either a static file or a string body can be returned as 1 response.
294 {
295 std::string path = "";
296 struct stat statbuf;
297 int statResult;
298 };
299
300 /// Return a static file as the response body, the content_type may be specified explicitly.
301 void set_static_file_info(std::string path, std::string content_type = "")
302 {
303 utility::sanitize_filename(path);
304 set_static_file_info_unsafe(path, content_type);
305 }
306
307 /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead),
308 /// the content_type may be specified explicitly.
309 void set_static_file_info_unsafe(std::string path, std::string content_type = "")
310 {
311 file_info.path = path;
312 file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
313#ifdef CROW_ENABLE_COMPRESSION
314 compressed = false;
315#endif
316 if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
317 {
318 code = 200;
319 this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
320
321 if (content_type.empty())
322 {
323 std::size_t last_dot = path.find_last_of('.');
324 std::string extension = path.substr(last_dot + 1);
325
326 if (!extension.empty())
327 {
328 this->add_header("Content-Type", get_mime_type(extension));
329 }
330 }
331 else
332 {
333 this->add_header("Content-Type", content_type);
334 }
335 }
336 else
337 {
338 code = 404;
339 file_info.path.clear();
340 }
341 }
342
343 private:
344 void write_header_into_buffer(std::vector<asio::const_buffer>& buffers, std::string& content_length_buffer, bool add_keep_alive, const std::string& server_name)
345 {
346 // TODO(EDev): HTTP version in status codes should be dynamic
347 // Keep in sync with common.h/status
348 static std::unordered_map<int, std::string> statusCodes = {
349 {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
350 {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
351
352 {status::OK, "HTTP/1.1 200 OK\r\n"},
353 {status::CREATED, "HTTP/1.1 201 Created\r\n"},
354 {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
355 {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
356 {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
357 {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
358 {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
359 {status::WEBDAV_MULTI_STATUS, "HTTP/1.1 207 Multi-Status\r\n"},
360 {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
361 {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
362 {status::FOUND, "HTTP/1.1 302 Found\r\n"},
363 {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
364 {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
365 {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
366 {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
367
368 {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
369 {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
370 {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
371 {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
372 {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
373 {status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"},
374 {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
375 {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
376 {status::GONE, "HTTP/1.1 410 Gone\r\n"},
377 {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
378 {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
379 {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
380 {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
381 {status::WEBDAV_PRECONDITION_FAILED, "HTTP/1.1 412 Precondition Failed\r\n"},
382 {status::WEBDAV_REQUEST_URI_TOO_LONG, "HTTP/1.1 414 Request-URI Too Long\r\n"},
383 {status::WEBDAV_UNPROCESSABLE_ENTITY, "HTTP/1.1 422 Unprocessable Entity\r\n"},
384 {status::WEBDAV_LOCKED, "HTTP/1.1 423 Locked\r\n"},
385 {status::WEBDAV_FAILED_DEPENDENCY, "HTTP/1.1 424 Failed Dependency\r\n"},
386 {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
387 {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
388 {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
389
390 {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
391 {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
392 {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
393 {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
394 {status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"},
395 {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
396 {status::WEBDAV_INSUFFICIENT_STORAGE, "HTTP/1.1 507 Insufficient Storage\r\n"},
397 };
398
399 static const std::string seperator = ": ";
400
401 buffers.clear();
402 buffers.reserve(4 * (headers.size() + 5) + 3);
403
404 if (!statusCodes.count(code))
405 {
406 CROW_LOG_WARNING << this << " status code "
407 << "(" << code << ")"
408 << " not defined, returning 500 instead";
409 code = 500;
410 }
411
412 auto& status = statusCodes.find(code)->second;
413 buffers.emplace_back(status.data(), status.size());
414
415 if (code >= 400 && body.empty())
416 body = statusCodes[code].substr(9);
417
418 for (auto& kv : headers)
419 {
420 buffers.emplace_back(kv.first.data(), kv.first.size());
421 buffers.emplace_back(seperator.data(), seperator.size());
422 buffers.emplace_back(kv.second.data(), kv.second.size());
423 buffers.emplace_back(crlf.data(), crlf.size());
424 }
425
426 if (!manual_length_header && !headers.count("content-length"))
427 {
428 content_length_buffer = std::to_string(body.size());
429 static std::string content_length_tag = "Content-Length: ";
430 buffers.emplace_back(content_length_tag.data(), content_length_tag.size());
431 buffers.emplace_back(content_length_buffer.data(), content_length_buffer.size());
432 buffers.emplace_back(crlf.data(), crlf.size());
433 }
434 if (!headers.count("server") && !server_name.empty())
435 {
436 static std::string server_tag = "Server: ";
437 buffers.emplace_back(server_tag.data(), server_tag.size());
438 buffers.emplace_back(server_name.data(), server_name.size());
439 buffers.emplace_back(crlf.data(), crlf.size());
440 }
441 /*if (!headers.count("date"))
442 {
443 static std::string date_tag = "Date: ";
444 date_str_ = get_cached_date_str();
445 buffers.emplace_back(date_tag.data(), date_tag.size());
446 buffers.emplace_back(date_str_.data(), date_str_.size());
447 buffers.emplace_back(crlf.data(), crlf.size());
448 }*/
449 if (add_keep_alive)
450 {
451 static std::string keep_alive_tag = "Connection: Keep-Alive";
452 buffers.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
453 buffers.emplace_back(crlf.data(), crlf.size());
454 }
455
456 buffers.emplace_back(crlf.data(), crlf.size());
457 }
458
459 bool completed_{};
460 std::function<void()> complete_request_handler_;
461 std::function<bool()> is_alive_helper_;
462 static_file_info file_info;
463 };
464} // namespace crow
An HTTP connection.
Definition http_connection.h:48
Handles matching requests to existing rules and upgrade requests.
Definition routing.h:1268
A websocket connection.
Definition websocket.h:111
The main namespace of the library. In this namespace is defined the most important classes and functi...
void sanitize_header_value(std::string &s)
Remove CR (\r) and LF ( ) characters from a header name or value to prevent header injection.
Definition http_request.h:25
const std::string & get_header_value(const ci_map &headers, const std::string &key)
Find and return the value associated with the key. (returns an empty string if nothing is found)
Definition http_request.h:33
This constains metadata (coming from the stat command) related to any static files associated with th...
Definition http_response.h:294
HTTP response.
Definition http_response.h:40
void end(const std::string &body_part)
Same as end() except it adds a body part right before ending.
Definition http_response.h:271
void add_header(std::string key, std::string value)
Add a new header to the response.
Definition http_response.h:69
void set_static_file_info(std::string path, std::string content_type="")
Return a static file as the response body, the content_type may be specified explicitly.
Definition http_response.h:301
bool skip_body
Whether this is a response to a HEAD request.
Definition http_response.h:56
void set_static_file_info_unsafe(std::string path, std::string content_type="")
Definition http_response.h:309
void moved(const std::string &location)
Return a "Found (Moved Temporarily)" response.
Definition http_response.h:228
bool manual_length_header
Whether Crow should automatically add a "Content-Length" header.
Definition http_response.h:57
void redirect_perm(const std::string &location)
Return a "Permanent Redirect" response.
Definition http_response.h:218
void moved_perm(const std::string &location)
Return a "Moved Permanently" response.
Definition http_response.h:238
bool is_completed() const noexcept
Check if the response has completed (whether response.end() has been called)
Definition http_response.h:190
int code
The Status code for the response.
Definition http_response.h:49
void set_header(std::string key, std::string value)
Set the value of an existing header in the response.
Definition http_response.h:60
void end()
Set the response completion flag and call the handler (to send the response).
Definition http_response.h:250
void redirect(const std::string &location)
Return a "Temporary Redirect" response.
Definition http_response.h:208
bool is_alive()
Check if the connection is still alive (usually by checking the socket status).
Definition http_response.h:278
ci_map headers
HTTP headers.
Definition http_response.h:51
std::string body
The actual payload containing the response data.
Definition http_response.h:50
bool compressed
If compression is enabled and this is false, the individual response will not be compressed.
Definition http_response.h:54
bool is_static_type()
Check whether the response has a static file defined.
Definition http_response.h:284