internal Decoder decode()
{
string str = this.str;
int len = str.Length;
int pos = 0;
// ==== scheme ====
// scan the string from the beginning looking for either a
// colon or any character which doesn't fit a valid scheme
bool hasUpper = false;
for (int i=0; i<len; ++i)
{
int c = str[i];
if (isScheme(c)) { hasUpper |= isUpper(c); continue; }
if (c != ':') break;
// at this point we have a scheme; if we detected
// any upper case characters normalize to lowercase
pos = i + 1;
string scheme = str.Substring(0, i);
if (hasUpper) scheme = FanStr.lower(scheme);
this.scheme = scheme;
break;
}
// ==== authority ====
// authority must start with //
if (pos+1 < len && str[pos] == '/' && str[pos+1] == '/')
{
// find end of authority which is /, ?, #, or end of string;
// while we're scanning look for @ and last colon which isn't
// inside an [] IPv6 literal
int authStart = pos+2, authEnd = len, at = -1, colon = -1;
for (int i=authStart; i<len; ++i)
{
int c = str[i];
if (c == '/' || c == '?' || c == '#') { authEnd = i; break; }
else if (c == '@' && at < 0) { at = i; colon = -1; }
else if (c == ':') colon = i;
else if (c == ']') colon = -1;
}
// start with assumption that there is no userinfo or port
int hostStart = authStart, hostEnd = authEnd;
// if we found an @ symbol, parse out userinfo
if (at > 0)
{
this.userInfo = substr(authStart, at, USER);
hostStart = at+1;
}
// if we found an colon, parse out port
if (colon > 0)
{
this.port = Long.valueOf(Convert.ToInt64(str.Substring(colon+1, authEnd-colon-1)));
hostEnd = colon;
}
// host is everything left in the authority
this.host = substr(hostStart, hostEnd, HOST);
pos = authEnd;
}
// ==== path ====
// scan the string looking '?' or '#' which ends the path
// section; while we're scanning count the number of slashes
int pathStart = pos, pathEnd = len, numSegs = 1, prev = 0;
for (int i=pathStart; i<len; ++i)
{
int c = str[i];
if (prev != '\\')
{
if (c == '?' || c == '#') { pathEnd = i; break; }
if (i != pathStart && c == '/') ++numSegs;
prev = c;
}
else
{
prev = (c != '\\') ? c : 0;
}
}
// we now have the complete path section
this.pathStr = substr(pathStart, pathEnd, PATH);
this.path = pathSegments(pathStr, numSegs);
pos = pathEnd;
// ==== query ====
if (pos < len && str[pos] == '?')
{
// look for end of query which is # or end of string
int queryStart = pos+1, queryEnd = len;
prev = 0;
for (int i=queryStart; i<len; ++i)
{
int c = str[i];
if (prev != '\\')
{
if (c == '#') { queryEnd = i; break; }
prev = c;
}
else
{
prev = (c != '\\') ? c : 0;
}
}
// we now have the complete query section
this.queryStr = substr(queryStart, queryEnd, QUERY);
this.query = parseQuery(queryStr);
pos = queryEnd;
}
// ==== frag ====
if (pos < len && str[pos] == '#')
{
this.frag = substr(pos+1, len, FRAG);
}
// === normalize ===
normalize();
return this;
}