#pragma once #include #include using InternetHandle = AutoHandle>; struct HttpRequest { HttpRequest( const wchar_t* agentName, const wchar_t* serverName, const wchar_t* action, const wchar_t* objectName, std::string body = "", const wchar_t* headers = NULL, const wchar_t* referrer = NULL, DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE, const wchar_t* httpVersion = NULL, const wchar_t** acceptTypes = NULL ); operator bool() { return errorCode == ERROR_SUCCESS; } std::wstring response; std::wstring headers; InternetHandle connection = NULL; InternetHandle request = NULL; DWORD errorCode = ERROR_SUCCESS; }; std::wstring Escape(const std::wstring& text); std::string Escape(const std::string& text); namespace JSON { inline std::wstring UTF(int charCode) { return { (wchar_t)charCode }; } template std::pair, int> Unescape(std::basic_string_view text) { std::basic_string unescaped; int i = 0; for (; i < text.size(); ++i) { char ch = text[i]; if (ch == '"') return { unescaped, i + 1 }; if (ch == '\\') { ch = text[i + 1]; if (ch == 'u' && std::isxdigit(text[i + 2]) && std::isxdigit(text[i + 3]) && std::isxdigit(text[i + 4]) && std::isxdigit(text[i + 5])) { char charCode[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 }; unescaped += UTF(strtol(charCode, nullptr, 16)); i += 5; continue; } for (auto [original, value] : Array{ { 'b', '\b' }, {'f', '\f'}, {'n', '\n'}, {'r', '\r'}, {'t', '\t'} }) if (ch == original) { unescaped.push_back(value); goto replaced; } unescaped.push_back(ch); replaced: i += 1; } else unescaped.push_back(ch); } return { unescaped, i }; } template std::basic_string Escape(std::basic_string text) { int oldSize = text.size(); text.resize(text.size() + std::count_if(text.begin(), text.end(), [](auto ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; })); auto out = text.rbegin(); for (int i = oldSize - 1; i >= 0; --i) { if (text[i] == '\n') *out++ = 'n'; else if (text[i] == '\t') *out++ = 't'; else if (text[i] == '\r') *out++ = 'r'; else if (text[i] == '\\' || text[i] == '"') *out++ = text[i]; else { *out++ = text[i]; continue; } *out++ = '\\'; } text.erase(std::remove_if(text.begin(), text.end(), [](uint64_t ch) { return ch < 0x20 || ch == 0x7f; }), text.end()); return text; } template struct Value : private std::variant, std::vector>, std::unordered_map, Value>> { using std::variant, std::vector>, std::unordered_map, Value>>::variant; explicit operator bool() const { return index(); } bool IsNull() const { return index() == 1; } auto Boolean() const { return std::get_if(this); } auto String() const { return std::get_if>(this); } auto Array() const { return std::get_if>>(this); } auto Object() const { return std::get_if, Value>>(this); } const Value& operator[](std::basic_string key) const { static const Value failure; if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second; return failure; } const Value& operator[](int i) const { static const Value failure; if (auto array = Array()) if (i < array->size()) return array->at(i); return failure; } }; template Value Parse(std::basic_string_view text, int64_t& i, int depth) { if (depth > 25) return {}; C ch; auto SkipWhitespace = [&] { while (i < text.size() && (text[i] == ' ' || text[i] == '\n' || text[i] == '\r' || text[i] == '\t')) ++i; if (i >= text.size()) return true; ch = text[i]; return false; }; auto ExtractString = [&] { i += 1; auto [string, length] = Unescape(text.substr(i)); i += length; return string; }; if (SkipWhitespace()) return {}; static C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' }; if (ch == nullStr[0]) if (std::char_traits::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), std::nullopt; else return {}; if (ch == trueStr[0]) if (std::char_traits::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true; else return {}; if (ch == falseStr[0]) if (std::char_traits::compare(text.data() + i, falseStr, std::size(falseStr)) == 0) return i += std::size(falseStr), false; else return {}; if (ch == '-' || (ch >= '0' && ch <= '9')) { // no numbers currently used, add a actual parser when needed while (i < text.size() && ((text[i] >= '0' && text[i] <= '9') || text[i] == '-' || text[i] == '+' || text[i] == 'e' || text[i] == 'E' || text[i] == '.')) ++i; return 0.0; } if (ch == '"') return ExtractString(); if (ch == '[') { std::vector> array; while (true) { i += 1; if (SkipWhitespace()) return {}; if (ch == ']') return i += 1, Value(array); if (!array.emplace_back(Parse(text, i, depth + 1))) return {}; if (SkipWhitespace()) return {}; if (ch == ']') return i += 1, Value(array); if (ch != ',') return {}; } } if (ch == '{') { std::unordered_map, Value> object; while (true) { i += 1; if (SkipWhitespace()) return {}; if (ch == '}') return i += 1, Value(object); if (ch != '"') return {}; auto key = ExtractString(); if (SkipWhitespace() || ch != ':') return {}; i += 1; if (!(object[std::move(key)] = Parse(text, i, depth + 1))) return {}; if (SkipWhitespace()) return {}; if (ch == '}') return i += 1, Value(object); if (ch != ',') return {}; } } return {}; } template Value Parse(const std::basic_string& text) { int64_t start = 0; return Parse((std::basic_string_view)text, start, 0); } }