#include <ctype.h> // for tolower()
#include "TUri.h"
#include "TObjArray.h"
#include "TObjString.h"
#include "TPRegexp.h"
const char* const kURI_pchar = "(?:[[:alpha:][:digit:]-._~!$&'()*+,;=:@]|%[0-9A-Fa-f][0-9A-Fa-f])";
const char* const kURI_unreserved = "[[:alpha:][:digit:]-._~]";
ClassImp(TUri)
TUri::TUri(const TString &uri)
{
SetUri(uri);
}
TUri::TUri(const char *uri)
{
SetUri(uri);
}
TUri::TUri(const TUri &uri) : TObject(uri)
{
fScheme = uri.fScheme;
fUserinfo = uri.fUserinfo;
fHost = uri.fHost;
fPort = uri.fPort;
fPath = uri.fPath;
fQuery = uri.fQuery;
fFragment = uri.fFragment;
fHasScheme = uri.fHasScheme;
fHasUserinfo = uri.fHasUserinfo;
fHasHost = uri.fHasHost;
fHasPort = uri.fHasPort;
fHasPath = uri.fHasPath;
fHasQuery = uri.fHasQuery;
fHasFragment = uri.fHasFragment;
}
TUri &TUri::operator= (const TUri & rhs)
{
if (this != &rhs) {
TObject::operator= (rhs);
fScheme = rhs.fScheme;
fUserinfo = rhs.fUserinfo;
fHost = rhs.fHost;
fPort = rhs.fPort;
fPath = rhs.fPath;
fQuery = rhs.fQuery;
fFragment = rhs.fFragment;
fHasScheme = rhs.fHasScheme;
fHasUserinfo = rhs.fHasUserinfo;
fHasHost = rhs.fHasHost;
fHasPort = rhs.fHasPort;
fHasPath = rhs.fHasPath;
fHasQuery = rhs.fHasQuery;
fHasFragment = rhs.fHasFragment;
}
return *this;
}
Bool_t operator== (const TUri &u1, const TUri &u2)
{
TUri u11 = u1;
TUri u22 = u2;
u11.Normalise();
u22.Normalise();
return u11.GetUri() == u22.GetUri();
}
const TString TUri::GetUri() const
{
TString result = "";
if (fHasScheme)
result = fScheme + ":";
result += GetHierPart();
if (fHasQuery)
result += TString("?") + fQuery;
if (fHasFragment)
result += TString("#") + fFragment;
return result;
}
const TString TUri::RemoveDotSegments(const TString &inp)
{
TString source = inp;
TString sink = TString("");
while (source.Length() > 0) {
if (TPRegexp("^\\.\\.?/(.*)$").Substitute(source, "/$1") > 0)
continue;
if (TPRegexp("^/\\./(.*)$|^/\\.($)").Substitute(source, "/$1") > 0)
continue;
if (TPRegexp("^/\\.\\./(.*)$|^/\\.\\.($)").Substitute(source, "/$1") > 0) {
Ssiz_t last = sink.Last('/');
if (last == -1)
last = 0;
sink.Remove(last, sink.Length() - last);
continue;
}
if (source.CompareTo(".") == 0 || source.CompareTo("..") == 0) {
source.Remove(0, source.Length() - 11);
continue;
}
TPRegexp regexp = TPRegexp("^(/?[^/]*)(?:/|$)");
TObjArray *tokens = regexp.MatchS(source);
TString segment = ((TObjString*) tokens->At(1))->GetString();
sink += segment;
source.Remove(0, segment.Length());
delete tokens;
}
return sink;
}
Bool_t TUri::IsAbsolute() const
{
return (HasScheme() && HasHierPart() && !HasFragment());
}
Bool_t TUri::IsRelative() const
{
return (!HasScheme() && HasRelativePart());
}
Bool_t TUri::IsUri() const
{
return (HasScheme() && HasHierPart());
}
Bool_t TUri::IsReference() const
{
return (IsUri() || IsRelative());
}
Bool_t TUri::SetScheme(const TString &scheme)
{
if (!scheme) {
fHasScheme = kFALSE;
return kTRUE;
}
if (IsScheme(scheme)) {
fScheme = scheme;
fHasScheme = kTRUE;
return kTRUE;
} else {
Error("SetScheme", "<scheme> component \"%s\" of URI is not compliant with RFC 3986.", scheme.Data());
return kFALSE;
}
}
Bool_t TUri::IsScheme(const TString &string)
{
return TPRegexp(
"^[[:alpha:]][[:alpha:][:digit:]+-.]*$"
).Match(string);
}
const TString TUri::GetAuthority() const
{
TString authority = fHasUserinfo ? fUserinfo + "@" + fHost : fHost;
if (fHasPort && !fPort.IsNull())
authority += TString(":") + TString(fPort);
return (authority);
}
Bool_t TUri::SetQuery(const TString &query)
{
if (!query) {
fHasQuery = kFALSE;
return kTRUE;
}
if (IsQuery(query)) {
fQuery = query;
fHasQuery = kTRUE;
return kTRUE;
} else {
Error("SetQuery", "<query> component \"%s\" of URI is not compliant with RFC 3986.", query.Data());
return kFALSE;
}
}
Bool_t TUri::IsQuery(const TString &string)
{
return TPRegexp(
TString("^([/?]|") + kURI_pchar + ")*$"
).Match(string);
}
Bool_t TUri::SetAuthority(const TString &authority)
{
if (authority.IsNull()) {
fHasUserinfo = kFALSE;
fHasHost = kFALSE;
fHasPort = kFALSE;
return kTRUE;
}
TPRegexp regexp = TPRegexp("^(?:(.*@))?([^:]*)((?::.*)?)$");
TObjArray *tokens = regexp.MatchS(authority);
if (tokens->GetEntries() != 4) {
Error("SetAuthority", "<authority> component \"%s\" of URI is not compliant with RFC 3986.", authority.Data());
return kFALSE;
}
Bool_t valid = kTRUE;
TString userinfo = ((TObjString*) tokens->At(1))->GetString();
if (userinfo.EndsWith("@")) {
userinfo.Remove(TString::kTrailing, '@');
valid &= SetUserInfo(userinfo);
}
TString host = ((TObjString*) tokens->At(2))->GetString();
valid &= SetHost(host);
TString port = ((TObjString*) tokens->At(3))->GetString();
if (port.BeginsWith(":")) {
port.Remove(TString::kLeading, ':');
valid &= SetPort(port);
}
return valid;
}
Bool_t TUri::IsAuthority(const TString &string)
{
TPRegexp regexp = TPRegexp("^(?:(.*)@)?([^:]*)(?::(.*))?$");
TObjArray *tokens = regexp.MatchS(string);
TString userinfo = ((TObjString*) tokens->At(1))->GetString();
TString host = ((TObjString*) tokens->At(2))->GetString();
TString port;
if (tokens->GetEntries() == 4)
port = ((TObjString*) tokens->At(3))->GetString();
else
port = "";
return (IsHost(host) && IsUserInfo(userinfo) && IsPort(port));
}
Bool_t TUri::SetUserInfo(const TString &userinfo)
{
if (userinfo.IsNull()) {
fHasUserinfo = kFALSE;
return kTRUE;
}
if (IsUserInfo(userinfo)) {
fUserinfo = userinfo;
fHasUserinfo = kTRUE;
return kTRUE;
} else {
Error("SetUserInfo", "<userinfo> component \"%s\" of URI is not compliant with RFC 3986.", userinfo.Data());
return kFALSE;
}
}
Bool_t TUri::IsUserInfo(const TString &string)
{
return (TPRegexp(
"^" + TString(kURI_pchar) + "*$"
).Match(string) > 0 && !TString(string).Contains("@"));
}
Bool_t TUri::SetHost(const TString &host)
{
if (IsHost(host)) {
fHost = host;
fHasHost = kTRUE;
return kTRUE;
} else {
Error("SetHost", "<host> component \"%s\" of URI is not compliant with RFC 3986.", host.Data());
return kFALSE;
}
}
Bool_t TUri::SetPort(const TString &port)
{
if (IsPort(port)) {
fPort = port;
fHasPort = kTRUE;
return kTRUE;
}
Error("SetPort", "<port> component \"%s\" of URI is not compliant with RFC 3986.", port.Data());
return kFALSE;
}
Bool_t TUri::SetPath(const TString &path)
{
if (IsPath(path)) {
fPath = path;
fHasPath = kTRUE;
return kTRUE;
}
Error("SetPath", "<path> component \"%s\" of URI is not compliant with RFC 3986.", path.Data());
return kFALSE;
}
Bool_t TUri::SetFragment(const TString &fragment)
{
if (IsFragment(fragment)) {
fFragment = fragment;
fHasFragment = kTRUE;
return kTRUE;
} else {
Error("SetFragment", "<fragment> component \"%s\" of URI is not compliant with RFC 3986.", fragment.Data());
return kFALSE;
}
}
Bool_t TUri::IsFragment(const TString &string)
{
return (TPRegexp(
"^(" + TString(kURI_pchar) + "|[/?])*$"
).Match(string) > 0);
}
void TUri::Print(Option_t *option) const
{
if (strcmp(option, "d") != 0) {
Printf("%s", GetUri().Data());
return ;
}
Printf("URI: <%s>", GetUri().Data());
Printf("(%c) |--scheme---------<%s>", fHasScheme ? 't' : 'f', fScheme.Data());
Printf(" |--hier-----------<%s>", GetHierPart().Data());
Printf("(%c) |--authority------<%s>", HasAuthority() ? 't' : 'f', GetAuthority().Data());
Printf("(%c) |--userinfo---<%s>", fHasUserinfo ? 't' : 'f', fUserinfo.Data());
Printf("(%c) |--host-------<%s>", fHasHost ? 't' : 'f', fHost.Data());
Printf("(%c) |--port-------<%s>", fHasPort ? 't' : 'f', fPort.Data());
Printf("(%c) |--path-------<%s>", fHasPath ? 't' : 'f', fPath.Data());
Printf("(%c) |--query------<%s>", fHasQuery ? 't' : 'f', fQuery.Data());
Printf("(%c) |--fragment---<%s>", fHasFragment ? 't' : 'f', fFragment.Data());
printf("path flags: ");
if (IsPathAbempty(fPath))
printf("abempty ");
if (IsPathAbsolute(fPath))
printf("absolute ");
if (IsPathRootless(fPath))
printf("rootless ");
if (IsPathEmpty(fPath))
printf("empty ");
printf("\nURI flags: ");
if (IsAbsolute())
printf("absolute-URI ");
if (IsRelative())
printf("relative-ref ");
if (IsUri())
printf("URI ");
if (IsReference())
printf("URI-reference ");
printf("\n");
}
void TUri::Reset()
{
fScheme = "";
fUserinfo = "";
fHost = "";
fPort = "";
fPath = "";
fQuery = "";
fFragment = "";
fHasScheme = kFALSE;
fHasUserinfo = kFALSE;
fHasHost = kFALSE;
fHasPort = kFALSE;
fHasPath = kFALSE;
fHasQuery = kFALSE;
fHasFragment = kFALSE;
}
Bool_t TUri::SetUri(const TString &uri)
{
Reset();
TPRegexp regexp = TPRegexp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)([?]([^#]*))?(#(.*))?");
TObjArray *tokens = regexp.MatchS(uri);
Bool_t valid = kTRUE;
switch (tokens->GetEntries()) {
case 10:
valid &= SetFragment(((TObjString*) tokens->At(9))->GetString());
case 8:
if (!((TString)((TObjString*) tokens->At(6))->GetString()).IsNull())
valid &= SetQuery(((TObjString*) tokens->At(7))->GetString());
case 6:
valid &= SetPath(((TObjString*) tokens->At(5))->GetString());
if (!((TString)((TObjString*) tokens->At(3))->GetString()).IsNull())
valid &= SetAuthority(((TObjString*) tokens->At(4))->GetString());
if (!((TString)((TObjString*) tokens->At(1))->GetString()).IsNull())
valid &= SetScheme(((TObjString*) tokens->At(2))->GetString());
break;
default:
Error("SetUri", "URI \"%s\" is not is not compliant with RFC 3986.", uri.Data());
valid = kFALSE;
}
if (!valid)
Reset();
delete tokens;
return valid;
}
const TString TUri::GetHierPart() const
{
if (HasAuthority() && IsPathAbempty(fPath))
return (TString("//") + GetAuthority() + fPath);
else
return fPath;
}
const TString TUri::GetRelativePart() const
{
if (HasAuthority() && IsPathAbempty(fPath))
return (TString("//") + GetAuthority() + fPath);
else
return fPath;
}
Bool_t TUri::SetHierPart(const TString &hier)
{
TPRegexp regexp = TPRegexp("^(//([^/?#]*))?([^?#]*)$");
TObjArray *tokens = regexp.MatchS(hier);
if (tokens->GetEntries() == 0) {
Error("SetHierPart", "<hier-part> component \"%s\" of URI is not compliant with RFC 3986.", hier.Data());
delete tokens;
return false;
}
TString delm = ((TObjString*) tokens->At(1))->GetString();
TString auth = ((TObjString*) tokens->At(2))->GetString();
TString path = ((TObjString*) tokens->At(3))->GetString();
Bool_t valid = kTRUE;
if (!delm.IsNull() && IsPathAbempty(path)) {
valid &= SetAuthority(auth);
valid &= SetPath(path);
} else {
if (IsPathAbsolute(path) || IsPathRootless(path) || IsPathEmpty(path))
valid &= SetPath(path);
else {
valid = kFALSE;
Error("SetHierPart", "<hier-part> component \"%s\" of URI is not compliant with RFC 3986.", hier.Data());
}
}
delete tokens;
return valid;
}
Bool_t TUri::IsHierPart(const TString &string)
{
TUri uri;
return (uri.SetHierPart(string));
}
Bool_t TUri::IsRelativePart(const TString &string)
{
TUri uri;
return (uri.SetRelativePart(string));
}
Bool_t TUri::SetRelativePart(const TString &relative)
{
TPRegexp regexp = TPRegexp("^(//([^/?#]*))?([^?#]*)$");
TObjArray *tokens = regexp.MatchS(relative);
if (tokens->GetEntries() == 0) {
Error("SetRelativePath", "<relative-part> component \"%s\" of URI is not compliant with RFC 3986.", relative.Data());
delete tokens;
return false;
}
TString delm = ((TObjString*) tokens->At(1))->GetString();
TString auth = ((TObjString*) tokens->At(2))->GetString();
TString path = ((TObjString*) tokens->At(3))->GetString();
Bool_t valid = kTRUE;
if (!delm.IsNull() && IsPathAbempty(path)) {
valid &= SetAuthority(auth);
valid &= SetPath(path);
} else {
if (IsPathAbsolute(path) || IsPathNoscheme(path) || IsPathEmpty(path))
valid &= SetPath(path);
else {
valid = kFALSE;
Error("SetRelativePath", "<relative-part> component \"%s\" of URI is not compliant with RFC 3986.", relative.Data());
}
}
delete tokens;
return valid;
}
const TString TUri::PctEncode(const TString &source)
{
TString sink = "";
for (Int_t i = 0; i < source.Length(); i++) {
if (IsUnreserved(TString(source(i)))) {
sink = sink + source[i];
} else {
char buffer[4];
sprintf(buffer, "%%%02X", source[i]);
sink = sink + buffer;
}
}
return sink;
}
Bool_t TUri::IsHost(const TString &string)
{
return (IsRegName(string) || IsIpv4(string));
}
Bool_t TUri::IsPath(const TString &string)
{
return (IsPathAbempty(string) ||
IsPathAbsolute(string) ||
IsPathNoscheme(string) ||
IsPathRootless(string) ||
IsPathEmpty(string));
}
Bool_t TUri::IsPathAbempty(const TString &string)
{
return (TPRegexp(
TString("^(/") + TString(kURI_pchar) + "*)*$"
).Match(string) > 0);
}
Bool_t TUri::IsPathAbsolute(const TString &string)
{
return (TPRegexp(
TString("^/(") + TString(kURI_pchar) + "+(/" + TString(kURI_pchar) + "*)*)?$"
).Match(string) > 0);
}
Bool_t TUri::IsPathNoscheme(const TString &string)
{
return (TPRegexp(
TString("^(([[:alpha:][:digit:]-._~!$&'()*+,;=@]|%[0-9A-Fa-f][0-9A-Fa-f])+)(/") + TString(kURI_pchar) + "*)*$"
).Match(string) > 0);
}
Bool_t TUri::IsPathRootless(const TString &string)
{
return TPRegexp(
TString("^") + TString(kURI_pchar) + "+(/" + TString(kURI_pchar) + "*)*$"
).Match(string);
}
Bool_t TUri::IsPathEmpty(const TString &string)
{
return TString(string).IsNull();
}
Bool_t TUri::IsPort(const TString &string)
{
return (TPRegexp("^[[:digit:]]*$").Match(string) > 0);
}
Bool_t TUri::IsRegName(const TString &string)
{
return (TPRegexp(
"^([[:alpha:][:digit:]-._~!$&'()*+,;=]|%[0-9A-Fa-f][0-9A-Fa-f])*$").Match(string) > 0);
}
Bool_t TUri::IsIpv4(const TString &string)
{
return (TPRegexp(
"^([[:digit:]]{1,3}[.]){3}[[:digit:]]{1,3}$").Match(string) > 0);
}
Bool_t TUri::IsUnreserved(const TString &string)
{
return (TPRegexp(
"^" + TString(kURI_unreserved) + "*$").Match(string) > 0);
}
void TUri::Normalise()
{
fScheme.ToLower();
if (fHasHost) {
TString host = GetHost();
host.ToLower();
SetHost(host);
}
fUserinfo = PctNormalise(PctDecodeUnreserved(fUserinfo));
fHost = PctNormalise(PctDecodeUnreserved(fHost));
fPath = PctNormalise(PctDecodeUnreserved(fPath));
fQuery = PctNormalise(PctDecodeUnreserved(fQuery));
fFragment = PctNormalise(PctDecodeUnreserved(fFragment));
if (fHasPath)
SetPath(RemoveDotSegments(GetPath()));
}
TString const TUri::PctDecodeUnreserved(const TString &source)
{
TString sink = "";
Int_t i = 0;
while (i < source.Length()) {
if (source[i] == '%') {
if (source.Length() < i+2) {
return sink;
}
char c1 = tolower(source[i + 1]) - '0';
if (c1 > 9)
c1 -= 39;
char c0 = tolower(source[i + 2]) - '0';
if (c0 > 9)
c0 -= 39;
char decoded = c1 << 4 | c0;
if (TPRegexp(kURI_unreserved).Match(decoded) > 0) {
sink = sink + decoded;
} else {
TString pct = source(i,3);
pct.ToUpper();
sink = sink + pct;
}
i += 2;
} else {
sink = sink + source[i];
}
i++;
}
return sink;
}
TString const TUri::PctNormalise(const TString &source)
{
TString sink = "";
Int_t i = 0;
while (i < source.Length()) {
if (source[i] == '%') {
if (source.Length() < i+2) {
return sink;
}
TString pct = source(i,3);
pct.ToUpper();
sink = sink + pct;
i += 2;
} else {
sink = sink + source[i];
}
i++;
}
return sink;
}
TString const TUri::PctDecode(const TString &source)
{
TString sink = "";
Int_t i = 0;
while (i < source.Length()) {
if (source[i] == '%') {
if (source.Length() < i+2) {
return sink;
}
char c1 = tolower(source[i + 1]) - '0';
if (c1 > 9)
c1 -= 39;
char c0 = tolower(source[i + 2]) - '0';
if (c0 > 9)
c0 -= 39;
sink = sink + (char)(c1 << 4 | c0);
i += 2;
} else {
sink = sink + source[i];
}
i++;
}
return sink;
}
TUri TUri::Transform(const TUri &reference, const TUri &base)
{
TUri target;
if (reference.HasScheme()) {
target.SetScheme(reference.GetScheme());
if (reference.HasAuthority())
target.SetAuthority(reference.GetAuthority());
if (reference.HasPath())
target.SetPath(RemoveDotSegments(reference.GetPath()));
if (reference.HasQuery())
target.SetQuery(reference.GetQuery());
} else {
if (reference.HasAuthority()) {
target.SetAuthority(reference.GetAuthority());
if (reference.HasPath())
target.SetPath(RemoveDotSegments(reference.GetPath()));
if (reference.HasQuery())
target.SetQuery(reference.GetQuery());
} else {
if (reference.GetPath().IsNull()) {
target.SetPath(base.GetPath());
if (reference.HasQuery()) {
target.SetQuery(reference.GetQuery());
} else {
if (base.HasQuery())
target.SetQuery(base.GetQuery());
}
} else {
if (reference.GetPath().BeginsWith("/")) {
target.SetPath(RemoveDotSegments(reference.GetPath()));
} else {
target.SetPath(RemoveDotSegments(MergePaths(reference, base)));
}
if (reference.HasQuery())
target.SetQuery(reference.GetQuery());
}
if (base.HasAuthority())
target.SetAuthority(base.GetAuthority());
}
if (base.HasScheme())
target.SetScheme(base.GetScheme());
}
if (reference.HasFragment())
target.SetFragment(reference.GetFragment());
return target;
}
const TString TUri::MergePaths(const TUri &reference, const TUri &base)
{
TString result = "";
if (base.HasAuthority() && base.GetPath().IsNull()) {
result = TString("/") + reference.GetPath();
} else {
TString basepath = base.GetPath();
Ssiz_t last = basepath.Last('/');
if (last == -1)
result = reference.GetPath();
else
result = basepath(0, last + 1) + reference.GetPath();
}
return result;
}