#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, DWORD port = INTERNET_DEFAULT_PORT, 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 { template std::basic_string Escape(std::basic_string text) { int oldSize = text.size(); text.resize(text.size() + std::count_if(text.begin(), text.end(), [](C 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 UTF {}; template <> struct UTF { inline static std::wstring FromCodepoint(unsigned codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs }; 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 Number() 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 { if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second; return failure; } const Value& operator[](int i) const { if (auto array = Array()) if (i < array->size()) return array->at(i); return failure; } static const Value failure; }; template const Value Value::failure; template Value Parse(const std::basic_string& text, int64_t& i, int depth) { if (depth > maxDepth) 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 = [&] { std::basic_string unescaped; i += 1; for (; i < text.size(); ++i) { auto ch = text[i]; if (ch == '"') return i += 1, unescaped; if (ch == '\\') { ch = text[i + 1]; if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5])) { char charCode[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 }; unescaped += UTF::FromCodepoint(strtoul(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; }; 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), nullptr; 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')) { std::string number; for (; i < text.size() && ((text[i] >= '0' && text[i] <= '9') || text[i] == '-' || text[i] == '+' || text[i] == 'e' || text[i] == 'E' || text[i] == '.'); ++i) number.push_back(text[i]); return strtod(number.c_str(), NULL); } 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(text, start, 0); } }