├── tests
└── AspNetCore.ReferrerBlock.Tests
│ ├── MSTestSettings.cs
│ ├── AspNetCore.ReferrerBlock.Tests.csproj
│ └── ReferrerBlockMiddlewareTests.cs
├── src
└── AspNetCore.ReferrerBlock
│ ├── ReferrerBlock.png
│ ├── Extensions
│ └── ReferrerBlockMiddlewareExtensions.cs
│ ├── AspNetCore.ReferrerBlock.csproj
│ ├── Configuration
│ └── ReferrerBlockOptions.cs
│ └── Middleware
│ └── ReferrerBlockMiddleware.cs
├── AspNetCore.ReferrerBlock.slnx
├── LICENSE
├── README.md
├── AspNetCore.ReferrerBlock.sln
├── BLOCKED_DOMAINS.md
└── .gitignore
/tests/AspNetCore.ReferrerBlock.Tests/MSTestSettings.cs:
--------------------------------------------------------------------------------
1 | [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
2 |
--------------------------------------------------------------------------------
/src/AspNetCore.ReferrerBlock/ReferrerBlock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tossnet/AspNetCore.ReferrerBlock/main/src/AspNetCore.ReferrerBlock/ReferrerBlock.png
--------------------------------------------------------------------------------
/AspNetCore.ReferrerBlock.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/AspNetCore.ReferrerBlock.Tests/AspNetCore.ReferrerBlock.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | latest
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/AspNetCore.ReferrerBlock/Extensions/ReferrerBlockMiddlewareExtensions.cs:
--------------------------------------------------------------------------------
1 | using ReferrerBlock.Configuration;
2 | using ReferrerBlock.Middleware;
3 |
4 | namespace Microsoft.AspNetCore.Builder;
5 |
6 | public static class ReferrerBlockMiddlewareExtensions
7 | {
8 | public static IApplicationBuilder UseReferrerBlock(
9 | this IApplicationBuilder app,
10 | Action? configure = null)
11 | {
12 | var options = new ReferrerBlockOptions();
13 | configure?.Invoke(options);
14 |
15 | // Remove null or empty values from collections to prevent blocking legitimate traffic
16 | options.BlockedDomains?.RemoveWhere(string.IsNullOrWhiteSpace);
17 | options.BlockedTLDs?.RemoveWhere(string.IsNullOrWhiteSpace);
18 | options.BlockedPatterns?.RemoveWhere(string.IsNullOrWhiteSpace);
19 |
20 | return app.UseMiddleware(options);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Christophe Peugnet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/AspNetCore.ReferrerBlock/AspNetCore.ReferrerBlock.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 |
8 | ReferrerBlock
9 | 2025.12.14
10 | Christophe Peugnet
11 |
12 | ReferrerBlock middleware to block referrer spam and malicious traffic based on TLDs, domains, and patterns. Perfect for Blazor, MVC, and Razor Pages applications.
13 | aspnetcore;middleware;security;spam;referrer;anti-spam
14 | https://github.com/tossnet/AspNetCore.ReferrerBlock.git
15 | https://github.com/tossnet/AspNetCore.ReferrerBlock.git
16 | git
17 | MIT
18 | README.md
19 | Initial release with referrer blocking capabilities.
20 | true
21 | true
22 | true
23 | snupkg
24 | 2025
25 | ReferrerBlock.png
26 | True
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | True
46 | \
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | # ReferrerBlock
4 |
5 | [](https://www.nuget.org/packages/ReferrerBlock/) 
6 | [](https://opensource.org/licenses/MIT)
7 |
8 | ReferrerBlock middleware to block referrer spam and malicious traffic.
9 |
10 | ## ⚙️ Usage
11 | ```csharp
12 | var builder = WebApplication.CreateBuilder(args); var app = builder.Build();
13 |
14 | app.UseReferrerBlock();
15 |
16 | app.Run();
17 | ```
18 |
19 | The middleware uses default blocking rules. Optionally, you can customize them:
20 | ```csharp
21 | app.UseReferrerBlock(options => {
22 | options.BlockedDomains.Add("spam-site.com");
23 | options.BlockedTLDs.Add(".suspicious");
24 | options.BlockedPatterns.Add("malicious");
25 | options.BlockedSubdomainPrefixes.Add("spam");
26 | });
27 | ```
28 |
29 | ## 📝 Examples
30 |
31 | ### Disable default TLD blocking
32 | ```csharp
33 | app.UseReferrerBlock(options =>
34 | {
35 | options.BlockedTLDs.Clear(); // Remove all default TLDs
36 | options.BlockedDomains.Add("spam-site.com");
37 | });
38 | ```
39 |
40 | ### Use only custom rules
41 | ```csharp
42 | app.UseReferrerBlock(options =>
43 | {
44 | // Clear all default rules
45 | options.BlockedTLDs.Clear();
46 | options.BlockedDomains.Clear();
47 | options.BlockedPatterns.Clear();
48 | options.BlockedSubdomainPrefixes.Clear();
49 |
50 | // Add only your custom rules
51 | options.BlockedDomains.Add("spam-site.com");
52 | options.BlockedDomains.Add("malicious-domain.com");
53 | options.BlockedTLDs.Add(".scam");
54 | options.BlockedPatterns.Add("suspicious");
55 | options.BlockedSubdomainPrefixes.Add("bot");
56 | });
57 | ```
58 |
59 | ### Combine default rules with custom ones
60 | ```csharp
61 | app.UseReferrerBlock(options =>
62 | {
63 | // Keep default rules and add custom ones
64 | options.BlockedDomains.Add("spam-site.com");
65 | options.BlockedTLDs.Add(".suspicious");
66 | options.BlockedPatterns.Add("malicious");
67 | options.BlockedSubdomainPrefixes.Add("bot");
68 | });
69 | ```
70 |
71 | ### Block subdomain prefixes with numeric variations
72 | The `BlockedSubdomainPrefixes` option allows you to block subdomains that start with a specific prefix followed by optional digits.
73 |
74 | ```csharp
75 | app.UseReferrerBlock(options =>
76 | {
77 | // Block subdomains like: iqri., iqri1., iqri18., hk., hk1., hk12., etc.
78 | options.BlockedSubdomainPrefixes.Add("iqri");
79 | options.BlockedSubdomainPrefixes.Add("hk");
80 | options.BlockedSubdomainPrefixes.Add("spam");
81 | });
82 | ```
83 |
84 | This will block referrers like:
85 | - `iqri.example.com` ✅ blocked
86 | - `iqri1.spammer.net` ✅ blocked
87 | - `iqri18.malicious.org` ✅ blocked
88 | - `hk12.badsite.com` ✅ blocked
89 |
90 | But will NOT block:
91 | - `iqri1x.example.com` ❌ not blocked (has letters after digits)
92 | - `myiqri1.example.com` ❌ not blocked (prefix not at start)
93 | - `iqrisite.com` ❌ not blocked (in domain name, not subdomain)
94 |
95 | ## 📊 Blocked Domains
96 |
97 | See [BLOCKED_DOMAINS.md](BLOCKED_DOMAINS.md) for the complete list of blocked domains, TLDs, and patterns with their addition history.
98 |
--------------------------------------------------------------------------------
/AspNetCore.ReferrerBlock.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.ReferrerBlock", "src\AspNetCore.ReferrerBlock\AspNetCore.ReferrerBlock.csproj", "{9CBE5E87-B1AE-4C00-A298-721A1C5DA248}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.ReferrerBlock.Tests", "tests\AspNetCore.ReferrerBlock.Tests\AspNetCore.ReferrerBlock.Tests.csproj", "{4B002A73-5441-4E25-B900-4C036DF3B717}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Debug|x64 = Debug|x64
18 | Debug|x86 = Debug|x86
19 | Release|Any CPU = Release|Any CPU
20 | Release|x64 = Release|x64
21 | Release|x86 = Release|x86
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|x64.ActiveCfg = Debug|Any CPU
27 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|x64.Build.0 = Debug|Any CPU
28 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|x86.ActiveCfg = Debug|Any CPU
29 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Debug|x86.Build.0 = Debug|Any CPU
30 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|x64.ActiveCfg = Release|Any CPU
33 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|x64.Build.0 = Release|Any CPU
34 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|x86.ActiveCfg = Release|Any CPU
35 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248}.Release|x86.Build.0 = Release|Any CPU
36 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|x64.ActiveCfg = Debug|Any CPU
39 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|x64.Build.0 = Debug|Any CPU
40 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|x86.ActiveCfg = Debug|Any CPU
41 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Debug|x86.Build.0 = Debug|Any CPU
42 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|x64.ActiveCfg = Release|Any CPU
45 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|x64.Build.0 = Release|Any CPU
46 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|x86.ActiveCfg = Release|Any CPU
47 | {4B002A73-5441-4E25-B900-4C036DF3B717}.Release|x86.Build.0 = Release|Any CPU
48 | EndGlobalSection
49 | GlobalSection(SolutionProperties) = preSolution
50 | HideSolutionNode = FALSE
51 | EndGlobalSection
52 | GlobalSection(NestedProjects) = preSolution
53 | {9CBE5E87-B1AE-4C00-A298-721A1C5DA248} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
54 | {4B002A73-5441-4E25-B900-4C036DF3B717} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/src/AspNetCore.ReferrerBlock/Configuration/ReferrerBlockOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ReferrerBlock.Configuration;
2 |
3 | public class ReferrerBlockOptions
4 | {
5 | // List of blocked domain extensions
6 | public HashSet BlockedTLDs { get; set; } = new(StringComparer.OrdinalIgnoreCase)
7 | {
8 | ".biz.id",
9 | ".co.id",
10 | ".icu",
11 | ".in",
12 | ".xyz",
13 | };
14 |
15 | // List of blocked subdomain prefixes (e.g., "iqri" matches iqri., iqri1., iqri18., etc.)
16 | public HashSet BlockedSubdomainPrefixes { get; set; } = new(StringComparer.OrdinalIgnoreCase)
17 | {
18 | "iqri",
19 | "iqra",
20 | "hk",
21 | };
22 |
23 | // List of suspicious domain patterns (incomplete or generic domains)
24 | public HashSet BlockedPatterns { get; set; } = new(StringComparer.OrdinalIgnoreCase)
25 | {
26 | "cekitchenware",
27 | "crmlogichub",
28 | "ctysss",
29 | "followpathcrm",
30 | "hiplay704",
31 | "ikancupang",
32 | "jbuif",
33 | "karyapemuda",
34 | "kawruhbasa",
35 | "kaysdogs",
36 | "missywilkinson",
37 | "parakerja",
38 | "pvdwatersports",
39 | "pulbarholdings",
40 | "raymandservice",
41 | "rororush",
42 | "sscgdpracticeset",
43 | "sharpdrivers",
44 | "teknovidia",
45 | "tempatbelajar",
46 | "toworld123",
47 | };
48 |
49 | // List of blocked complete domains
50 | public HashSet BlockedDomains { get; set; } = new(StringComparer.OrdinalIgnoreCase)
51 | {
52 | "airplanesupdate.com",
53 | "aksarabrita.com",
54 | "barondamaluku.com",
55 | "befps.com",
56 | "biayakuliah.id",
57 | "brandipphoto.com",
58 | "chfaloans.com",
59 | "cipicipichips.com",
60 | "crminsightspro.com",
61 | "crmsoftwareedge.com",
62 | "cozynestplans.com",
63 | "def-atk.com",
64 | "dgfzg.com",
65 | "fiveminutesgames.com",
66 | "galaxys21manual.com",
67 | "gazdp.com",
68 | "genshindb.org",
69 | "globalinsuranceresourcecenter.com",
70 | "greenpeakshinecleaning.com",
71 | "hargamakanan.com",
72 | "hellyeahomeland.com",
73 | "holopoems.com",
74 | "indonesiamapan.com",
75 | "jawapos.com",
76 | "jy47.top",
77 | "kebumenupdate.com",
78 | "kiprahkita.com",
79 | "kuyasfoodexpress.com",
80 | "liveasiannews.com",
81 | "lovesyh.com",
82 | "mitsubishiprice.com",
83 | "nativeindonesia.com",
84 | "pengkicau.com",
85 | "periodcostume.top",
86 | "qccaaf.com",
87 | "maibirthday.com",
88 | "mdsdy.com",
89 | "mojok.co",
90 | "mqypc.com",
91 | "needlecorner.com",
92 | "nwgme.online",
93 | "oneadcc.com",
94 | "orderlos3reales1.com",
95 | "pauljimandjoespodcast.com",
96 | "patioinstallationcompanies.com",
97 | "pegawai.info",
98 | "penjudihijrah.com",
99 | "porosjambimedia.com",
100 | "roomvivo.com",
101 | "saastoolcompare.com",
102 | "samsungprinter.app",
103 | "scfxdl.com",
104 | "secondechance.org",
105 | "selectaproperty.us",
106 | "seohost.us",
107 | "smartcrmmanager.com",
108 | "soldatiki.info",
109 | "sportbooksbox.com",
110 | "syrfy.com",
111 | "teknowarta.com",
112 | "thebirdlore.com",
113 | "theguineapigexpert.com",
114 | "topikbisnis.com",
115 | "tipgalore.com",
116 | "tugassains.com",
117 | "trailerhomeleveling.com",
118 | "uaetiming.com",
119 | "vivarecharge.com",
120 | "vtpcys.com",
121 | "wavpn.click",
122 | "wartaupdate.com",
123 | "wagnerdom.local",
124 | "wantmybots.com",
125 | "wellsfederal.com",
126 | "worldhxx.com",
127 | "xlyanghub.com",
128 | "zhengzhouchendi.com",
129 | "zqauc.com",
130 | };
131 | }
132 |
--------------------------------------------------------------------------------
/src/AspNetCore.ReferrerBlock/Middleware/ReferrerBlockMiddleware.cs:
--------------------------------------------------------------------------------
1 | using ReferrerBlock.Configuration;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace ReferrerBlock.Middleware;
6 |
7 | public class ReferrerBlockMiddleware
8 | {
9 | private readonly RequestDelegate _next;
10 | private readonly ILogger _logger;
11 | private readonly ReferrerBlockOptions _options;
12 |
13 | public ReferrerBlockMiddleware(
14 | RequestDelegate next,
15 | ILogger logger,
16 | ReferrerBlockOptions options)
17 | {
18 | _next = next;
19 | _logger = logger;
20 | _options = options;
21 | }
22 |
23 | public async Task InvokeAsync(HttpContext context)
24 | {
25 | var referer = context.Request.Headers.Referer.ToString();
26 |
27 | if (!string.IsNullOrEmpty(referer))
28 | {
29 | try
30 | {
31 | // add a schema by defaut is missing
32 | if (!referer.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
33 | !referer.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
34 | {
35 | referer = "https://" + referer;
36 | }
37 |
38 |
39 | var uri = new Uri(referer);
40 | var host = uri.Host.ToLowerInvariant();
41 |
42 | if (IsBlocked(host))
43 | {
44 | _logger.LogWarning(
45 | "🚫 Referrer spam blocked: {Host} | IP: {IP} | Path: {Path}",
46 | host,
47 | context.Connection.RemoteIpAddress,
48 | context.Request.Path
49 | );
50 |
51 | // Slow down aggressive bots
52 | await Task.Delay(Random.Shared.Next(100, 500));
53 |
54 | context.Response.StatusCode = StatusCodes.Status410Gone;
55 | await context.Response.WriteAsync("Gone");
56 | return;
57 | }
58 | }
59 | catch (UriFormatException)
60 | {
61 | _logger.LogDebug("Malformed referrer detected: {Referer}", referer);
62 | }
63 | }
64 |
65 | await _next(context);
66 | }
67 |
68 | private bool IsBlocked(string host)
69 | {
70 | // Check TLDs - TLDs already contain the dot (e.g., ".icu")
71 | if (_options.BlockedTLDs?.Any(tld => !string.IsNullOrEmpty(tld) &&
72 | host.EndsWith(tld, StringComparison.OrdinalIgnoreCase)) == true)
73 | return true;
74 |
75 | // Check exact domains
76 | if (_options.BlockedDomains?.Contains(host, StringComparer.OrdinalIgnoreCase) == true)
77 | return true;
78 |
79 | // Check subdomains
80 | if (_options.BlockedDomains?.Any(blocked => !string.IsNullOrEmpty(blocked) &&
81 | host.EndsWith($".{blocked}", StringComparison.OrdinalIgnoreCase)) == true)
82 | return true;
83 |
84 | // Check patterns
85 | if (_options.BlockedPatterns?.Any(pattern => !string.IsNullOrEmpty(pattern) &&
86 | host.Contains(pattern, StringComparison.OrdinalIgnoreCase)) == true)
87 | return true;
88 |
89 | // Check subdomain prefixes (e.g., iqri1., hk12.)
90 | if (_options.BlockedSubdomainPrefixes?.Any(prefix => !string.IsNullOrEmpty(prefix) &&
91 | IsMatchingSubdomainPrefix(host, prefix)) == true)
92 | return true;
93 |
94 | return false;
95 | }
96 |
97 | ///
98 | /// Checks if host starts with prefix followed by optional digits and a dot.
99 | /// Matches: iqri., iqri1., iqri18. but not iqrix. or iqri1x.
100 | ///
101 | private static bool IsMatchingSubdomainPrefix(ReadOnlySpan host, string prefix)
102 | {
103 | if (!host.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
104 | return false;
105 |
106 | var remaining = host[prefix.Length..];
107 |
108 | // Skip any digits after the prefix
109 | int i = 0;
110 | while (i < remaining.Length && char.IsAsciiDigit(remaining[i]))
111 | i++;
112 |
113 | // Must be followed by a dot (subdomain separator)
114 | return i < remaining.Length && remaining[i] == '.';
115 | }
116 | }
--------------------------------------------------------------------------------
/BLOCKED_DOMAINS.md:
--------------------------------------------------------------------------------
1 | # Blocked Domains Registry
2 |
3 | This file documents spam/malicious domains, TLDs, and patterns blocked by default in ReferrerBlock middleware.
4 |
5 | ## Purpose
6 | - **Transparency**: Understand why domains are blocked
7 | - **Maintenance**: Track when domains were added
8 | - **Contribution**: Help reviewers validate new additions
9 |
10 | ## Reporting Issues
11 | If a domain is incorrectly blocked, please [open an issue](https://github.com/tossnet/AspNetCore.ReferrerBlock/issues).
12 |
13 | ## WHOIS Lookup
14 | Use [whois.com](https://www.whois.com/) or similar services to investigate suspicious domains.
15 |
16 | ---
17 |
18 | ## Blocked Items
19 |
20 | | Type | Value | Added | Reason |
21 | |------|-------|-------|--------|
22 | | Domain | (hk n.)airplanesupdate.com | 2025-11-23 | Referrer spam. Multiple subdomain variants) |
23 | | Domain | periodcostume.top | 2025-11-23 | Referrer spam |
24 | | Domain | globalinsuranceresourcecenter.com | 2025-11-23 | Referrer spam |
25 | | Domain | liveasiannews.com | 2025-11-24 | Referrer spam |
26 | | Domain | indonesiamapan.com | 2025-11-24 | Referrer spam |
27 | | Domain | biayakuliah.id | 2025-11-24 | Referrer spam |
28 | | Domain | iklanbarisposkota.top | 2025-11-24 | Referrer spam |
29 | | Domain | chfaloans.com | 2025-11-24 | Referrer spam |
30 | | Domain | wagnerdom.local | 2025-11-25 | Referrer spam |
31 | | Domain | brandipphoto.com | 2025-11-26 | Referrer spam |
32 | | Domain | (glad., eth.)teknowarta.com | 2025-11-26 | Referrer spam. Multiple subdomain variants |
33 | | Domain | def-atk.com | 2025-11-26 | Referrer spam |
34 | | Domain | (tools.)genshindb.org | 2025-11-26 | Referrer spam |
35 | | Domain | hellyeahomeland.com | 2025-11-26 | Referrer spam |
36 | | Domain | petalsearch.com | 2025-11-26 | Referrer spam |
37 | | Domain | (hk n.)seohost.us | 2025-11-27 | Referrer spam. Multiple subdomain variants |
38 | | Domain | (sports.)nativeindonesia.com | 2025-11-27 | Referrer spam |
39 | | Domain | (hk n.)selectaproperty.us | 2025-11-28 | Referrer spam. Multiple subdomain variants |
40 | | Domain | (iqri n.)pauljimandjoespodcast.com | 2025-11-28 | Referrer spam. Multiple subdomain variants |
41 | | Domain | thebirdlore.com | 2025-11-28 | Referrer spam |
42 | | Domain | porosjambimedia.com | 2025-11-28 | Referrer spam |
43 | | Domain | (iqri n.)mitsubishiprice.com | 2025-11-30 | Referrer spam. Multiple subdomain variants |
44 | | Domain | lovesyh.com | 2025-12-01 | Referrer spam |
45 | | Domain | tipgalore.com | 2025-12-01 | Referrer spam |
46 | | Domain | vtpcys.com | 2025-12-02 | Referrer spam |
47 | | Domain | scfxdl.com | 2025-12-03 | Referrer spam |
48 | | Domain | roomvivo.com | 2025-12-03 | Referrer spam |
49 | | Domain | wavpn.click | 2025-12-03 | Referrer spam |
50 | | Domain | crminsightspro.com | 2025-12-05 | Referrer spam |
51 | | Domain | aaruslan.soldatiki.info | 2025-12-05 | Referrer spam |
52 | | Domain | vip.vivarecharge.com | 2025-12-05 | Referrer spam |
53 | | Domain | fintech.syrfy.com | 2025-12-05 | Referrer spam |
54 | | Domain | jy47.top | 2025-12-05 | Referrer spam |
55 | | Domain | refinance.qccaaf.com | 2025-12-05 | Referrer spam |
56 | | Domain | shareit.nwgme.online | 2025-12-05 | Referrer spam |
57 | | Domain | goes.greenpeakshinecleaning.com | 2025-12-06 | Referrer spam |
58 | | Domain | education.jawapos.com | 2025-12-06 | Referrer spam |
59 | | Domain | gs2.trailerhomeleveling.com | 2025-12-06 | Referrer spam |
60 | | Domain | fn.needlecorner.com | 2025-12-06 | Referrer spam |
61 | | Domain | jersey.kuyasfoodexpress.com | 2025-12-06 | Referrer spam |
62 | | Domain | ca.galaxys21manual.com | 2025-12-07 | Referrer spam |
63 | | Domain | dgfzg.com | 2025-12-08 | Referrer spam |
64 | | Domain | guide.zqauc.com | 2025-12-08 | Referrer spam |
65 | | Domain | wantmybots.com | 2025-12-09 | Referrer spam. Multiple subdomain variants |
66 | | Domain | saastoolcompare.com | 2025-12-09 | Referrer spam. |
67 | | Domain | tr.theguineapigexpert.com | 2025-12-10 | Referrer spam. |
68 | | Domain | crmsoftwareedge.com | 2025-12-10 | Referrer spam. |
69 | | Domain | topikbisnis.com | 2025-12-10 | Referrer spam. Multiple subdomain variants |
70 | | Domain | (iqri n.,hk n.)penjudihijrah.com | 2025-12-10 | Referrer spam. Multiple subdomain variants |
71 | | Domain | fiveminutesgames.com | 2025-12-10 | Referrer spam. |
72 | | Domain | s1.oneadcc.com | 2025-12-11 | Referrer spam. |
73 | | Domain | smartcrmmanager.com | 2025-12-11 | Referrer spam. |
74 | | Domain | luxury.maibirthday.com | 2025-12-12 | Referrer spam. |
75 | | Domain | lawyer.mqypc.com | 2025-12-12 | Referrer spam. |
76 | | Domain | dog.worldhxx.com | 2025-12-12 | Referrer spam. |
77 | | Domain | digilife.hargamakanan.com | 2025-12-13 | Referrer spam. |
78 | | Domain | ww51.cipicipichips.com | 2025-12-13 | Referrer spam. |
79 | | Domain | as.pegawai.info | 2025-12-13 | Referrer spam. |
80 | | Domain | one.uaetiming.com | 2025-12-13 | Referrer spam. |
81 | | Domain | mdsdy.com | 2025-12-13 | Referrer spam. |
82 | | Domain | eat.befps.com | 2025-12-13 | Referrer spam. |
83 | | Domain | sl.sportbooksbox.com | 2025-12-13 | Referrer spam. |
84 | | Domain | depe.holopoems.com | 2025-12-13 | Referrer spam. |
85 | | Domain | ag.samsungprinter.app | 2025-12-13 | Referrer spam. |
86 | | Domain | iqra(n). | 2025-12-13 | Subdomain Referrer spam. |
87 |
88 |
89 |
90 |
91 | ---
92 |
93 | ## How to Contribute
94 |
95 | 1. Verify the domain/pattern is actually spam (check analytics, WHOIS)
96 | 2. Add entry to the table with current date
97 | 3. Submit a pull request with evidence/reasoning
98 | 4. Keep entries sorted alphabetically by value within each type
99 |
100 | **Types:**
101 | - **Domain**: Specific domain names (e.g., `spam-site.com`)
102 | - **TLD**: Top-level domains (e.g., `.xyz`, `.top`)
103 | - **Pattern**: Keyword patterns in referrer URLs (e.g., `casino`, `viagra`)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 | *.env
13 |
14 | # User-specific files (MonoDevelop/Xamarin Studio)
15 | *.userprefs
16 |
17 | # Mono auto generated files
18 | mono_crash.*
19 |
20 | # Build results
21 | [Dd]ebug/
22 | [Dd]ebugPublic/
23 | [Rr]elease/
24 | [Rr]eleases/
25 | x64/
26 | x86/
27 | [Ww][Ii][Nn]32/
28 | [Aa][Rr][Mm]/
29 | [Aa][Rr][Mm]64/
30 | [Aa][Rr][Mm]64[Ee][Cc]/
31 | bld/
32 | [Oo]bj/
33 | [Oo]ut/
34 | [Ll]og/
35 | [Ll]ogs/
36 |
37 | # Build results on 'Bin' directories
38 | **/[Bb]in/*
39 | # Uncomment if you have tasks that rely on *.refresh files to move binaries
40 | # (https://github.com/github/gitignore/pull/3736)
41 | #!**/[Bb]in/*.refresh
42 |
43 | # Visual Studio 2015/2017 cache/options directory
44 | .vs/
45 | # Uncomment if you have tasks that create the project's static files in wwwroot
46 | #wwwroot/
47 |
48 | # Visual Studio 2017 auto generated files
49 | Generated\ Files/
50 |
51 | # MSTest test Results
52 | [Tt]est[Rr]esult*/
53 | [Bb]uild[Ll]og.*
54 | *.trx
55 |
56 | # NUnit
57 | *.VisualState.xml
58 | TestResult.xml
59 | nunit-*.xml
60 |
61 | # Approval Tests result files
62 | *.received.*
63 |
64 | # Build Results of an ATL Project
65 | [Dd]ebugPS/
66 | [Rr]eleasePS/
67 | dlldata.c
68 |
69 | # Benchmark Results
70 | BenchmarkDotNet.Artifacts/
71 |
72 | # .NET Core
73 | project.lock.json
74 | project.fragment.lock.json
75 | artifacts/
76 |
77 | # ASP.NET Scaffolding
78 | ScaffoldingReadMe.txt
79 |
80 | # StyleCop
81 | StyleCopReport.xml
82 |
83 | # Files built by Visual Studio
84 | *_i.c
85 | *_p.c
86 | *_h.h
87 | *.ilk
88 | *.meta
89 | *.obj
90 | *.idb
91 | *.iobj
92 | *.pch
93 | *.pdb
94 | *.ipdb
95 | *.pgc
96 | *.pgd
97 | *.rsp
98 | # but not Directory.Build.rsp, as it configures directory-level build defaults
99 | !Directory.Build.rsp
100 | *.sbr
101 | *.tlb
102 | *.tli
103 | *.tlh
104 | *.tmp
105 | *.tmp_proj
106 | *_wpftmp.csproj
107 | *.log
108 | *.tlog
109 | *.vspscc
110 | *.vssscc
111 | .builds
112 | *.pidb
113 | *.svclog
114 | *.scc
115 |
116 | # Chutzpah Test files
117 | _Chutzpah*
118 |
119 | # Visual C++ cache files
120 | ipch/
121 | *.aps
122 | *.ncb
123 | *.opendb
124 | *.opensdf
125 | *.sdf
126 | *.cachefile
127 | *.VC.db
128 | *.VC.VC.opendb
129 |
130 | # Visual Studio profiler
131 | *.psess
132 | *.vsp
133 | *.vspx
134 | *.sap
135 |
136 | # Visual Studio Trace Files
137 | *.e2e
138 |
139 | # TFS 2012 Local Workspace
140 | $tf/
141 |
142 | # Guidance Automation Toolkit
143 | *.gpState
144 |
145 | # ReSharper is a .NET coding add-in
146 | _ReSharper*/
147 | *.[Rr]e[Ss]harper
148 | *.DotSettings.user
149 |
150 | # TeamCity is a build add-in
151 | _TeamCity*
152 |
153 | # DotCover is a Code Coverage Tool
154 | *.dotCover
155 |
156 | # AxoCover is a Code Coverage Tool
157 | .axoCover/*
158 | !.axoCover/settings.json
159 |
160 | # Coverlet is a free, cross platform Code Coverage Tool
161 | coverage*.json
162 | coverage*.xml
163 | coverage*.info
164 |
165 | # Visual Studio code coverage results
166 | *.coverage
167 | *.coveragexml
168 |
169 | # NCrunch
170 | _NCrunch_*
171 | .NCrunch_*
172 | .*crunch*.local.xml
173 | nCrunchTemp_*
174 |
175 | # MightyMoose
176 | *.mm.*
177 | AutoTest.Net/
178 |
179 | # Web workbench (sass)
180 | .sass-cache/
181 |
182 | # Installshield output folder
183 | [Ee]xpress/
184 |
185 | # DocProject is a documentation generator add-in
186 | DocProject/buildhelp/
187 | DocProject/Help/*.HxT
188 | DocProject/Help/*.HxC
189 | DocProject/Help/*.hhc
190 | DocProject/Help/*.hhk
191 | DocProject/Help/*.hhp
192 | DocProject/Help/Html2
193 | DocProject/Help/html
194 |
195 | # Click-Once directory
196 | publish/
197 |
198 | # Publish Web Output
199 | *.[Pp]ublish.xml
200 | *.azurePubxml
201 | # Note: Comment the next line if you want to checkin your web deploy settings,
202 | # but database connection strings (with potential passwords) will be unencrypted
203 | *.pubxml
204 | *.publishproj
205 |
206 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
207 | # checkin your Azure Web App publish settings, but sensitive information contained
208 | # in these scripts will be unencrypted
209 | PublishScripts/
210 |
211 | # NuGet Packages
212 | *.nupkg
213 | # NuGet Symbol Packages
214 | *.snupkg
215 | # The packages folder can be ignored because of Package Restore
216 | **/[Pp]ackages/*
217 | # except build/, which is used as an MSBuild target.
218 | !**/[Pp]ackages/build/
219 | # Uncomment if necessary however generally it will be regenerated when needed
220 | #!**/[Pp]ackages/repositories.config
221 | # NuGet v3's project.json files produces more ignorable files
222 | *.nuget.props
223 | *.nuget.targets
224 |
225 | # Microsoft Azure Build Output
226 | csx/
227 | *.build.csdef
228 |
229 | # Microsoft Azure Emulator
230 | ecf/
231 | rcf/
232 |
233 | # Windows Store app package directories and files
234 | AppPackages/
235 | BundleArtifacts/
236 | Package.StoreAssociation.xml
237 | _pkginfo.txt
238 | *.appx
239 | *.appxbundle
240 | *.appxupload
241 |
242 | # Visual Studio cache files
243 | # files ending in .cache can be ignored
244 | *.[Cc]ache
245 | # but keep track of directories ending in .cache
246 | !?*.[Cc]ache/
247 |
248 | # Others
249 | ClientBin/
250 | ~$*
251 | *~
252 | *.dbmdl
253 | *.dbproj.schemaview
254 | *.jfm
255 | *.pfx
256 | *.publishsettings
257 | orleans.codegen.cs
258 |
259 | # Including strong name files can present a security risk
260 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
261 | #*.snk
262 |
263 | # Since there are multiple workflows, uncomment next line to ignore bower_components
264 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
265 | #bower_components/
266 |
267 | # RIA/Silverlight projects
268 | Generated_Code/
269 |
270 | # Backup & report files from converting an old project file
271 | # to a newer Visual Studio version. Backup files are not needed,
272 | # because we have git ;-)
273 | _UpgradeReport_Files/
274 | Backup*/
275 | UpgradeLog*.XML
276 | UpgradeLog*.htm
277 | ServiceFabricBackup/
278 | *.rptproj.bak
279 |
280 | # SQL Server files
281 | *.mdf
282 | *.ldf
283 | *.ndf
284 |
285 | # Business Intelligence projects
286 | *.rdl.data
287 | *.bim.layout
288 | *.bim_*.settings
289 | *.rptproj.rsuser
290 | *- [Bb]ackup.rdl
291 | *- [Bb]ackup ([0-9]).rdl
292 | *- [Bb]ackup ([0-9][0-9]).rdl
293 |
294 | # Microsoft Fakes
295 | FakesAssemblies/
296 |
297 | # GhostDoc plugin setting file
298 | *.GhostDoc.xml
299 |
300 | # Node.js Tools for Visual Studio
301 | .ntvs_analysis.dat
302 | node_modules/
303 |
304 | # Visual Studio 6 build log
305 | *.plg
306 |
307 | # Visual Studio 6 workspace options file
308 | *.opt
309 |
310 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
311 | *.vbw
312 |
313 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
314 | *.vbp
315 |
316 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
317 | *.dsw
318 | *.dsp
319 |
320 | # Visual Studio 6 technical files
321 | *.ncb
322 | *.aps
323 |
324 | # Visual Studio LightSwitch build output
325 | **/*.HTMLClient/GeneratedArtifacts
326 | **/*.DesktopClient/GeneratedArtifacts
327 | **/*.DesktopClient/ModelManifest.xml
328 | **/*.Server/GeneratedArtifacts
329 | **/*.Server/ModelManifest.xml
330 | _Pvt_Extensions
331 |
332 | # Paket dependency manager
333 | **/.paket/paket.exe
334 | paket-files/
335 |
336 | # FAKE - F# Make
337 | **/.fake/
338 |
339 | # CodeRush personal settings
340 | **/.cr/personal
341 |
342 | # Python Tools for Visual Studio (PTVS)
343 | **/__pycache__/
344 | *.pyc
345 |
346 | # Cake - Uncomment if you are using it
347 | #tools/**
348 | #!tools/packages.config
349 |
350 | # Tabs Studio
351 | *.tss
352 |
353 | # Telerik's JustMock configuration file
354 | *.jmconfig
355 |
356 | # BizTalk build output
357 | *.btp.cs
358 | *.btm.cs
359 | *.odx.cs
360 | *.xsd.cs
361 |
362 | # OpenCover UI analysis results
363 | OpenCover/
364 |
365 | # Azure Stream Analytics local run output
366 | ASALocalRun/
367 |
368 | # MSBuild Binary and Structured Log
369 | *.binlog
370 | MSBuild_Logs/
371 |
372 | # AWS SAM Build and Temporary Artifacts folder
373 | .aws-sam
374 |
375 | # NVidia Nsight GPU debugger configuration file
376 | *.nvuser
377 |
378 | # MFractors (Xamarin productivity tool) working folder
379 | **/.mfractor/
380 |
381 | # Local History for Visual Studio
382 | **/.localhistory/
383 |
384 | # Visual Studio History (VSHistory) files
385 | .vshistory/
386 |
387 | # BeatPulse healthcheck temp database
388 | healthchecksdb
389 |
390 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
391 | MigrationBackup/
392 |
393 | # Ionide (cross platform F# VS Code tools) working folder
394 | **/.ionide/
395 |
396 | # Fody - auto-generated XML schema
397 | FodyWeavers.xsd
398 |
399 | # VS Code files for those working on multiple tools
400 | .vscode/*
401 | !.vscode/settings.json
402 | !.vscode/tasks.json
403 | !.vscode/launch.json
404 | !.vscode/extensions.json
405 | !.vscode/*.code-snippets
406 |
407 | # Local History for Visual Studio Code
408 | .history/
409 |
410 | # Built Visual Studio Code Extensions
411 | *.vsix
412 |
413 | # Windows Installer files from build outputs
414 | *.cab
415 | *.msi
416 | *.msix
417 | *.msm
418 | *.msp
419 |
--------------------------------------------------------------------------------
/tests/AspNetCore.ReferrerBlock.Tests/ReferrerBlockMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | using ReferrerBlock.Configuration;
2 | using ReferrerBlock.Middleware;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.Extensions.Logging;
5 | using Moq;
6 | using System.Net;
7 |
8 | namespace AspNetCore.ReferrerBlock.Tests;
9 |
10 | [TestClass]
11 | public class ReferrerBlockMiddlewareTests
12 | {
13 | private Mock _nextMock;
14 | private Mock> _loggerMock;
15 | private ReferrerBlockOptions _options;
16 | private DefaultHttpContext _httpContext;
17 |
18 | [TestInitialize]
19 | public void Setup()
20 | {
21 | _nextMock = new Mock();
22 | _loggerMock = new Mock>();
23 | _options = new ReferrerBlockOptions();
24 | _httpContext = new DefaultHttpContext();
25 | _httpContext.Connection.RemoteIpAddress = IPAddress.Parse("192.168.1.1");
26 | _httpContext.Request.Path = "/test";
27 | }
28 |
29 | #region Tests sans Referer
30 |
31 | [TestMethod]
32 | public async Task NoReferer_ShouldCallNext()
33 | {
34 | // Arrange
35 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
36 |
37 | // Act
38 | await middleware.InvokeAsync(_httpContext);
39 |
40 | // Assert
41 | _nextMock.Verify(next => next(_httpContext), Times.Once);
42 | }
43 |
44 | [TestMethod]
45 | public async Task EmptyReferer_ShouldCallNext()
46 | {
47 | // Arrange
48 | _httpContext.Request.Headers.Referer = string.Empty;
49 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
50 |
51 | // Act
52 | await middleware.InvokeAsync(_httpContext);
53 |
54 | // Assert
55 | _nextMock.Verify(next => next(_httpContext), Times.Once);
56 | }
57 |
58 | #endregion
59 |
60 | #region Tests avec Referer valide (non bloqué)
61 |
62 | [TestMethod]
63 | public async Task ValidReferer_NotBlocked_ShouldCallNext()
64 | {
65 | // Arrange
66 | _httpContext.Request.Headers.Referer = "https://www.google.com";
67 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
68 |
69 | // Act
70 | await middleware.InvokeAsync(_httpContext);
71 |
72 | // Assert
73 | _nextMock.Verify(next => next(_httpContext), Times.Once);
74 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
75 | }
76 |
77 | [TestMethod]
78 | public async Task ValidReferer_AllowedTLD_ShouldCallNext()
79 | {
80 | // Arrange
81 | _httpContext.Request.Headers.Referer = "https://example.com";
82 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
83 |
84 | // Act
85 | await middleware.InvokeAsync(_httpContext);
86 |
87 | // Assert
88 | _nextMock.Verify(next => next(_httpContext), Times.Once);
89 | }
90 |
91 | #endregion
92 |
93 | #region Tests TLD bloqués
94 |
95 | [TestMethod]
96 | public async Task BlockedTLD_ICU_ShouldReturn410()
97 | {
98 | // Arrange
99 | _httpContext.Request.Headers.Referer = "https://spam.icu";
100 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
101 |
102 | // Act
103 | await middleware.InvokeAsync(_httpContext);
104 |
105 | // Assert
106 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
107 | _nextMock.Verify(next => next(_httpContext), Times.Never);
108 | }
109 |
110 | [TestMethod]
111 | public async Task BlockedTLD_XYZ_ShouldReturn410()
112 | {
113 | // Arrange
114 | _httpContext.Request.Headers.Referer = "https://malicious.xyz";
115 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
116 |
117 | // Act
118 | await middleware.InvokeAsync(_httpContext);
119 |
120 | // Assert
121 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
122 | _nextMock.Verify(next => next(_httpContext), Times.Never);
123 | }
124 |
125 | [TestMethod]
126 | public async Task BlockedTLD_BizId_ShouldReturn410()
127 | {
128 | // Arrange
129 | _httpContext.Request.Headers.Referer = "https://spam.biz.id";
130 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
131 |
132 | // Act
133 | await middleware.InvokeAsync(_httpContext);
134 |
135 | // Assert
136 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
137 | }
138 |
139 | [TestMethod]
140 | public async Task BlockedTLD_id_ShouldReturn410()
141 | {
142 | // Arrange
143 | _httpContext.Request.Headers.Referer = "hk4.ikancupang.id";
144 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
145 |
146 | // Act
147 | await middleware.InvokeAsync(_httpContext);
148 |
149 | // Assert
150 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
151 | }
152 |
153 | [TestMethod]
154 | public async Task BlockedTLD_CaseInsensitive_ShouldReturn410()
155 | {
156 | // Arrange
157 | _httpContext.Request.Headers.Referer = "https://SPAM.ICU";
158 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
159 |
160 | // Act
161 | await middleware.InvokeAsync(_httpContext);
162 |
163 | // Assert
164 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
165 | }
166 |
167 | #endregion
168 |
169 | #region Tests domaines bloqués
170 |
171 | [TestMethod]
172 | public async Task BlockedDomain_Exact_ShouldReturn410()
173 | {
174 | // Arrange
175 | _httpContext.Request.Headers.Referer = "https://gazdp.com";
176 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
177 |
178 | // Act
179 | await middleware.InvokeAsync(_httpContext);
180 |
181 | // Assert
182 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
183 | _nextMock.Verify(next => next(_httpContext), Times.Never);
184 | }
185 |
186 | [TestMethod]
187 | public async Task BlockedDomain_Subdomain_ShouldReturn410()
188 | {
189 | // Arrange
190 | _httpContext.Request.Headers.Referer = "https://www.gazdp.com";
191 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
192 |
193 | // Act
194 | await middleware.InvokeAsync(_httpContext);
195 |
196 | // Assert
197 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
198 | }
199 |
200 | [TestMethod]
201 | public async Task BlockedDomain_DeepSubdomain_ShouldReturn410()
202 | {
203 | // Arrange
204 | _httpContext.Request.Headers.Referer = "https://sub.example.gazdp.com";
205 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
206 |
207 | // Act
208 | await middleware.InvokeAsync(_httpContext);
209 |
210 | // Assert
211 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
212 | }
213 |
214 | [TestMethod]
215 | public async Task BlockedDomain_CaseInsensitive_ShouldReturn410()
216 | {
217 | // Arrange
218 | _httpContext.Request.Headers.Referer = "https://GAZDP.COM";
219 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
220 |
221 | // Act
222 | await middleware.InvokeAsync(_httpContext);
223 |
224 | // Assert
225 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
226 | }
227 |
228 | [TestMethod]
229 | public async Task BlockedDomain_MojokCo_Subdomain_ShouldReturn410()
230 | {
231 | // Arrange
232 | _httpContext.Request.Headers.Referer = "https://lifestyle.mojok.co/";
233 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
234 |
235 | // Act
236 | await middleware.InvokeAsync(_httpContext);
237 |
238 | // Assert
239 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
240 | _nextMock.Verify(next => next(_httpContext), Times.Never);
241 | }
242 |
243 | #endregion
244 |
245 | #region Tests subdomain prefixes bloqués
246 |
247 | [TestMethod]
248 | public async Task BlockedSubdomainPrefix_Iqri_ShouldReturn410()
249 | {
250 | // Arrange
251 | _httpContext.Request.Headers.Referer = "https://iqri.example.com";
252 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
253 |
254 | // Act
255 | await middleware.InvokeAsync(_httpContext);
256 |
257 | // Assert
258 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
259 | _nextMock.Verify(next => next(_httpContext), Times.Never);
260 | }
261 |
262 | [TestMethod]
263 | public async Task BlockedSubdomainPrefix_IqriWithDigit_ShouldReturn410()
264 | {
265 | // Arrange
266 | _httpContext.Request.Headers.Referer = "https://iqri1.spammer.net";
267 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
268 |
269 | // Act
270 | await middleware.InvokeAsync(_httpContext);
271 |
272 | // Assert
273 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
274 | _nextMock.Verify(next => next(_httpContext), Times.Never);
275 | }
276 |
277 | [TestMethod]
278 | public async Task BlockedSubdomainPrefix_IqriWithMultipleDigits_ShouldReturn410()
279 | {
280 | // Arrange
281 | _httpContext.Request.Headers.Referer = "https://iqri18.malicious.org";
282 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
283 |
284 | // Act
285 | await middleware.InvokeAsync(_httpContext);
286 |
287 | // Assert
288 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
289 | _nextMock.Verify(next => next(_httpContext), Times.Never);
290 | }
291 |
292 | [TestMethod]
293 | public async Task BlockedSubdomainPrefix_Hk_ShouldReturn410()
294 | {
295 | // Arrange
296 | _httpContext.Request.Headers.Referer = "https://hk.spam.com";
297 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
298 |
299 | // Act
300 | await middleware.InvokeAsync(_httpContext);
301 |
302 | // Assert
303 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
304 | _nextMock.Verify(next => next(_httpContext), Times.Never);
305 | }
306 |
307 | [TestMethod]
308 | public async Task BlockedSubdomainPrefix_HkWithDigits_ShouldReturn410()
309 | {
310 | // Arrange
311 | _httpContext.Request.Headers.Referer = "https://hk12.badsite.net";
312 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
313 |
314 | // Act
315 | await middleware.InvokeAsync(_httpContext);
316 |
317 | // Assert
318 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
319 | _nextMock.Verify(next => next(_httpContext), Times.Never);
320 | }
321 |
322 | [TestMethod]
323 | public async Task BlockedSubdomainPrefix_CaseInsensitive_ShouldReturn410()
324 | {
325 | // Arrange
326 | _httpContext.Request.Headers.Referer = "https://IQRI5.EXAMPLE.COM";
327 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
328 |
329 | // Act
330 | await middleware.InvokeAsync(_httpContext);
331 |
332 | // Assert
333 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
334 | }
335 |
336 | [TestMethod]
337 | public async Task BlockedSubdomainPrefix_WithLettersAfterDigits_ShouldNotBlock()
338 | {
339 | // Arrange - "iqri1x" should NOT match because it has letters after digits
340 | _httpContext.Request.Headers.Referer = "https://iqri1x.example.com";
341 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
342 |
343 | // Act
344 | await middleware.InvokeAsync(_httpContext);
345 |
346 | // Assert
347 | _nextMock.Verify(next => next(_httpContext), Times.Once);
348 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
349 | }
350 |
351 | [TestMethod]
352 | public async Task BlockedSubdomainPrefix_NotAtStart_ShouldNotBlock()
353 | {
354 | // Arrange - "subiqri1" should NOT match because iqri is not at the start
355 | _httpContext.Request.Headers.Referer = "https://subiqri1.example.com";
356 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
357 |
358 | // Act
359 | await middleware.InvokeAsync(_httpContext);
360 |
361 | // Assert
362 | _nextMock.Verify(next => next(_httpContext), Times.Once);
363 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
364 | }
365 |
366 | [TestMethod]
367 | public async Task BlockedSubdomainPrefix_InDomainName_ShouldNotBlock()
368 | {
369 | // Arrange - "iqri" in domain name (not subdomain) should NOT be blocked by prefix rules
370 | _httpContext.Request.Headers.Referer = "https://iqrisite.com";
371 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
372 |
373 | // Act
374 | await middleware.InvokeAsync(_httpContext);
375 |
376 | // Assert
377 | _nextMock.Verify(next => next(_httpContext), Times.Once);
378 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
379 | }
380 |
381 | #endregion
382 |
383 | #region Tests patterns bloqués
384 |
385 | [TestMethod]
386 | public async Task BlockedPattern_ShouldReturn410()
387 | {
388 | // Arrange
389 | _httpContext.Request.Headers.Referer = "https://ctysss.example.com";
390 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
391 |
392 | // Act
393 | await middleware.InvokeAsync(_httpContext);
394 |
395 | // Assert
396 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
397 | _nextMock.Verify(next => next(_httpContext), Times.Never);
398 | }
399 |
400 | [TestMethod]
401 | public async Task BlockedPattern_InMiddle_ShouldReturn410()
402 | {
403 | // Arrange
404 | _httpContext.Request.Headers.Referer = "https://shop-ctysss-online.com";
405 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
406 |
407 | // Act
408 | await middleware.InvokeAsync(_httpContext);
409 |
410 | // Assert
411 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
412 | }
413 |
414 | [TestMethod]
415 | public async Task BlockedPattern_CaseInsensitive_ShouldReturn410()
416 | {
417 | // Arrange
418 | _httpContext.Request.Headers.Referer = "https://CTYSSS.example.com";
419 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
420 |
421 | // Act
422 | await middleware.InvokeAsync(_httpContext);
423 |
424 | // Assert
425 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
426 | }
427 |
428 | #endregion
429 |
430 | #region Tests d'erreurs
431 |
432 | [TestMethod]
433 | public async Task MalformedReferer_ShouldCallNext()
434 | {
435 | // Arrange - Use a truly malformed URI that will throw UriFormatException
436 | _httpContext.Request.Headers.Referer = "https://[invalid-ipv6";
437 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
438 |
439 | // Act
440 | await middleware.InvokeAsync(_httpContext);
441 |
442 | // Assert
443 | _nextMock.Verify(next => next(_httpContext), Times.Once);
444 | _loggerMock.Verify(
445 | x => x.Log(
446 | LogLevel.Debug,
447 | It.IsAny(),
448 | It.Is((v, t) => v.ToString()!.Contains("Malformed referrer detected")),
449 | It.IsAny(),
450 | It.IsAny>()),
451 | Times.Once);
452 | }
453 |
454 | #endregion
455 |
456 | #region Tests de logging
457 |
458 | [TestMethod]
459 | public async Task BlockedReferer_ShouldLogWarning()
460 | {
461 | // Arrange
462 | _httpContext.Request.Headers.Referer = "https://spam.icu";
463 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
464 |
465 | // Act
466 | await middleware.InvokeAsync(_httpContext);
467 |
468 | // Assert
469 | _loggerMock.Verify(
470 | x => x.Log(
471 | LogLevel.Warning,
472 | It.IsAny(),
473 | It.Is((v, t) => v.ToString()!.Contains("Referrer spam blocked")),
474 | It.IsAny(),
475 | It.IsAny>()),
476 | Times.Once);
477 | }
478 |
479 | #endregion
480 |
481 | #region Tests de réponse
482 |
483 | [TestMethod]
484 | public async Task BlockedReferer_ShouldReturnHTMLResponse()
485 | {
486 | // Arrange
487 | _httpContext.Request.Headers.Referer = "https://spam.icu";
488 | _httpContext.Response.Body = new MemoryStream();
489 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
490 |
491 | // Act
492 | await middleware.InvokeAsync(_httpContext);
493 |
494 | // Assert
495 | _httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
496 | using var reader = new StreamReader(_httpContext.Response.Body);
497 | var responseBody = await reader.ReadToEndAsync();
498 |
499 | Assert.IsTrue(responseBody.Contains(""));
500 | Assert.IsTrue(responseBody.Contains("Gone"));
501 | }
502 |
503 | [TestMethod]
504 | public async Task BlockedReferer_ShouldIncludeDelay()
505 | {
506 | // Arrange
507 | _httpContext.Request.Headers.Referer = "https://spam.icu";
508 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
509 | var stopwatch = System.Diagnostics.Stopwatch.StartNew();
510 |
511 | // Act
512 | await middleware.InvokeAsync(_httpContext);
513 | stopwatch.Stop();
514 |
515 | // Assert
516 | Assert.IsTrue(stopwatch.ElapsedMilliseconds >= 100, "Le délai devrait être d'au moins 100ms");
517 | Assert.IsTrue(stopwatch.ElapsedMilliseconds < 600, "Le délai ne devrait pas dépasser 500ms + marge");
518 | }
519 |
520 | #endregion
521 |
522 | #region Tests de scénarios réels
523 |
524 | [TestMethod]
525 | public async Task MultipleBlockedDomains_ShouldBlockAll()
526 | {
527 | // Arrange
528 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
529 | var blockedUrls = new[]
530 | {
531 | "https://gazdp.com",
532 | "https://spam.icu",
533 | "https://ctysss-shop.com"
534 | };
535 |
536 | foreach (var url in blockedUrls)
537 | {
538 | var context = new DefaultHttpContext();
539 | context.Request.Headers.Referer = url;
540 | context.Connection.RemoteIpAddress = IPAddress.Parse("192.168.1.1");
541 |
542 | // Act
543 | await middleware.InvokeAsync(context);
544 |
545 | // Assert
546 | Assert.AreEqual(StatusCodes.Status410Gone, context.Response.StatusCode, $"Failed for URL: {url}");
547 | }
548 | }
549 |
550 | [TestMethod]
551 | public async Task HTTPAndHTTPS_BothShouldBeBlocked()
552 | {
553 | // Arrange
554 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
555 |
556 | var httpContext = new DefaultHttpContext();
557 | httpContext.Request.Headers.Referer = "http://spam.icu";
558 | httpContext.Connection.RemoteIpAddress = IPAddress.Parse("192.168.1.1");
559 |
560 | var httpsContext = new DefaultHttpContext();
561 | httpsContext.Request.Headers.Referer = "https://spam.icu";
562 | httpsContext.Connection.RemoteIpAddress = IPAddress.Parse("192.168.1.1");
563 |
564 | // Act
565 | await middleware.InvokeAsync(httpContext);
566 | await middleware.InvokeAsync(httpsContext);
567 |
568 | // Assert
569 | Assert.AreEqual(StatusCodes.Status410Gone, httpContext.Response.StatusCode);
570 | Assert.AreEqual(StatusCodes.Status410Gone, httpsContext.Response.StatusCode);
571 | }
572 |
573 | #endregion
574 |
575 | #region Tests des cas limites (Edge Cases)
576 |
577 | [TestMethod]
578 | public async Task InvokeAsync_BlockedTLD_WithPort_ShouldReturn410()
579 | {
580 | // Arrange
581 | _httpContext.Request.Headers.Referer = "https://spam.icu:8080/path";
582 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
583 |
584 | // Act
585 | await middleware.InvokeAsync(_httpContext);
586 |
587 | // Assert
588 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
589 | _nextMock.Verify(next => next(_httpContext), Times.Never);
590 | }
591 |
592 | [TestMethod]
593 | public async Task InvokeAsync_BlockedTLD_WithQueryString_ShouldReturn410()
594 | {
595 | // Arrange
596 | _httpContext.Request.Headers.Referer = "https://spam.icu?param=value&other=test";
597 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
598 |
599 | // Act
600 | await middleware.InvokeAsync(_httpContext);
601 |
602 | // Assert
603 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
604 | }
605 |
606 | [TestMethod]
607 | public async Task InvokeAsync_BlockedTLD_WithFragment_ShouldReturn410()
608 | {
609 | // Arrange
610 | _httpContext.Request.Headers.Referer = "https://spam.icu#section";
611 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
612 |
613 | // Act
614 | await middleware.InvokeAsync(_httpContext);
615 |
616 | // Assert
617 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
618 | }
619 |
620 | [TestMethod]
621 | public async Task InvokeAsync_BlockedTLD_WithPath_ShouldReturn410()
622 | {
623 | // Arrange
624 | _httpContext.Request.Headers.Referer = "https://spam.icu/deep/path/to/page.html";
625 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
626 |
627 | // Act
628 | await middleware.InvokeAsync(_httpContext);
629 |
630 | // Assert
631 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
632 | }
633 |
634 | [TestMethod]
635 | public async Task InvokeAsync_MultipleSubdomains_BlockedTLD_ShouldReturn410()
636 | {
637 | // Arrange
638 | _httpContext.Request.Headers.Referer = "https://deep.sub.domain.spam.icu";
639 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
640 |
641 | // Act
642 | await middleware.InvokeAsync(_httpContext);
643 |
644 | // Assert
645 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
646 | }
647 |
648 | [TestMethod]
649 | public async Task InvokeAsync_IPv4Address_ShouldCallNext()
650 | {
651 | // Arrange
652 | _httpContext.Request.Headers.Referer = "http://192.168.1.1";
653 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
654 |
655 | // Act
656 | await middleware.InvokeAsync(_httpContext);
657 |
658 | // Assert
659 | _nextMock.Verify(next => next(_httpContext), Times.Once);
660 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
661 | }
662 |
663 | [TestMethod]
664 | public async Task InvokeAsync_ComplexURL_AllComponents_BlockedTLD_ShouldReturn410()
665 | {
666 | // Arrange
667 | _httpContext.Request.Headers.Referer = "https://sub.spam.icu:8080/path/to/page?param1=value1¶m2=value2#section";
668 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
669 |
670 | // Act
671 | await middleware.InvokeAsync(_httpContext);
672 |
673 | // Assert
674 | Assert.AreEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
675 | }
676 |
677 | [TestMethod]
678 | public async Task InvokeAsync_LegitimateSubdomain_SimilarToBlocked_ShouldNotBlock()
679 | {
680 | // Arrange - Verify that "notgazdp.com" is NOT blocked when "gazdp.com" is blocked
681 | _httpContext.Request.Headers.Referer = "https://notgazdp.com";
682 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, _options);
683 |
684 | // Act
685 | await middleware.InvokeAsync(_httpContext);
686 |
687 | // Assert
688 | _nextMock.Verify(next => next(_httpContext), Times.Once);
689 | Assert.AreNotEqual(StatusCodes.Status410Gone, _httpContext.Response.StatusCode);
690 | }
691 |
692 | [TestMethod]
693 | public async Task InvokeAsync_EmptyStringInOptions_ShouldNotCrash()
694 | {
695 | // Arrange
696 | var customOptions = new ReferrerBlockOptions();
697 | customOptions.BlockedDomains.Add(string.Empty);
698 | customOptions.BlockedPatterns.Add(string.Empty);
699 | _httpContext.Request.Headers.Referer = "https://example.com";
700 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, customOptions);
701 |
702 | // Act
703 | await middleware.InvokeAsync(_httpContext);
704 |
705 | // Assert - Should not crash, should process normally
706 | _nextMock.Verify(next => next(_httpContext), Times.Once);
707 | }
708 |
709 | [TestMethod]
710 | public async Task InvokeAsync_NullCollectionsInOptions_ShouldNotCrash()
711 | {
712 | // Arrange
713 | var customOptions = new ReferrerBlockOptions
714 | {
715 | BlockedDomains = null!,
716 | BlockedTLDs = null!,
717 | BlockedPatterns = null!,
718 | BlockedSubdomainPrefixes = null!
719 | };
720 | _httpContext.Request.Headers.Referer = "https://spam.icu";
721 | var middleware = new ReferrerBlockMiddleware(_nextMock.Object, _loggerMock.Object, customOptions);
722 |
723 | // Act
724 | await middleware.InvokeAsync(_httpContext);
725 |
726 | // Assert - Should not crash, should allow through (no blocking rules)
727 | _nextMock.Verify(next => next(_httpContext), Times.Once);
728 | }
729 |
730 | #endregion
731 | }
--------------------------------------------------------------------------------