Hermod
A cross-platform, modular and fully GDPR-compliant email archival solution!
Loading...
Searching...
No Matches
Domain.cs
Go to the documentation of this file.
1using System;
2
3namespace Hermod.Core.Accounts {
4
5 using Exceptions;
6 using Serilog.Events;
7 using System.Net;
8 using System.Text.RegularExpressions;
9
20 public partial class Domain {
21
22 public const string IanaValidTldListUrl = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt";
23
28 [GeneratedRegex(@"(?:[a-z0-9-](?:[a-z0-9-]{0,61}[a-z0-9-])?\.)+[a-z0-9-][a-z0-9-]{0,61}[a-z0-9-]", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
29 public static partial Regex DomainRegex();
30
31 public static List<string> ValidTlds { get; } = new List<string>();
32
41 public Domain(int id, string tld, string domainName, params DomainUser[] users) {
42 if (string.IsNullOrEmpty(tld) || string.IsNullOrEmpty(domainName)) { throw new MalformedDomainException($"{ domainName }.{ tld }"); }
43
44 Id = id;
45 Tld = tld;
46 DomainName = domainName;
47 DomainUsers = users.ToList();
48 }
49
57 public Domain(int id, string fqdn, params DomainUser[] users) {
58 if (string.IsNullOrEmpty(fqdn) || !fqdn.Contains('.')) { throw new MalformedDomainException(fqdn); }
59
60 Id = id;
61 var splits = fqdn.Split('.').Where(x => x != ".").ToArray();
62 Tld = splits[0];
63 DomainName = string.Join('.', splits[1..]);
64 DomainUsers = users.ToList();
65 }
66
72 public Domain(Domain domain, bool includeUsers = false) {
73 Id = domain.Id;
74 Tld = domain.Tld;
75 DomainName = domain.DomainName;
76
77 if (includeUsers) {
78 DomainUsers = new List<DomainUser>(domain.DomainUsers.ConvertAll(x => x)); // shallow copy
79 } else {
80 DomainUsers = new List<DomainUser>();
81 }
82 }
83
89 public int Id { get; set; }
90
97 public string Tld { get; set; }
98
105 public string DomainName { get; set; }
106
110 public List<DomainUser> DomainUsers { get; set; }
111
133 public static bool IsValidDomain(string fqdn, out int? tldLevels, out string? tld, out string? domain) {
134 tldLevels = null;
135 tld = null;
136 domain = null;
137
138 if (
139 string.IsNullOrEmpty(fqdn) ||
140 string.IsNullOrWhiteSpace(fqdn) ||
141 !IsFqdnInBounds(fqdn) ||
142 fqdn.Any(ContainsInvalidChars) ||
143 !DomainRegex().IsMatch(fqdn)
144 ) { return false; }
145
146 fqdn = fqdn.ToLowerInvariant();
147
148 var containsValidTld = false;
149 var domainLevels = fqdn.Split('.').Where(x => x != ".").ToList();
150 var firstNonTopLevel = -1;
151
152 // Determine whether or not the fqdn contains valid TLDs.
153 // Non-valid domains will also be accepted by Hermod
154 for (int i = 0; i < domainLevels.Count; i++) {
155 if (!IsValidTld(domainLevels.ElementAt(i))) {
156 firstNonTopLevel = i;
157 break;
158 }
159
160 containsValidTld = true;
161 }
162
163 if (firstNonTopLevel == 0) {
164 // We've found a domain with invalid TLDs.
165 // For Hermod's sake, this is perfectly acceptable!
166 // We'll just use the first level as the TLD.
167 firstNonTopLevel = 1;
168 }
169
170 tldLevels = firstNonTopLevel;
171 tld = string.Join('.', domainLevels.GetRange(0, firstNonTopLevel));
172 domain = string.Join('.', domainLevels.GetRange(firstNonTopLevel, domainLevels.Count - firstNonTopLevel));
173
174 return containsValidTld;
175 }
176
177 private static bool ContainsInvalidChars(char c) => !char.IsDigit(c) && !char.IsLetter(c) && c != '-' && c != '.';
178
184 public static bool IsFqdnInBounds(string fqdn) => fqdn.Length is (<= 255) and (> 0);
185
191 public static bool IsValidTld(string tld) {
192 if (ValidTlds.Count == 0 && !DownloadCurrentValidTldListFromIana()) { return false; }
193
194 return ValidTlds.Contains(tld);
195 }
196
202 using var httpClient = new HttpClient { };
203 using var httpResponse = httpClient.GetAsync(IanaValidTldListUrl).GetAwaiter().GetResult();
204
205 if (httpResponse is null) { return ValidTlds.Count > 0; }
206
207 using (var sReader = new StreamReader(httpResponse.Content.ReadAsStream())) {
208 ValidTlds.Clear();
209 while (sReader.Peek() != -1) {
210 var line = sReader.ReadLine();
211 if (line is null) { break; }
212 if (line.StartsWith("#") || line.Length == 0) { continue; }
213
214 ValidTlds.Add(line.ToLowerInvariant());
215 }
216 }
217
218 return ValidTlds.Count > 0;
219 }
220
225 public static async Task<bool> DownloadCurrentValidTldListFromIanaAsync() {
226 using var httpClient = new HttpClient { };
227 using var httpResponse = await httpClient.GetAsync(IanaValidTldListUrl);
228
229 if (httpResponse is null) { return ValidTlds.Count > 0; }
230
231 using (var sReader = new StreamReader(await httpResponse.Content.ReadAsStreamAsync())) {
232 ValidTlds.Clear();
233 while (sReader.Peek() != -1) {
234 var line = await sReader.ReadLineAsync();
235 if (line is null) { break; }
236 if (line.StartsWith("#") || line.Length == 0) { continue; }
237
238 ValidTlds.Add(line.ToLowerInvariant());
239 }
240 }
241
242 return ValidTlds.Count > 0;
243 }
244
245 }
246}
247
Represents a single domain.
Definition: Domain.cs:20
string DomainName
The name of the domain.
Definition: Domain.cs:105
static bool IsFqdnInBounds(string fqdn)
Gets a value indicating whether or not an FQDN is within bounds set by the ICANN.
Domain(Domain domain, bool includeUsers=false)
Copy-constructor.
Definition: Domain.cs:72
static bool DownloadCurrentValidTldListFromIana()
Downloads the latest list of currently valid TLDs from IANA.
Definition: Domain.cs:201
static bool ContainsInvalidChars(char c)
int Id
The domain's ID.
Definition: Domain.cs:89
const string IanaValidTldListUrl
Definition: Domain.cs:22
List< DomainUser > DomainUsers
A list of users belonging to the domain.
Definition: Domain.cs:110
static List< string > ValidTlds
Definition: Domain.cs:31
Domain(int id, string fqdn, params DomainUser[] users)
Constructs a new instance of this class.
Definition: Domain.cs:57
Domain(int id, string tld, string domainName, params DomainUser[] users)
Creates a new instance of this class.
Definition: Domain.cs:41
string Tld
The top-level domain.
Definition: Domain.cs:97
static bool IsValidTld(string tld)
Gets a value indicating whether or not the tld is valid.
Definition: Domain.cs:191
static partial Regex DomainRegex()
Domain-validation RegEx inspired by .
static bool IsValidDomain(string fqdn, out int? tldLevels, out string? tld, out string? domain)
Gets a value indicating whether or not a given domain is valid.
Definition: Domain.cs:133
static async Task< bool > DownloadCurrentValidTldListFromIanaAsync()
Asynchronously downloads the latest list of TLDs from the IANA.
Definition: Domain.cs:225
This class represents a single user (email account) in a domain.
Definition: DomainUser.cs:11
Exception class that is thrown when a malformed FQDN (fully-qualified domain name) was passed somewhe...