Crow  1.1
A C++ microframework for the web
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 
25 namespace crow
26 {
27  template<typename Adaptor, typename Handler, typename... Middlewares>
28  class Connection;
29 
30  class Router;
31 
32  /// HTTP response
33  struct response
34  {
35  template<typename Adaptor, typename Handler, typename... Middlewares>
36  friend class crow::Connection;
37 
38  friend class Router;
39 
40  int code{200}; ///< The Status code for the response.
41  std::string body; ///< The actual payload containing the response data.
42  ci_map headers; ///< HTTP headers.
43 
44 #ifdef CROW_ENABLE_COMPRESSION
45  bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
46 #endif
47  bool skip_body = false; ///< Whether this is a response to a HEAD request.
48  bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
49 
50  /// Set the value of an existing header in the response.
51  void set_header(std::string key, std::string value)
52  {
53  headers.erase(key);
54  headers.emplace(std::move(key), std::move(value));
55  }
56 
57  /// Add a new header to the response.
58  void add_header(std::string key, std::string value)
59  {
60  headers.emplace(std::move(key), std::move(value));
61  }
62 
63  const std::string& get_header_value(const std::string& key)
64  {
65  return crow::get_header_value(headers, key);
66  }
67 
68  // naive validation of a mime-type string
69  static bool validate_mime_type(const std::string& candidate) noexcept
70  {
71  // Here we simply check that the candidate type starts with
72  // a valid parent type, and has at least one character afterwards.
73  std::array<std::string, 10> valid_parent_types = {
74  "application/", "audio/", "font/", "example/",
75  "image/", "message/", "model/", "multipart/",
76  "text/", "video/"};
77  for (const std::string& parent : valid_parent_types)
78  {
79  // ensure the candidate is *longer* than the parent,
80  // to avoid unnecessary string comparison and to
81  // reject zero-length subtypes.
82  if (candidate.size() <= parent.size())
83  {
84  continue;
85  }
86  // strncmp is used rather than substr to avoid allocation,
87  // but a string_view approach would be better if Crow
88  // migrates to C++17.
89  if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
90  {
91  return true;
92  }
93  }
94  return false;
95  }
96 
97  // Find the mime type from the content type either by lookup,
98  // or by the content type itself, if it is a valid a mime type.
99  // Defaults to text/plain.
100  static std::string get_mime_type(const std::string& contentType)
101  {
102  const auto mimeTypeIterator = mime_types.find(contentType);
103  if (mimeTypeIterator != mime_types.end())
104  {
105  return mimeTypeIterator->second;
106  }
107  else if (validate_mime_type(contentType))
108  {
109  return contentType;
110  }
111  else
112  {
113  CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
114  return "text/plain";
115  }
116  }
117 
118 
119  // clang-format off
120  response() {}
121  explicit response(int code) : code(code) {}
122  response(std::string body) : body(std::move(body)) {}
123  response(int code, std::string body) : code(code), body(std::move(body)) {}
124  // clang-format on
125  response(returnable&& value)
126  {
127  body = value.dump();
128  set_header("Content-Type", value.content_type);
129  }
130  response(returnable& value)
131  {
132  body = value.dump();
133  set_header("Content-Type", value.content_type);
134  }
135  response(int code, returnable& value):
136  code(code)
137  {
138  body = value.dump();
139  set_header("Content-Type", value.content_type);
140  }
141  response(int code, returnable&& value):
142  code(code), body(value.dump())
143  {
144  set_header("Content-Type", std::move(value.content_type));
145  }
146 
147  response(response&& r)
148  {
149  *this = std::move(r);
150  }
151 
152  response(std::string contentType, std::string body):
153  body(std::move(body))
154  {
155  set_header("Content-Type", get_mime_type(contentType));
156  }
157 
158  response(int code, std::string contentType, std::string body):
159  code(code), body(std::move(body))
160  {
161  set_header("Content-Type", get_mime_type(contentType));
162  }
163 
164  response& operator=(const response& r) = delete;
165 
166  response& operator=(response&& r) noexcept
167  {
168  body = std::move(r.body);
169  code = r.code;
170  headers = std::move(r.headers);
171  completed_ = r.completed_;
172  file_info = std::move(r.file_info);
173  return *this;
174  }
175 
176  /// Check if the response has completed (whether response.end() has been called)
177  bool is_completed() const noexcept
178  {
179  return completed_;
180  }
181 
182  void clear()
183  {
184  body.clear();
185  code = 200;
186  headers.clear();
187  completed_ = false;
188  file_info = static_file_info{};
189  }
190 
191  /// Return a "Temporary Redirect" response.
192 
193  ///
194  /// Location can either be a route or a full URL.
195  void redirect(const std::string& location)
196  {
197  code = 307;
198  set_header("Location", location);
199  }
200 
201  /// Return a "Permanent Redirect" response.
202 
203  ///
204  /// Location can either be a route or a full URL.
205  void redirect_perm(const std::string& location)
206  {
207  code = 308;
208  set_header("Location", location);
209  }
210 
211  /// Return a "Found (Moved Temporarily)" response.
212 
213  ///
214  /// Location can either be a route or a full URL.
215  void moved(const std::string& location)
216  {
217  code = 302;
218  set_header("Location", location);
219  }
220 
221  /// Return a "Moved Permanently" response.
222 
223  ///
224  /// Location can either be a route or a full URL.
225  void moved_perm(const std::string& location)
226  {
227  code = 301;
228  set_header("Location", location);
229  }
230 
231  void write(const std::string& body_part)
232  {
233  body += body_part;
234  }
235 
236  /// Set the response completion flag and call the handler (to send the response).
237  void end()
238  {
239  if (!completed_)
240  {
241  completed_ = true;
242  if (skip_body)
243  {
244  set_header("Content-Length", std::to_string(body.size()));
245  body = "";
246  manual_length_header = true;
247  }
248  if (complete_request_handler_)
249  {
250  complete_request_handler_();
251  manual_length_header = false;
252  skip_body = false;
253  }
254  }
255  }
256 
257  /// Same as end() except it adds a body part right before ending.
258  void end(const std::string& body_part)
259  {
260  body += body_part;
261  end();
262  }
263 
264  /// Check if the connection is still alive (usually by checking the socket status).
265  bool is_alive()
266  {
267  return is_alive_helper_ && is_alive_helper_();
268  }
269 
270  /// Check whether the response has a static file defined.
272  {
273  return file_info.path.size();
274  }
275 
276  /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
277 
278  ///
279  /// Either a static file or a string body can be returned as 1 response.
281  {
282  std::string path = "";
283  struct stat statbuf;
284  int statResult;
285  };
286 
287  /// Return a static file as the response body
288  void set_static_file_info(std::string path)
289  {
290  utility::sanitize_filename(path);
292  }
293 
294  /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead)
295  void set_static_file_info_unsafe(std::string path)
296  {
297  file_info.path = path;
298  file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
299 #ifdef CROW_ENABLE_COMPRESSION
300  compressed = false;
301 #endif
302  if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
303  {
304  std::size_t last_dot = path.find_last_of(".");
305  std::string extension = path.substr(last_dot + 1);
306  code = 200;
307  this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
308 
309  if (!extension.empty())
310  {
311  this->add_header("Content-Type", get_mime_type(extension));
312  }
313  }
314  else
315  {
316  code = 404;
317  file_info.path.clear();
318  }
319  }
320 
321  private:
322  bool completed_{};
323  std::function<void()> complete_request_handler_;
324  std::function<bool()> is_alive_helper_;
325  static_file_info file_info;
326  };
327 } // namespace crow
An HTTP connection.
Definition: http_connection.h:48
Handles matching requests to existing rules and upgrade requests.
Definition: routing.h:1254
This constains metadata (coming from the stat command) related to any static files associated with th...
Definition: http_response.h:281
HTTP response.
Definition: http_response.h:34
void end(const std::string &body_part)
Same as end() except it adds a body part right before ending.
Definition: http_response.h:258
void add_header(std::string key, std::string value)
Add a new header to the response.
Definition: http_response.h:58
bool skip_body
Whether this is a response to a HEAD request.
Definition: http_response.h:47
void moved(const std::string &location)
Return a "Found (Moved Temporarily)" response.
Definition: http_response.h:215
void set_static_file_info(std::string path)
Return a static file as the response body.
Definition: http_response.h:288
bool manual_length_header
Whether Crow should automatically add a "Content-Length" header.
Definition: http_response.h:48
void redirect_perm(const std::string &location)
Return a "Permanent Redirect" response.
Definition: http_response.h:205
void moved_perm(const std::string &location)
Return a "Moved Permanently" response.
Definition: http_response.h:225
bool is_completed() const noexcept
Check if the response has completed (whether response.end() has been called)
Definition: http_response.h:177
int code
The Status code for the response.
Definition: http_response.h:40
void set_header(std::string key, std::string value)
Set the value of an existing header in the response.
Definition: http_response.h:51
void end()
Set the response completion flag and call the handler (to send the response).
Definition: http_response.h:237
void redirect(const std::string &location)
Return a "Temporary Redirect" response.
Definition: http_response.h:195
bool is_alive()
Check if the connection is still alive (usually by checking the socket status).
Definition: http_response.h:265
void set_static_file_info_unsafe(std::string path)
Return a static file as the response body without sanitizing the path (use set_static_file_info inste...
Definition: http_response.h:295
ci_map headers
HTTP headers.
Definition: http_response.h:42
std::string body
The actual payload containing the response data.
Definition: http_response.h:41
bool compressed
If compression is enabled and this is false, the individual response will not be compressed.
Definition: http_response.h:45
bool is_static_type()
Check whether the response has a static file defined.
Definition: http_response.h:271