#ifndef URLParser_h
#define URLParser_h
#include "URLComponent.h"
#include "URLSegments.h"
namespace WTF {
template<typename CHAR>
class URLParser {
public:
enum SpecialPort {
UnspecifiedPort = -1,
InvalidPort = -2,
};
static bool isPossibleAuthorityTerminator(CHAR ch)
{
return isURLSlash(ch) || ch == '?' || ch == '#' || ch == ';';
}
static void parseAuthority(const CHAR* spec, const URLComponent& auth, URLComponent& username, URLComponent& password, URLComponent& host, URLComponent& port)
{
if (!auth.length()) {
username.reset();
password.reset();
host.reset();
port.reset();
return;
}
int i = auth.begin() + auth.length() - 1;
while (i > auth.begin() && spec[i] != '@')
--i;
if (spec[i] == '@') {
parseUserInfo(spec, URLComponent(auth.begin(), i - auth.begin()), username, password);
parseServerInfo(spec, URLComponent::fromRange(i + 1, auth.begin() + auth.length()), host, port);
} else {
username.reset();
password.reset();
parseServerInfo(spec, auth, host, port);
}
}
static bool extractScheme(const CHAR* spec, int specLength, URLComponent& scheme)
{
int begin = 0;
while (begin < specLength && shouldTrimFromURL(spec[begin]))
begin++;
if (begin == specLength)
return false;
for (int i = begin; i < specLength; i++) {
if (spec[i] == ':') {
scheme = URLComponent::fromRange(begin, i);
return true;
}
}
return false; }
static void parseAfterScheme(const CHAR* spec, int specLength, int afterScheme, URLSegments& parsed)
{
int numberOfSlashes = consecutiveSlashes(spec, afterScheme, specLength);
int afterSlashes = afterScheme + numberOfSlashes;
URLComponent authority;
URLComponent fullPath;
int authEnd = nextAuthorityTerminator(spec, afterSlashes, specLength);
authority = URLComponent(afterSlashes, authEnd - afterSlashes);
if (authEnd == specLength) fullPath = URLComponent();
else fullPath = URLComponent(authEnd, specLength - authEnd);
parseAuthority(spec, authority, parsed.username, parsed.password, parsed.host, parsed.port);
parsePath(spec, fullPath, parsed.path, parsed.query, parsed.fragment);
}
static void parseStandardURL(const CHAR* spec, int specLength, URLSegments& parsed)
{
int begin = 0;
trimURL(spec, begin, specLength);
int afterScheme;
if (extractScheme(spec, specLength, parsed.scheme))
afterScheme = parsed.scheme.end() + 1; else {
parsed.scheme.reset();
afterScheme = begin;
}
parseAfterScheme(spec, specLength, afterScheme, parsed);
}
static void parsePath(const CHAR* spec, const URLComponent& path, URLComponent& filepath, URLComponent& query, URLComponent& fragment)
{
if (!path.isValid()) {
filepath.reset();
query.reset();
fragment.reset();
return;
}
int pathEnd = path.begin() + path.length();
int querySeparator = -1; int refSeparator = -1; for (int i = path.begin(); i < pathEnd; i++) {
switch (spec[i]) {
case '?':
if (querySeparator < 0)
querySeparator = i;
break;
case '#':
refSeparator = i;
i = pathEnd; break;
default:
break;
}
}
int fileEnd, queryEnd;
if (refSeparator >= 0) {
fileEnd = refSeparator;
queryEnd = refSeparator;
fragment = URLComponent::fromRange(refSeparator + 1, pathEnd);
} else {
fileEnd = pathEnd;
queryEnd = pathEnd;
fragment.reset();
}
if (querySeparator >= 0) {
fileEnd = querySeparator;
query = URLComponent::fromRange(querySeparator + 1, queryEnd);
} else
query.reset();
if (fileEnd != path.begin())
filepath = URLComponent::fromRange(path.begin(), fileEnd);
else
filepath.reset();
}
static void parsePathURL(const CHAR* spec, int specLength, URLSegments& parsed)
{
parsed.username.reset();
parsed.password.reset();
parsed.host.reset();
parsed.port.reset();
parsed.query.reset();
parsed.fragment.reset();
int begin = 0;
trimURL(spec, begin, specLength);
if (begin == specLength) {
parsed.scheme.reset();
parsed.path.reset();
return;
}
if (extractScheme(&spec[begin], specLength - begin, parsed.scheme)) {
parsed.scheme.setBegin(parsed.scheme.begin() + begin);
if (parsed.scheme.end() == specLength - 1)
parsed.path.reset();
else
parsed.path = URLComponent::fromRange(parsed.scheme.end() + 1, specLength);
} else {
parsed.scheme.reset();
parsed.path = URLComponent::fromRange(begin, specLength);
}
}
static void parseMailtoURL(const CHAR* spec, int specLength, URLSegments& parsed)
{
parsed.username.reset();
parsed.password.reset();
parsed.host.reset();
parsed.port.reset();
parsed.fragment.reset();
parsed.query.reset();
int begin = 0;
trimURL(spec, begin, specLength);
if (begin == specLength) {
parsed.scheme.reset();
parsed.path.reset();
return;
}
int pathBegin = -1;
int pathEnd = -1;
if (extractScheme(&spec[begin], specLength - begin, parsed.scheme)) {
parsed.scheme.setBegin(parsed.scheme.begin() + begin);
if (parsed.scheme.end() != specLength - 1) {
pathBegin = parsed.scheme.end() + 1;
pathEnd = specLength;
}
} else {
parsed.scheme.reset();
pathBegin = begin;
pathEnd = specLength;
}
for (int i = pathBegin; i < pathEnd; ++i) {
if (spec[i] == '?') {
parsed.query = URLComponent::fromRange(i + 1, pathEnd);
pathEnd = i;
break;
}
}
if (pathBegin == pathEnd)
parsed.path.reset();
else
parsed.path = URLComponent::fromRange(pathBegin, pathEnd);
}
static int parsePort(const CHAR* spec, const URLComponent& component)
{
const int maxDigits = 5;
if (component.isEmptyOrInvalid())
return UnspecifiedPort;
URLComponent nonZeroDigits(component.end(), 0);
for (int i = 0; i < component.length(); ++i) {
if (spec[component.begin() + i] != '0') {
nonZeroDigits = URLComponent::fromRange(component.begin() + i, component.end());
break;
}
}
if (!nonZeroDigits.length())
return 0;
if (nonZeroDigits.length() > maxDigits)
return InvalidPort;
int port = 0;
for (int i = 0; i < nonZeroDigits.length(); ++i) {
CHAR ch = spec[nonZeroDigits.begin() + i];
if (!isPortDigit(ch))
return InvalidPort;
port *= 10;
port += static_cast<char>(ch) - '0';
}
if (port > 65535)
return InvalidPort;
return port;
}
static void extractFileName(const CHAR* spec, const URLComponent& path, URLComponent& fileName)
{
if (path.isEmptyOrInvalid()) {
fileName.reset();
return;
}
int fileEnd = path.end();
for (int i = path.end() - 1; i > path.begin(); --i) {
if (spec[i] == ';') {
fileEnd = i;
break;
}
}
for (int i = fileEnd - 1; i >= path.begin(); --i) {
if (isURLSlash(spec[i])) {
fileName = URLComponent::fromRange(i + 1, fileEnd);
return;
}
}
fileName = URLComponent::fromRange(path.begin(), fileEnd);
}
static bool extractQueryKeyValue(const CHAR* spec, URLComponent& query, URLComponent& key, URLComponent& value)
{
if (query.isEmptyOrInvalid())
return false;
int start = query.begin();
int current = start;
int end = query.end();
key.setBegin(current);
while (current < end && spec[current] != '&' && spec[current] != '=')
++current;
key.setLength(current - key.begin());
if (current < end && spec[current] == '=')
++current;
value.setBegin(current);
while (current < end && spec[current] != '&')
++current;
value.setLength(current - value.begin());
if (current < end && spec[current] == '&')
++current;
query = URLComponent::fromRange(current, end);
return true;
}
public:
static inline bool isURLSlash(CHAR ch)
{
return ch == '/' || ch == '\\';
}
static inline bool shouldTrimFromURL(CHAR ch)
{
return ch <= ' ';
}
static inline void trimURL(const CHAR* spec, int& begin, int& end)
{
while (begin < end && shouldTrimFromURL(spec[begin]))
++begin;
while (end > begin && shouldTrimFromURL(spec[end - 1]))
--end;
}
static inline int consecutiveSlashes(const CHAR *string, int beginOffset, int stringLength)
{
int count = 0;
while (beginOffset + count < stringLength && isURLSlash(string[beginOffset + count]))
++count;
return count;
}
private:
URLParser();
static inline bool isPortDigit(CHAR ch)
{
return ch >= '0' && ch <= '9';
}
static int nextAuthorityTerminator(const CHAR* spec, int startOffset, int specLength)
{
for (int i = startOffset; i < specLength; i++) {
if (isPossibleAuthorityTerminator(spec[i]))
return i;
}
return specLength; }
static void parseUserInfo(const CHAR* spec, const URLComponent& user, URLComponent& username, URLComponent& password)
{
int colonOffset = 0;
while (colonOffset < user.length() && spec[user.begin() + colonOffset] != ':')
++colonOffset;
if (colonOffset < user.length()) {
username = URLComponent(user.begin(), colonOffset);
password = URLComponent::fromRange(user.begin() + colonOffset + 1, user.begin() + user.length());
} else {
username = user;
password = URLComponent();
}
}
static void parseServerInfo(const CHAR* spec, const URLComponent& serverInfo, URLComponent& host, URLComponent& port)
{
if (!serverInfo.length()) {
host.reset();
port.reset();
return;
}
int ipv6Terminator = spec[serverInfo.begin()] == '[' ? serverInfo.end() : -1;
int colon = -1;
for (int i = serverInfo.begin(); i < serverInfo.end(); i++) {
switch (spec[i]) {
case ']':
ipv6Terminator = i;
break;
case ':':
colon = i;
break;
default:
break;
}
}
if (colon > ipv6Terminator) {
host = URLComponent::fromRange(serverInfo.begin(), colon);
if (!host.length())
host.reset();
port = URLComponent::fromRange(colon + 1, serverInfo.end());
} else {
host = serverInfo;
port.reset();
}
}
};
}
#endif // URLParser_h