Last active
October 1, 2024 06:58
-
-
Save copernicus365/385d62f1e26247d1d78e70bd98c56fc8 to your computer and use it in GitHub Desktop.
Class to get the subdomain from the domain / host of a Uri.Host string if it exists
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace DotNetXtensions.Uri; | |
/// <summary> | |
/// `Uri` class does not allow getting a subdomain from the `uri.Host`. | |
/// This class provides a highly efficient method of getting a subdomain | |
/// if it exists on an input host / domain string (ideally sent in via | |
/// <see cref="Uri.Host"/>). | |
/// </summary> | |
public class UriHostWithoutSubdomain | |
{ | |
/// <summary> | |
/// If subdomain detected returns the subdomain, else returns input value. | |
/// If null returns null. | |
/// </summary> | |
/// <param name="host">Send in `Uri.Host`</param> | |
public static string GetHostWithoutSubdomain(string host) | |
{ | |
if(host == null) | |
return null; | |
if(!HasSubdomain(host, out int registrableDomainIndex)) | |
return host; | |
string regDomain = host.Substring(registrableDomainIndex); | |
return regDomain; | |
} | |
/// <summary> | |
/// Detects if an input host string contains a subdomain. Is a highly efficient | |
/// implementation: with NO allocations, and with two for loops sharing a single `int i`: | |
/// one up to the first period, and one that continues from that point | |
/// | |
/// and is NOT a validator of a domain string. For efficiency purposes | |
/// that is not desirable, as the intended use-case as well as separation of concerns | |
/// is that a `Uri.Host` value was input, or something like that. | |
/// Relying on this allows us to provide this highly efficient algorithm, which essentially | |
/// only needs to walk once through the string chars looking first for the first period, | |
/// and then, if a second period is detected, it is established that a subdomain exists. | |
/// If so, the out arg allows one to know where to take a substring to get the subdomain, | |
/// or the second-level domain without it, etc. | |
/// <para /> | |
/// </summary> | |
/// <param name="host">Uri host / domain string</param> | |
/// <param name="postSubdomainIndex">If subdomain exists, this will be set to the | |
/// index at which the 'second-level domain' begins (after the subdomain and 1 AFTER the period, | |
/// so in foo.example.com, index will be at 'e' in 'example.com'). | |
/// But if no subdomain (if FALSE), then this will be the position of the 'top-level' domain, | |
/// e.g. at the 'c' in "com" in "example.com". If one wants they could use this to | |
/// efficiently get the top-level domain and etc.</param> | |
public static bool HasSubdomain(string host, out int postSubdomainIndex) | |
{ | |
ArgumentNullException.ThrowIfNull(host); | |
int i = 0; | |
int len = host.Length; | |
for(; i < len; i++) | |
if(host[i] == '.') | |
break; | |
if(i >= len) { | |
// no period at all | |
postSubdomainIndex = 0; | |
return false; | |
} | |
postSubdomainIndex = ++i; | |
for(; i < len; i++) | |
if(host[i] == '.') | |
break; | |
if(i >= len) { | |
// no second period was found, so period was for TLD (top-level domain) | |
return false; | |
} | |
// second period WAS found, and we are NOT at the end yet, | |
// but we do NOT need to go further now, subdomain ends before first period, | |
// we're now already at second | |
return true; | |
} | |
} | |
public class UriHostWithoutSubdomainTests | |
{ | |
[Fact] public void NoPeriodAtAll() => _run("example", null); | |
[Fact] public void NoSub1() => _run("example.com", null, 8); | |
[Fact] public void Sub1() => _run("sb.example.com", "example.com", 3); | |
[Fact] public void Sub2() => _run("domain.gov.uk", "gov.uk", 7); | |
[Fact] public void WWWAsSub() => _run("www.navilavi.com", "navilavi.com", 4); | |
[Fact] public void EmptySubDom1() => _run(".example.com", "example.com", 1); | |
// | |
void _run(string domain, string host = null, int expectedIndex = -1) | |
{ | |
string expected = host ?? domain; | |
string val = UriHostWithoutSubdomain.GetHostWithoutSubdomain(domain); | |
Assert.True(val == expected); | |
if(expectedIndex >= 0) { | |
bool hasSubdomain = host == domain; | |
bool hasSub = UriHostWithoutSubdomain.HasSubdomain(domain, out int _index); | |
Assert.True(_index == expectedIndex); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment