private static string CombineUri(Uri basePart, string relativePart, UriFormat uriFormat)
{
//NB: relativePart is ensured as not empty by the caller
// Another assumption is that basePart is an AbsoluteUri
// This method was not optimized for efficiency
// Means a relative Uri ctor may be relatively slow plus it increases the footprint of the baseUri
char c1 = relativePart[0];
//check a special case for the base as DOS path and a rooted relative string
if (basePart.IsDosPath &&
(c1 == '/' || c1 == '\\') &&
(relativePart.Length == 1 || (relativePart[1] != '/' && relativePart[1] != '\\')))
{
// take relative part appended to the base string after the drive letter
int idx = basePart.OriginalString.IndexOf(':');
if (basePart.IsImplicitFile)
{
return basePart.OriginalString.Substring(0, idx + 1) + relativePart;
}
// The basePart has explicit scheme (could be not file:), take the DOS drive ':' position
idx = basePart.OriginalString.IndexOf(':', idx + 1);
return basePart.OriginalString.Substring(0, idx + 1) + relativePart;
}
// Check special case for Unc or absolute path in relativePart when base is FILE
if (StaticIsFile(basePart.Syntax))
{
if (c1 == '\\' || c1 == '/')
{
if (relativePart.Length >= 2 && (relativePart[1] == '\\' || relativePart[1] == '/'))
{
//Assuming relative is a Unc path and base is a file uri.
return basePart.IsImplicitFile ? relativePart : "file:" + relativePart;
}
// here we got an absolute path in relativePart,
// For compatibility with V1.0 parser we restrict the compression scope to Unc Share, i.e. \\host\share\
if (basePart.IsUnc)
{
string share = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
UriFormat.Unescaped);
for (int i = 1; i < share.Length; ++i)
{
if (share[i] == '/')
{
share = share.Substring(0, i);
break;
}
}
if (basePart.IsImplicitFile)
{
return @"\\"
+ basePart.GetParts(UriComponents.Host, UriFormat.Unescaped)
+ share
+ relativePart;
}
return "file://"
+ basePart.GetParts(UriComponents.Host, uriFormat)
+ share
+ relativePart;
}
// It's not obvious but we've checked (for this relativePart format) that baseUti is nor UNC nor DOS path
//
// Means base is a Unix style path and, btw, IsImplicitFile cannot be the case either
return "file://" + relativePart;
}
}
// If we are here we did not recognize absolute DOS/UNC path for a file: base uri
// Note that DOS path may still happen in the relativePart and if so it may override the base uri scheme.
bool convBackSlashes = basePart.Syntax.InFact(UriSyntaxFlags.ConvertPathSlashes);
string left = null;
// check for network or local absolute path
if (c1 == '/' || (c1 == '\\' && convBackSlashes))
{
if (relativePart.Length >= 2 && relativePart[1] == '/')
{
// got an authority in relative path and the base scheme is not file (checked)
return basePart.Scheme + ':' + relativePart;
}
// Got absolute relative path, and the base is nor FILE nor a DOS path (checked at the method start)
if (basePart.HostType == Flags.IPv6HostType)
{
left = basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)
+ '[' + basePart.DnsSafeHost + ']'
+ basePart.GetParts(UriComponents.KeepDelimiter | UriComponents.Port, uriFormat);
}
else
{
left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
}
if (convBackSlashes && c1 == '\\')
relativePart = '/' + relativePart.Substring(1);
return left + relativePart;
}
// Here we got a relative path
// Need to run path Compression because this is how relative Uri combining works
// Take the base part path up to and including the last slash
left = basePart.GetParts(UriComponents.Path | UriComponents.KeepDelimiter,
basePart.IsImplicitFile ? UriFormat.Unescaped : uriFormat);
int length = left.Length;
char[] path = new char[length + relativePart.Length];
if (length > 0)
{
left.CopyTo(0, path, 0, length);
while (length > 0)
{
if (path[--length] == '/')
{
++length;
break;
}
}
}
//Append relative path to the result
relativePart.CopyTo(0, path, length, relativePart.Length);
// Split relative on path and extra (for compression)
c1 = basePart.Syntax.InFact(UriSyntaxFlags.MayHaveQuery) ? '?' : c_DummyChar;
// The implicit file check is to avoid a fragment in the implicit file combined uri.
char c2 = (!basePart.IsImplicitFile && basePart.Syntax.InFact(UriSyntaxFlags.MayHaveFragment)) ? '#' :
c_DummyChar;
string extra = string.Empty;
// assuming c_DummyChar may not happen in an unicode uri string
if (!(c1 == c_DummyChar && c2 == c_DummyChar))
{
int i = 0;
for (; i < relativePart.Length; ++i)
{
if (path[length + i] == c1 || path[length + i] == c2)
{
break;
}
}
if (i == 0)
{
extra = relativePart;
}
else if (i < relativePart.Length)
{
extra = relativePart.Substring(i);
}
length += i;
}
else
{
length += relativePart.Length;
}
// Take the base part up to the path
if (basePart.HostType == Flags.IPv6HostType)
{
if (basePart.IsImplicitFile)
{
left = @"\\[" + basePart.DnsSafeHost + ']';
}
else
{
left = basePart.GetParts(UriComponents.Scheme | UriComponents.UserInfo, uriFormat)
+ '[' + basePart.DnsSafeHost + ']'
+ basePart.GetParts(UriComponents.KeepDelimiter | UriComponents.Port, uriFormat);
}
}
else
{
if (basePart.IsImplicitFile)
{
if (basePart.IsDosPath)
{
// The FILE DOS path comes as /c:/path, we have to exclude first 3 chars from compression
path = Compress(path, 3, ref length, basePart.Syntax);
return new string(path, 1, length - 1) + extra;
}
else
{
left = @"\\" + basePart.GetParts(UriComponents.Host, UriFormat.Unescaped);
}
}
else
{
left = basePart.GetParts(UriComponents.SchemeAndServer | UriComponents.UserInfo, uriFormat);
}
}
//compress the path
path = Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax);
return left + new string(path, 0, length) + extra;
}