208 lines
6.2 KiB
C
Raw Normal View History

2019-02-10 21:46:39 -05:00
#pragma once
#include <winhttp.h>
2020-12-14 06:26:01 -07:00
#include <variant>
2019-02-10 21:46:39 -05:00
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
2019-06-13 04:01:29 -04:00
struct HttpRequest
2019-02-10 21:46:39 -05:00
{
2019-06-13 04:01:29 -04:00
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,
2019-06-13 04:01:29 -04:00
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
const wchar_t* httpVersion = NULL,
const wchar_t** acceptTypes = NULL
2019-06-13 04:01:29 -04:00
);
operator bool() { return errorCode == ERROR_SUCCESS; }
std::wstring response;
std::wstring headers;
2019-06-13 04:01:29 -04:00
InternetHandle connection = NULL;
InternetHandle request = NULL;
DWORD errorCode = ERROR_SUCCESS;
2019-06-05 20:26:50 -04:00
};
2019-06-13 04:01:29 -04:00
std::wstring Escape(const std::wstring& text);
2020-12-14 06:26:01 -07:00
std::string Escape(const std::string& text);
namespace JSON
{
2020-12-14 06:26:01 -07:00
inline std::wstring UTF(int charCode)
{
return { (wchar_t)charCode };
}
template <typename C>
std::pair<std::basic_string<C>, int> Unescape(std::basic_string_view<C> text)
{
std::basic_string<C> 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];
2020-12-14 07:48:02 -07:00
if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5]))
2020-12-14 06:26:01 -07:00
{
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<char, char>{ { '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 <typename C>
std::basic_string<C> Escape(std::basic_string<C> 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 <typename C>
struct Value : private std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
{
using std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
explicit operator bool() const { return index(); }
bool IsNull() const { return index() == 1; }
auto Boolean() const { return std::get_if<bool>(this); }
auto String() const { return std::get_if<std::basic_string<C>>(this); }
auto Array() const { return std::get_if<std::vector<Value<C>>>(this); }
auto Object() const { return std::get_if<std::unordered_map<std::basic_string<C>, Value<C>>>(this); }
const Value<C>& operator[](std::basic_string<C> key) const
{
static const Value<C> failure;
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
return failure;
}
const Value<C>& operator[](int i) const
{
static const Value<C> failure;
if (auto array = Array()) if (i < array->size()) return array->at(i);
return failure;
}
};
template <typename C>
Value<C> Parse(std::basic_string_view<C> 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<C>::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<C>::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<C>::compare(text.data() + i, falseStr, std::size(falseStr)) == 0) return i += std::size(falseStr), false;
else return {};
if (ch == '-' || (ch >= '0' && ch <= '9'))
{
2020-12-14 07:48:02 -07:00
// no numbers currently used, add an actual parser when needed
2020-12-14 06:26:01 -07:00
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<Value<C>> array;
while (true)
{
i += 1;
if (SkipWhitespace()) return {};
if (ch == ']') return i += 1, Value<C>(array);
if (!array.emplace_back(Parse(text, i, depth + 1))) return {};
if (SkipWhitespace()) return {};
if (ch == ']') return i += 1, Value<C>(array);
if (ch != ',') return {};
}
}
if (ch == '{')
{
std::unordered_map<std::basic_string<C>, Value<C>> object;
while (true)
{
i += 1;
if (SkipWhitespace()) return {};
if (ch == '}') return i += 1, Value<C>(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<C>(object);
if (ch != ',') return {};
}
}
return {};
}
template <typename C>
Value<C> Parse(const std::basic_string<C>& text)
{
int64_t start = 0;
return Parse((std::basic_string_view<C>)text, start, 0);
}
}