RDFS
The Rice Comp413 2017 class' continuation on the work of the 2016 RDFS.
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
utility.h
1 #ifndef SIMPLE_WEB_UTILITY_HPP
2 #define SIMPLE_WEB_UTILITY_HPP
3 
4 #include "status_code.h"
5 #include <atomic>
6 #include <iostream>
7 #include <memory>
8 #include <string>
9 #include <unordered_map>
10 
11 namespace SimpleWeb {
12  inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
13  return str1.size() == str2.size() &&
14  std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
15  return tolower(a) == tolower(b);
16  });
17  }
19  public:
20  bool operator()(const std::string &str1, const std::string &str2) const noexcept {
21  return case_insensitive_equal(str1, str2);
22  }
23  };
24  // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
26  public:
27  std::size_t operator()(const std::string &str) const noexcept {
28  std::size_t h = 0;
29  std::hash<int> hash;
30  for(auto c : str)
31  h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
32  return h;
33  }
34  };
35 
36  using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
37 
39  class Percent {
40  public:
42  static std::string encode(const std::string &value) noexcept {
43  static auto hex_chars = "0123456789ABCDEF";
44 
45  std::string result;
46  result.reserve(value.size()); // Minimum size of result
47 
48  for(auto &chr : value) {
49  if(chr == ' ')
50  result += '+';
51  else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
52  result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
53  else
54  result += chr;
55  }
56 
57  return result;
58  }
59 
61  static std::string decode(const std::string &value) noexcept {
62  std::string result;
63  result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
64 
65  for(std::size_t i = 0; i < value.size(); ++i) {
66  auto &chr = value[i];
67  if(chr == '%' && i + 2 < value.size()) {
68  auto hex = value.substr(i + 1, 2);
69  auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
70  result += decoded_chr;
71  i += 2;
72  }
73  else if(chr == '+')
74  result += ' ';
75  else
76  result += chr;
77  }
78 
79  return result;
80  }
81  };
82 
84  class QueryString {
85  public:
87  static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
88  std::string result;
89 
90  bool first = true;
91  for(auto &field : fields) {
92  result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
93  first = false;
94  }
95 
96  return result;
97  }
98 
100  static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
101  CaseInsensitiveMultimap result;
102 
103  if(query_string.empty())
104  return result;
105 
106  std::size_t name_pos = 0;
107  auto name_end_pos = std::string::npos;
108  auto value_pos = std::string::npos;
109  for(std::size_t c = 0; c < query_string.size(); ++c) {
110  if(query_string[c] == '&') {
111  auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
112  if(!name.empty()) {
113  auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
114  result.emplace(std::move(name), Percent::decode(value));
115  }
116  name_pos = c + 1;
117  name_end_pos = std::string::npos;
118  value_pos = std::string::npos;
119  }
120  else if(query_string[c] == '=') {
121  name_end_pos = c;
122  value_pos = c + 1;
123  }
124  }
125  if(name_pos < query_string.size()) {
126  auto name = query_string.substr(name_pos, name_end_pos - name_pos);
127  if(!name.empty()) {
128  auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
129  result.emplace(std::move(name), Percent::decode(value));
130  }
131  }
132 
133  return result;
134  }
135  };
136 
137  class HttpHeader {
138  public:
140  static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
141  CaseInsensitiveMultimap result;
142  std::string line;
143  getline(stream, line);
144  std::size_t param_end;
145  while((param_end = line.find(':')) != std::string::npos) {
146  std::size_t value_start = param_end + 1;
147  if(value_start < line.size()) {
148  if(line[value_start] == ' ')
149  value_start++;
150  if(value_start < line.size())
151  result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
152  }
153 
154  getline(stream, line);
155  }
156  return result;
157  }
158  };
159 
161  public:
163  static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
164  header.clear();
165  std::string line;
166  getline(stream, line);
167  std::size_t method_end;
168  if((method_end = line.find(' ')) != std::string::npos) {
169  method = line.substr(0, method_end);
170 
171  std::size_t query_start = std::string::npos;
172  std::size_t path_and_query_string_end = std::string::npos;
173  for(std::size_t i = method_end + 1; i < line.size(); ++i) {
174  if(line[i] == '?' && (i + 1) < line.size())
175  query_start = i + 1;
176  else if(line[i] == ' ') {
177  path_and_query_string_end = i;
178  break;
179  }
180  }
181  if(path_and_query_string_end != std::string::npos) {
182  if(query_start != std::string::npos) {
183  path = line.substr(method_end + 1, query_start - method_end - 2);
184  query_string = line.substr(query_start, path_and_query_string_end - query_start);
185  }
186  else
187  path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
188 
189  std::size_t protocol_end;
190  if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
191  if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
192  return false;
193  version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
194  }
195  else
196  return false;
197 
198  header = HttpHeader::parse(stream);
199  }
200  else
201  return false;
202  }
203  else
204  return false;
205  return true;
206  }
207  };
208 
210  public:
212  static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
213  header.clear();
214  std::string line;
215  getline(stream, line);
216  std::size_t version_end = line.find(' ');
217  if(version_end != std::string::npos) {
218  if(5 < line.size())
219  version = line.substr(5, version_end - 5);
220  else
221  return false;
222  if((version_end + 1) < line.size())
223  status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
224  else
225  return false;
226 
227  header = HttpHeader::parse(stream);
228  }
229  else
230  return false;
231  return true;
232  }
233  };
234 
236  public:
239  static CaseInsensitiveMultimap parse(const std::string &line) {
240  CaseInsensitiveMultimap result;
241 
242  std::size_t para_start_pos = 0;
243  std::size_t para_end_pos = std::string::npos;
244  std::size_t value_start_pos = std::string::npos;
245  for(std::size_t c = 0; c < line.size(); ++c) {
246  if(para_start_pos != std::string::npos) {
247  if(para_end_pos == std::string::npos) {
248  if(line[c] == ';') {
249  result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string());
250  para_start_pos = std::string::npos;
251  }
252  else if(line[c] == '=')
253  para_end_pos = c;
254  }
255  else {
256  if(value_start_pos == std::string::npos) {
257  if(line[c] == '"' && c + 1 < line.size())
258  value_start_pos = c + 1;
259  }
260  else if(line[c] == '"') {
261  result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos));
262  para_start_pos = std::string::npos;
263  para_end_pos = std::string::npos;
264  value_start_pos = std::string::npos;
265  }
266  }
267  }
268  else if(line[c] != ' ' && line[c] != ';')
269  para_start_pos = c;
270  }
271  if(para_start_pos != std::string::npos && para_end_pos == std::string::npos)
272  result.emplace(line.substr(para_start_pos), std::string());
273 
274  return result;
275  }
276  };
277 } // namespace SimpleWeb
278 
279 #ifdef __SSE2__
280 #include <emmintrin.h>
281 namespace SimpleWeb {
282  inline void spin_loop_pause() noexcept { _mm_pause(); }
283 } // namespace SimpleWeb
284 // TODO: need verification that the following checks are correct:
285 #elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
286 #include <intrin.h>
287 namespace SimpleWeb {
288  inline void spin_loop_pause() noexcept { _mm_pause(); }
289 } // namespace SimpleWeb
290 #else
291 namespace SimpleWeb {
292  inline void spin_loop_pause() noexcept {}
293 } // namespace SimpleWeb
294 #endif
295 
296 namespace SimpleWeb {
298  class ScopeRunner {
300  std::atomic<long> count;
301 
302  public:
303  class SharedLock {
304  friend class ScopeRunner;
305  std::atomic<long> &count;
306  SharedLock(std::atomic<long> &count) noexcept : count(count) {}
307  SharedLock &operator=(const SharedLock &) = delete;
308  SharedLock(const SharedLock &) = delete;
309 
310  public:
311  ~SharedLock() noexcept {
312  count.fetch_sub(1);
313  }
314  };
315 
316  ScopeRunner() noexcept : count(0) {}
317 
319  std::unique_ptr<SharedLock> continue_lock() noexcept {
320  long expected = count;
321  while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
322  spin_loop_pause();
323 
324  if(expected < 0)
325  return nullptr;
326  else
327  return std::unique_ptr<SharedLock>(new SharedLock(count));
328  }
329 
331  void stop() noexcept {
332  long expected = 0;
333  while(!count.compare_exchange_weak(expected, -1)) {
334  if(expected < 0)
335  return;
336  expected = 0;
337  spin_loop_pause();
338  }
339  }
340  };
341 } // namespace SimpleWeb
342 
343 #endif // SIMPLE_WEB_UTILITY_HPP
static std::string create(const CaseInsensitiveMultimap &fields) noexcept
Returns query string created from given field names and values.
Definition: utility.h:87
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept
Parse status line and header fields.
Definition: utility.h:212
Definition: utility.h:303
Definition: utility.h:160
Definition: utility.h:25
Definition: utility.h:209
Definition: utility.h:137
Query string creation and parsing.
Definition: utility.h:84
void stop() noexcept
Blocks until all shared locks are released, then prevents future shared locks.
Definition: utility.h:331
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept
Returns query keys with percent-decoded values.
Definition: utility.h:100
Definition: utility.h:18
std::unique_ptr< SharedLock > continue_lock() noexcept
Returns nullptr if scope should be exited, or a shared lock otherwise.
Definition: utility.h:319
static std::string decode(const std::string &value) noexcept
Returns percent-decoded string.
Definition: utility.h:61
static std::string encode(const std::string &value) noexcept
Returns percent-encoded string.
Definition: utility.h:42
Definition: utility.h:235
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept
Parse header fields.
Definition: utility.h:140
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept
Parse request line and header fields.
Definition: utility.h:163
Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
Definition: utility.h:298
static CaseInsensitiveMultimap parse(const std::string &line)
Definition: utility.h:239
Percent encoding and decoding.
Definition: utility.h:39