mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
427 lines
12 KiB
C++
427 lines
12 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "util/URL.h"
|
|
|
|
#include "rbx/debug.h"
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(UseNewUrlClass, true);
|
|
|
|
namespace
|
|
{
|
|
|
|
class RFC3986
|
|
{
|
|
public:
|
|
enum CharClassBit
|
|
{
|
|
CharClassBit_Unreserved = 0x01,
|
|
CharClassBit_PercentEncoded = 0x02,
|
|
CharClassBit_SubDelim = 0x04,
|
|
CharClassBit_HexDigit = 0x08,
|
|
CharClassBit_Pchar = 0x10 |
|
|
CharClassBit_Unreserved |
|
|
CharClassBit_PercentEncoded |
|
|
CharClassBit_SubDelim,
|
|
CharClassBit_RegName =
|
|
CharClassBit_Unreserved |
|
|
CharClassBit_PercentEncoded |
|
|
CharClassBit_SubDelim,
|
|
CharClassBit_Path = 0x20 | CharClassBit_Pchar,
|
|
CharClassBit_QueryOrFragment = 0x40 | CharClassBit_Pchar,
|
|
CharClassBit_Scheme = 0x80
|
|
};
|
|
|
|
static const char* findFirstCharNotInClass(const char* begin, const char* end, CharClassBit charclass);
|
|
|
|
static bool checkStringIsOfClass(const char* begin, const char* end, CharClassBit charclass)
|
|
{
|
|
const char* firstnot = findFirstCharNotInClass(begin, end, charclass);
|
|
return begin != end && firstnot == end;
|
|
}
|
|
|
|
static bool checkStringIsOfClass(const std::string& str, CharClassBit charclass)
|
|
{
|
|
return checkStringIsOfClass(str.c_str(), str.c_str() + str.length(), charclass);
|
|
}
|
|
|
|
static uint8_t charClassBits(char c)
|
|
{
|
|
return charClassTable_[static_cast<unsigned char>(c)];
|
|
}
|
|
|
|
private:
|
|
static const uint8_t charClassTable_[256];
|
|
}; // class RFC3986
|
|
|
|
const uint8_t RFC3986::charClassTable_[256] =
|
|
{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x04, 0x00, 0x00, 0x04, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04, 0x84, 0x04, 0x81, 0x81, 0x60,
|
|
0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x10, 0x04, 0x00, 0x04, 0x00, 0x40,
|
|
0x10, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
|
|
0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
|
|
0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
const char* RFC3986::findFirstCharNotInClass(const char* begin, const char* end, CharClassBit charClass)
|
|
{
|
|
for (; begin != end; ++begin)
|
|
{
|
|
const uint8_t classMatch = charClassBits(*begin) & charClass;
|
|
if (!classMatch)
|
|
return begin;
|
|
if (classMatch & CharClassBit_PercentEncoded)
|
|
{
|
|
if (end - begin < 3 || !(charClassBits(begin[1]) & charClassBits(begin[2]) & CharClassBit_HexDigit))
|
|
return begin;
|
|
begin += 2;
|
|
}
|
|
}
|
|
return end;
|
|
}
|
|
|
|
} // unnamed namespace
|
|
|
|
namespace RBX
|
|
{
|
|
|
|
Url Url::fromString(const char* str)
|
|
{
|
|
Url url;
|
|
|
|
const char* end = str + ::strlen(str);
|
|
|
|
const char* scheme_begin = str;
|
|
const char *host_begin = scheme_begin;
|
|
|
|
if (end == str)
|
|
return url;
|
|
|
|
const char *scheme_end = RFC3986::findFirstCharNotInClass(scheme_begin, end, RFC3986::CharClassBit_Scheme);
|
|
|
|
if (end - scheme_end >= 3 && scheme_end[0] == ':' && scheme_end[1] == '/' && scheme_end[2] == '/')
|
|
{
|
|
url.scheme_ = std::string(scheme_begin, scheme_end);
|
|
host_begin = scheme_end + 3;
|
|
}
|
|
|
|
const char *host_end = strchr(host_begin, '/');
|
|
if (!host_end)
|
|
{
|
|
url.host_ = std::string(host_begin, end);
|
|
url.path_ = "/";
|
|
return url;
|
|
}
|
|
else
|
|
{
|
|
url.host_ = std::string(host_begin, host_end);
|
|
}
|
|
|
|
const char *path_begin = host_end;
|
|
const char *path_end = end;
|
|
|
|
if (path_begin == path_end)
|
|
{
|
|
url.path_ = "/";
|
|
}
|
|
else
|
|
{
|
|
// both query and fragment allow '?' char, so fragment is detected before query
|
|
const char *fragment_begin = ::strchr(path_begin, '#');
|
|
if (fragment_begin)
|
|
{
|
|
path_end = fragment_begin;
|
|
url.fragment_ = std::string(fragment_begin + 1, end);
|
|
}
|
|
|
|
const char *query_begin = ::strchr(path_begin, '?');
|
|
if (query_begin && (!fragment_begin || query_begin < fragment_begin))
|
|
{
|
|
const char *query_end = path_end;
|
|
path_end = query_begin;
|
|
url.query_ = std::string(query_begin + 1, query_end);
|
|
}
|
|
|
|
url.path_ = std::string(path_begin, path_end);
|
|
}
|
|
|
|
url.normalize();
|
|
return url;
|
|
} // Url::fromString()
|
|
|
|
Url Url::fromComponents(
|
|
const std::string& scheme,
|
|
const std::string& host,
|
|
const std::string& path,
|
|
const std::string& query,
|
|
const std::string& fragment)
|
|
{
|
|
Url url;
|
|
url.scheme_ = scheme;
|
|
url.host_ = host;
|
|
url.path_ = path;
|
|
url.query_ = query;
|
|
url.fragment_ = fragment;
|
|
url.normalize();
|
|
return url;
|
|
}
|
|
|
|
void Url::normalize()
|
|
{
|
|
std::transform(scheme_.begin(), scheme_.end(), scheme_.begin(), ::tolower);
|
|
std::transform(host_.begin(), host_.end(), host_.begin(), ::tolower);
|
|
|
|
// remove excess things like /.., //, /. from path
|
|
if (path_.empty())
|
|
{
|
|
if (!scheme_.empty() && !host_.empty())
|
|
path_ = "/";
|
|
}
|
|
else
|
|
{
|
|
// Having '/' at the beginning:
|
|
// - required by RFC
|
|
// - really helps with boundary conditions below
|
|
if (path_[0] != '/')
|
|
path_.insert(path_.begin(), '/');
|
|
|
|
// Process path components in-place in reverse
|
|
// Every component can be:
|
|
// - passed as is
|
|
// - skipped:
|
|
// - // or /.
|
|
// - /.. (and depth++)
|
|
// - if --depth > 0
|
|
|
|
char *begin = &path_[0];
|
|
char *end = begin + path_.length();
|
|
|
|
char *p = end[-1] == '/' ? end - 1 : end;
|
|
char *skipstart = 0, *skipend = 0;
|
|
size_t depth = 0;
|
|
while (p != begin)
|
|
{
|
|
// get next path component
|
|
char * const compend = p;
|
|
while (*(--p) != '/');
|
|
|
|
const size_t complen = compend - p;
|
|
RBXASSERT(complen > 0);
|
|
|
|
bool skip = false;
|
|
// skip "//"
|
|
if (complen == 1 ||
|
|
// skip "/."
|
|
(complen == 2 && p[1] == '.'))
|
|
{
|
|
skip = true;
|
|
}
|
|
// skip "/.."
|
|
else if (complen == 3 && p[1] == '.' && p[2] == '.')
|
|
{
|
|
++depth;
|
|
skip = true;
|
|
}
|
|
// skip parents if one or more "/.." were encountered
|
|
else if (depth > 0)
|
|
{
|
|
--depth;
|
|
skip = true;
|
|
}
|
|
// anything else is passed
|
|
else
|
|
{
|
|
skip = false;
|
|
}
|
|
|
|
// handle skipping
|
|
if (skip)
|
|
{
|
|
// remember/extend skipping range
|
|
skipstart = p;
|
|
if (!skipend)
|
|
skipend = compend;
|
|
}
|
|
// if next component is to be passed
|
|
// cut out any existing range to skip
|
|
else if (skipstart)
|
|
{
|
|
RBXASSERT(skipend);
|
|
const size_t skiplen = skipend - skipstart;
|
|
const size_t taillen = end - skipend;
|
|
std::memmove(skipstart, skipend, taillen);
|
|
end -= skiplen;
|
|
skipstart = skipend = 0;
|
|
}
|
|
} // while (p != begin)
|
|
|
|
// skip any left range
|
|
if (skipstart)
|
|
{
|
|
RBXASSERT(skipend);
|
|
const size_t skiplen = skipend - skipstart;
|
|
const size_t taillen = end - skipend;
|
|
std::memmove(skipstart, skipend, taillen);
|
|
end -= skiplen;
|
|
}
|
|
|
|
if (end > begin)
|
|
path_.resize(end - begin);
|
|
else
|
|
path_ = "/";
|
|
|
|
} // if path is not empty
|
|
} // Url::normalize()
|
|
|
|
std::string Url::asString() const
|
|
{
|
|
std::string result;
|
|
result.reserve(
|
|
scheme_.length() + 3 +
|
|
host_.length() +
|
|
path_.length() + 1 +
|
|
query_.length() + 1 +
|
|
fragment_.length() + 1);
|
|
|
|
if (!scheme_.empty())
|
|
{
|
|
result += scheme_;
|
|
result += "://";
|
|
}
|
|
|
|
result += host_;
|
|
|
|
if (path_.empty())
|
|
{
|
|
if (!host_.empty())
|
|
{
|
|
result += '/';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (path_[0] != '/')
|
|
result += '/';
|
|
result += path_;
|
|
}
|
|
|
|
if (!query_.empty())
|
|
{
|
|
result += '?';
|
|
result += query_;
|
|
}
|
|
|
|
if (!fragment_.empty())
|
|
{
|
|
result += '#';
|
|
result += fragment_;
|
|
}
|
|
|
|
return result;
|
|
} // Url::asString()
|
|
|
|
bool Url::isValid() const
|
|
{
|
|
return hasValidScheme() && hasValidHost() && hasValidPath() && hasValidQuery() && hasValidFragment();
|
|
}
|
|
|
|
bool Url::hasValidScheme() const
|
|
{
|
|
if (scheme_.empty() || !::isalpha(scheme_[0]))
|
|
return false;
|
|
|
|
return RFC3986::checkStringIsOfClass(scheme_, RFC3986::CharClassBit_Scheme);
|
|
}
|
|
|
|
bool Url::hasHttpScheme() const
|
|
{
|
|
return scheme_ == "http" || scheme_ == "https";
|
|
}
|
|
|
|
bool Url::hasValidHost() const
|
|
{
|
|
// NOTE: only reg-name host part is expected
|
|
// URL containing userinfo@ or :port parts will be invalid
|
|
return RFC3986::checkStringIsOfClass(host_, RFC3986::CharClassBit_RegName);
|
|
}
|
|
|
|
bool Url::hasValidPath() const
|
|
{
|
|
return RFC3986::checkStringIsOfClass(path_, RFC3986::CharClassBit_Path);
|
|
}
|
|
|
|
bool Url::hasValidQuery() const
|
|
{
|
|
return query_.empty() || RFC3986::checkStringIsOfClass(path_, RFC3986::CharClassBit_QueryOrFragment);
|
|
}
|
|
|
|
bool Url::hasValidFragment() const
|
|
{
|
|
return fragment_.empty() || RFC3986::checkStringIsOfClass(path_, RFC3986::CharClassBit_QueryOrFragment);
|
|
}
|
|
|
|
bool Url::isSubdomainOf(const char* domain) const
|
|
{
|
|
auto index = boost::ifind_last(host_, domain);
|
|
|
|
if (index.begin() == host_.end())
|
|
return false;
|
|
|
|
if (index.end() != host_.end())
|
|
return false;
|
|
|
|
if (index.begin() > host_.begin())
|
|
return host_[index.begin() - host_.begin() - 1] == '.';
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Url::pathEquals(const char* path) const
|
|
{
|
|
// Empty paths are equivalent
|
|
const bool argIsEmpty = !path || !path[0] || (path[0] == '/' && !path[1]);
|
|
const bool ownPathIsEmpty = pathIsEmpty();
|
|
|
|
if (argIsEmpty || ownPathIsEmpty)
|
|
return argIsEmpty && ownPathIsEmpty;
|
|
|
|
if (path[0] == '/')
|
|
++path;
|
|
|
|
return boost::algorithm::equals(path_.c_str() + 1, path);
|
|
}
|
|
|
|
bool Url::pathEqualsCaseInsensitive(const char* path) const
|
|
{
|
|
// Empty paths are equivalent
|
|
const bool argIsEmpty = !path || !path[0] || (path[0] == '/' && !path[1]);
|
|
const bool ownPathIsEmpty = pathIsEmpty();
|
|
|
|
if (argIsEmpty || ownPathIsEmpty)
|
|
return argIsEmpty && ownPathIsEmpty;
|
|
|
|
if (path[0] == '/')
|
|
++path;
|
|
|
|
return boost::algorithm::iequals(path_.c_str() + 1, path);
|
|
}
|
|
|
|
|
|
|
|
} // namespace util
|