identt
Utility.hpp
Go to the documentation of this file.
1 
33 #ifndef _IDENTT_HTTP_UTILITY_HPP_
34 #define _IDENTT_HTTP_UTILITY_HPP_
35 
36 #include "StatusCode.hpp"
37 #include <atomic>
38 #include <cstdlib>
39 #include <iostream>
40 #include <memory>
41 #include <string>
42 #include <unordered_map>
43 #include <chrono>
44 #include <ctime>
45 #include <mutex>
46 
47 #ifndef DEPRECATED
48 #if defined(__GNUC__) || defined(__clang__)
49 #define DEPRECATED __attribute__((deprecated))
50 #elif defined(_MSC_VER)
51 #define DEPRECATED __declspec(deprecated)
52 #else
53 #define DEPRECATED
54 #endif
55 #endif
56 
57 #if __cplusplus > 201402L || _MSVC_LANG > 201402L
58 #include <string_view>
59 namespace identt {
60 namespace http {
61 using string_view = std::string_view;
62 }
63 }
64 #else
65 #include <boost/utility/string_ref.hpp>
66 namespace identt {
67 namespace http {
68 using string_view = boost::string_ref;
69 }
70 }
71 #endif
72 
73 namespace identt {
74 namespace http {
75 
76 inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept
77 {
78  return str1.size() == str2.size() &&
79  std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
80  return tolower(a) == tolower(b);
81  });
82 }
84 public:
85  bool operator()(const std::string &str1, const std::string &str2) const noexcept
86  {
87  return case_insensitive_equal(str1, str2);
88  }
89 };
90 // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
92 public:
93  std::size_t operator()(const std::string &str) const noexcept
94  {
95  std::size_t h = 0;
96  std::hash<int> hash;
97  for(auto c : str)
98  h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
99  return h;
100  }
101 };
102 
103 using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
104 
106 class Percent {
107 public:
109  static std::string encode(const std::string &value) noexcept
110  {
111  static auto hex_chars = "0123456789ABCDEF";
112 
113  std::string result;
114  result.reserve(value.size()); // Minimum size of result
115 
116  for(auto &chr : value) {
117  if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || chr == '-' || chr == '.' || chr == '_' || chr == '~'))
118  result += std::string("%") + hex_chars[static_cast<unsigned char>(chr) >> 4] + hex_chars[static_cast<unsigned char>(chr) & 15];
119  else
120  result += chr;
121  }
122 
123  return result;
124  }
125 
127  static std::string decode(const std::string &value) noexcept
128  {
129  std::string result;
130  result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
131 
132  for(std::size_t i = 0; i < value.size(); ++i) {
133  auto &chr = value[i];
134  if(chr == '%' && i + 2 < value.size()) {
135  auto hex = value.substr(i + 1, 2);
136  auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
137  result += decoded_chr;
138  i += 2;
139  }
140  else if(chr == '+')
141  result += ' ';
142  else
143  result += chr;
144  }
145 
146  return result;
147  }
148 };
149 
151 class QueryString {
152 public:
154  static std::string create(const CaseInsensitiveMultimap &fields) noexcept
155  {
156  std::string result;
157 
158  bool first = true;
159  for(auto &field : fields) {
160  result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
161  first = false;
162  }
163 
164  return result;
165  }
166 
168  static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept
169  {
170  CaseInsensitiveMultimap result;
171 
172  if(query_string.empty())
173  return result;
174 
175  std::size_t name_pos = 0;
176  auto name_end_pos = std::string::npos;
177  auto value_pos = std::string::npos;
178  for(std::size_t c = 0; c < query_string.size(); ++c) {
179  if(query_string[c] == '&') {
180  auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
181  if(!name.empty()) {
182  auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
183  result.emplace(std::move(name), Percent::decode(value));
184  }
185  name_pos = c + 1;
186  name_end_pos = std::string::npos;
187  value_pos = std::string::npos;
188  }
189  else if(query_string[c] == '=') {
190  name_end_pos = c;
191  value_pos = c + 1;
192  }
193  }
194  if(name_pos < query_string.size()) {
195  auto name = query_string.substr(name_pos, name_end_pos - name_pos);
196  if(!name.empty()) {
197  auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
198  result.emplace(std::move(name), Percent::decode(value));
199  }
200  }
201 
202  return result;
203  }
204 };
205 
206 class HttpHeader {
207 public:
209  static CaseInsensitiveMultimap parse(std::istream &stream) noexcept
210  {
211  CaseInsensitiveMultimap result;
212  std::string line;
213  std::size_t param_end;
214  while(getline(stream, line) && (param_end = line.find(':')) != std::string::npos) {
215  std::size_t value_start = param_end + 1;
216  while(value_start + 1 < line.size() && line[value_start] == ' ')
217  ++value_start;
218  if(value_start < line.size())
219  result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - (line.back() == '\r' ? 1 : 0)));
220  }
221  return result;
222  }
223 
224  class FieldValue {
225  public:
227  public:
230  static CaseInsensitiveMultimap parse(const std::string &value)
231  {
232  CaseInsensitiveMultimap result;
233 
234  std::size_t name_start_pos = std::string::npos;
235  std::size_t name_end_pos = std::string::npos;
236  std::size_t value_start_pos = std::string::npos;
237  for(std::size_t c = 0; c < value.size(); ++c) {
238  if(name_start_pos == std::string::npos) {
239  if(value[c] != ' ' && value[c] != ';')
240  name_start_pos = c;
241  }
242  else {
243  if(name_end_pos == std::string::npos) {
244  if(value[c] == ';') {
245  result.emplace(value.substr(name_start_pos, c - name_start_pos), std::string());
246  name_start_pos = std::string::npos;
247  }
248  else if(value[c] == '=')
249  name_end_pos = c;
250  }
251  else {
252  if(value_start_pos == std::string::npos) {
253  if(value[c] == '"' && c + 1 < value.size())
254  value_start_pos = c + 1;
255  else
256  value_start_pos = c;
257  }
258  else if(value[c] == '"' || value[c] == ';') {
259  result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, c - value_start_pos)));
260  name_start_pos = std::string::npos;
261  name_end_pos = std::string::npos;
262  value_start_pos = std::string::npos;
263  }
264  }
265  }
266  }
267  if(name_start_pos != std::string::npos) {
268  if(name_end_pos == std::string::npos)
269  result.emplace(value.substr(name_start_pos), std::string());
270  else if(value_start_pos != std::string::npos) {
271  if(value.back() == '"')
272  result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, value.size() - 1)));
273  else
274  result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos)));
275  }
276  }
277 
278  return result;
279  }
280  };
281  };
282 };
283 
285 public:
297  static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept
298  {
299  std::string line;
300  std::size_t method_end;
301  if(getline(stream, line) && (method_end = line.find(' ')) != std::string::npos) {
302  method = line.substr(0, method_end);
303 
304  std::size_t query_start = std::string::npos;
305  std::size_t path_and_query_string_end = std::string::npos;
306  for(std::size_t i = method_end + 1; i < line.size(); ++i) {
307  if(line[i] == '?' && (i + 1) < line.size())
308  query_start = i + 1;
309  else if(line[i] == ' ') {
310  path_and_query_string_end = i;
311  break;
312  }
313  }
314  if(path_and_query_string_end != std::string::npos) {
315  if(query_start != std::string::npos) {
316  path = line.substr(method_end + 1, query_start - method_end - 2);
317  query_string = line.substr(query_start, path_and_query_string_end - query_start);
318  }
319  else
320  path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
321 
322  std::size_t protocol_end;
323  if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
324  if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
325  return false;
326  version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
327  }
328  else
329  return false;
330 
331  header = HttpHeader::parse(stream);
332  }
333  else
334  return false;
335  }
336  else
337  return false;
338  return true;
339  }
340 };
341 
343 public:
353  static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept
354  {
355  std::string line;
356  std::size_t version_end;
357  if(getline(stream, line) && (version_end = line.find(' ')) != std::string::npos) {
358  if(5 < line.size())
359  version = line.substr(5, version_end - 5);
360  else
361  return false;
362  if((version_end + 1) < line.size())
363  status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - (line.back() == '\r' ? 1 : 0));
364  else
365  return false;
366 
367  header = HttpHeader::parse(stream);
368  }
369  else
370  return false;
371  return true;
372  }
373 };
374 
376 class Date {
377 public:
379  static std::string to_string(const std::chrono::system_clock::time_point time_point) noexcept
380  {
381  static std::string result_cache;
382  static std::chrono::system_clock::time_point last_time_point;
383 
384  static std::mutex mutex;
385  std::lock_guard<std::mutex> lock(mutex);
386 
387  if(std::chrono::duration_cast<std::chrono::seconds>(time_point - last_time_point).count() == 0 && !result_cache.empty())
388  return result_cache;
389 
390  last_time_point = time_point;
391 
392  std::string result;
393  result.reserve(29);
394 
395  auto time = std::chrono::system_clock::to_time_t(time_point);
396  tm tm;
397 #if defined(_MSC_VER) || defined(__MINGW32__)
398  if(gmtime_s(&tm, &time) != 0)
399  return {};
400  auto gmtime = &tm;
401 #else
402  auto gmtime = gmtime_r(&time, &tm);
403  if(!gmtime)
404  return {};
405 #endif
406 
407  switch(gmtime->tm_wday) {
408  case 0:
409  result += "Sun, ";
410  break;
411  case 1:
412  result += "Mon, ";
413  break;
414  case 2:
415  result += "Tue, ";
416  break;
417  case 3:
418  result += "Wed, ";
419  break;
420  case 4:
421  result += "Thu, ";
422  break;
423  case 5:
424  result += "Fri, ";
425  break;
426  case 6:
427  result += "Sat, ";
428  break;
429  }
430 
431  result += gmtime->tm_mday < 10 ? '0' : static_cast<char>(gmtime->tm_mday / 10 + 48);
432  result += static_cast<char>(gmtime->tm_mday % 10 + 48);
433 
434  switch(gmtime->tm_mon) {
435  case 0:
436  result += " Jan ";
437  break;
438  case 1:
439  result += " Feb ";
440  break;
441  case 2:
442  result += " Mar ";
443  break;
444  case 3:
445  result += " Apr ";
446  break;
447  case 4:
448  result += " May ";
449  break;
450  case 5:
451  result += " Jun ";
452  break;
453  case 6:
454  result += " Jul ";
455  break;
456  case 7:
457  result += " Aug ";
458  break;
459  case 8:
460  result += " Sep ";
461  break;
462  case 9:
463  result += " Oct ";
464  break;
465  case 10:
466  result += " Nov ";
467  break;
468  case 11:
469  result += " Dec ";
470  break;
471  }
472 
473  auto year = gmtime->tm_year + 1900;
474  result += static_cast<char>(year / 1000 + 48);
475  result += static_cast<char>((year / 100) % 10 + 48);
476  result += static_cast<char>((year / 10) % 10 + 48);
477  result += static_cast<char>(year % 10 + 48);
478  result += ' ';
479 
480  result += gmtime->tm_hour < 10 ? '0' : static_cast<char>(gmtime->tm_hour / 10 + 48);
481  result += static_cast<char>(gmtime->tm_hour % 10 + 48);
482  result += ':';
483 
484  result += gmtime->tm_min < 10 ? '0' : static_cast<char>(gmtime->tm_min / 10 + 48);
485  result += static_cast<char>(gmtime->tm_min % 10 + 48);
486  result += ':';
487 
488  result += gmtime->tm_sec < 10 ? '0' : static_cast<char>(gmtime->tm_sec / 10 + 48);
489  result += static_cast<char>(gmtime->tm_sec % 10 + 48);
490 
491  result += " GMT";
492 
493  result_cache = result;
494  return result;
495  }
496 };
497 
498 } // namespace http
499 } // namespace identt
500 
501 #ifdef __SSE2__
502 #include <emmintrin.h>
503 
504 namespace identt {
505 namespace http {
506 inline void spin_loop_pause() noexcept
507 {
508  _mm_pause();
509 }
510 } // namespace http
511 } // namespace identt
512 // TODO: need verification that the following checks are correct:
513 #elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
514 #include <intrin.h>
515 
516 namespace identt {
517 namespace http {
518 inline void spin_loop_pause() noexcept
519 {
520  _mm_pause();
521 }
522 } // namespace http
523 } // namespace identt
524 #else
525 namespace identt {
526 namespace http {
527 inline void spin_loop_pause() noexcept {}
528 } // namespace http
529 } // namespace identt
530 #endif
531 
532 namespace identt {
533 namespace http {
535 class ScopeRunner {
537  std::atomic<long> count;
538 
539 public:
540  class SharedLock {
541  friend class ScopeRunner;
542  std::atomic<long> &count;
543  SharedLock(std::atomic<long> &count) noexcept : count(count) {}
544  SharedLock &operator=(const SharedLock &) = delete;
545  SharedLock(const SharedLock &) = delete;
546 
547  public:
548  ~SharedLock() noexcept
549  {
550  count.fetch_sub(1);
551  }
552  };
553 
554  ScopeRunner() noexcept : count(0) {}
555 
558  std::unique_ptr<SharedLock> continue_lock() noexcept
559  {
560  long expected = count;
561  while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
562  spin_loop_pause();
563 
564  if(expected < 0)
565  return nullptr;
566  else
567  return std::unique_ptr<SharedLock>(new SharedLock(count));
568  }
569 
571  void stop() noexcept
572  {
573  long expected = 0;
574  while(!count.compare_exchange_weak(expected, -1)) {
575  if(expected < 0)
576  return;
577  expected = 0;
578  spin_loop_pause();
579  }
580  }
581 };
582 } // namespace http
583 } // namespace identt
584 
585 #endif // _IDENTT_HTTP_UTILITY_HPP_
Definition: Utility.hpp:91
Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
Definition: Utility.hpp:535
static CaseInsensitiveMultimap parse(const std::string &value)
Parse Set-Cookie or Content-Disposition from given header field value.
Definition: Utility.hpp:230
Date class working with formats specified in RFC 7231 Date/Time Formats.
Definition: Utility.hpp:376
Definition: Utility.hpp:206
Definition: Utility.hpp:540
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept
Returns query keys with percent-decoded values.
Definition: Utility.hpp:168
Definition: Utility.hpp:284
static std::string encode(const std::string &value) noexcept
Returns percent-encoded string.
Definition: Utility.hpp:109
void stop() noexcept
Blocks until all shared locks are released, then prevents future shared locks.
Definition: Utility.hpp:571
Definition: Utility.hpp:342
Definition: Utility.hpp:224
Definition: CryptoBase.hpp:49
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept
Parse status line and header fields from a response stream.
Definition: Utility.hpp:353
Definition: Utility.hpp:83
Query string creation and parsing.
Definition: Utility.hpp:151
static std::string to_string(const std::chrono::system_clock::time_point time_point) noexcept
Returns the given std::chrono::system_clock::time_point as a string with the following format: Wed...
Definition: Utility.hpp:379
static std::string create(const CaseInsensitiveMultimap &fields) noexcept
Returns query string created from given field names and values.
Definition: Utility.hpp:154
std::unique_ptr< SharedLock > continue_lock() noexcept
Returns nullptr if scope should be exited, or a shared lock otherwise.
Definition: Utility.hpp:558
Percent encoding and decoding.
Definition: Utility.hpp:106
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept
Parse header fields from stream.
Definition: Utility.hpp:209
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 from a request stream.
Definition: Utility.hpp:297
static std::string decode(const std::string &value) noexcept
Returns percent-decoded string.
Definition: Utility.hpp:127