Improved enrichment validation, logging, and test coverage.
Strengthened URL Validation. Improved HTTP Client Configuration. Logged total rejected tracker count at the information level. Logged individual rejected tracker details at the debug level. Debug logs for bytes and enriched bytes are now conditional. Converted some tests from Fact to Theory. Used Assert.Collection in EnricherTest.cs. Standardized URL Encoding in Enricher.cs.
This commit is contained in:
parent
4be3280c3a
commit
98042f8040
4 changed files with 114 additions and 46 deletions
|
|
@ -41,19 +41,30 @@ public class EnricherTest : IDisposable
|
|||
Assert.Equal(TestMagnetLink, enriched);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnrichMagnetLink_AddsTrackers_WhenTrackersFromTrackerGrabber()
|
||||
[Theory]
|
||||
[InlineData("magnet:?xt=urn:btih:HASH",
|
||||
new[]
|
||||
{
|
||||
"http://new-tracker.com/announce"
|
||||
},
|
||||
"magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Fnew-tracker.com%2Fannounce")]
|
||||
[InlineData("magnet:?xt=urn:btih:HASH",
|
||||
new[]
|
||||
{
|
||||
"http://tracker1.com/announce", "http://tracker2.com/announce"
|
||||
},
|
||||
"magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce")]
|
||||
public async Task EnrichMagnetLink_AddsTrackers_WhenTrackersFromTrackerGrabber_Theory(String magnetLink, String[] newTrackers, String expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
SetupTrackerListGrabber(["http://new-tracker.com/announce"]);
|
||||
|
||||
SetupTrackerListGrabber(newTrackers);
|
||||
var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object);
|
||||
|
||||
// Act
|
||||
var enriched = await enricher.EnrichMagnetLink(TestMagnetLink);
|
||||
var enriched = await enricher.EnrichMagnetLink(magnetLink);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TestMagnetLink + $"&tr={Uri.EscapeDataString("http://new-tracker.com/announce")}", enriched);
|
||||
Assert.Equal(expectedResult, enriched);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -281,17 +292,27 @@ public class EnricherTest : IDisposable
|
|||
Assert.Equal(2, trValues.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnrichMagnetLink_BasicMagnetWithHashOnly()
|
||||
[Theory]
|
||||
[InlineData("magnet:?xt=urn:btih:HASH",
|
||||
new[]
|
||||
{
|
||||
"udp://tracker1"
|
||||
},
|
||||
"magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1")]
|
||||
[InlineData("magnet:?xt=urn:btih:HASH",
|
||||
new[]
|
||||
{
|
||||
"udp://tracker1", "udp://tracker2"
|
||||
},
|
||||
"magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2")]
|
||||
[InlineData("magnet:?xt=urn:btih:HASH",
|
||||
new String[]
|
||||
{
|
||||
},
|
||||
"magnet:?xt=urn:btih:HASH")]
|
||||
public async Task EnrichMagnetLink_BasicMagnetWithHashOnly_Theory(String magnetLink, String[] newTrackers, String expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var magnetLink = "magnet:?xt=urn:btih:HASH";
|
||||
|
||||
var newTrackers = new[]
|
||||
{
|
||||
"udp://tracker1"
|
||||
};
|
||||
|
||||
SetupTrackerListGrabber(newTrackers);
|
||||
var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object);
|
||||
|
||||
|
|
@ -299,20 +320,30 @@ public class EnricherTest : IDisposable
|
|||
var result = await enricher.EnrichMagnetLink(magnetLink);
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"{magnetLink}&tr={Uri.EscapeDataString("udp://tracker1")}", result);
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnrichMagnetLink_MagnetEndsWithQuestionMarkOnly()
|
||||
[Theory]
|
||||
[InlineData("magnet:?",
|
||||
new[]
|
||||
{
|
||||
"udp://tracker1"
|
||||
},
|
||||
"magnet:?tr=udp%3A%2F%2Ftracker1")]
|
||||
[InlineData("magnet:?",
|
||||
new[]
|
||||
{
|
||||
"udp://tracker1", "udp://tracker2"
|
||||
},
|
||||
"magnet:?tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2")]
|
||||
[InlineData("magnet:?",
|
||||
new String[]
|
||||
{
|
||||
},
|
||||
"magnet:?")]
|
||||
public async Task EnrichMagnetLink_MagnetEndsWithQuestionMarkOnly_Theory(String magnetLink, String[] newTrackers, String expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var magnetLink = "magnet:?";
|
||||
|
||||
var newTrackers = new[]
|
||||
{
|
||||
"udp://tracker1"
|
||||
};
|
||||
|
||||
SetupTrackerListGrabber(newTrackers);
|
||||
var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object);
|
||||
|
||||
|
|
@ -320,7 +351,7 @@ public class EnricherTest : IDisposable
|
|||
var result = await enricher.EnrichMagnetLink(magnetLink);
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"{magnetLink}tr={Uri.EscapeDataString("udp://tracker1")}", result);
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -365,8 +396,10 @@ public class EnricherTest : IDisposable
|
|||
Assert.Equal(newTrackers[0], ((BEncodedString)enrichedDict["announce"]).Text);
|
||||
var announceList = (BEncodedList)enrichedDict["announce-list"];
|
||||
Assert.Equal(2, announceList.Count);
|
||||
Assert.Equal(newTrackers[0], ((BEncodedString)((BEncodedList)announceList[0])[0]).Text);
|
||||
Assert.Equal(newTrackers[1], ((BEncodedString)((BEncodedList)announceList[1])[0]).Text);
|
||||
|
||||
Assert.Collection(announceList,
|
||||
tier => Assert.Equal(newTrackers[0], ((BEncodedString)((BEncodedList)tier)[0]).Text),
|
||||
tier => Assert.Equal(newTrackers[1], ((BEncodedString)((BEncodedList)tier)[0]).Text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -108,7 +107,7 @@ public sealed class Enricher(ILogger<Enricher> logger, ITrackerListGrabber track
|
|||
}
|
||||
else
|
||||
{
|
||||
outParams.Add($"{kv.Key}={HttpUtility.UrlEncode(v)}");
|
||||
outParams.Add($"{kv.Key}={Uri.EscapeDataString(v)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ public class Torrents(
|
|||
MagnetLink magnet;
|
||||
try
|
||||
{
|
||||
magnet = MagnetLink.Parse(enriched);
|
||||
magnet = MagnetLink.Parse(magnetLink);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -139,15 +139,24 @@ public class Torrents(
|
|||
{
|
||||
MonoTorrent.Torrent monoTorrent;
|
||||
|
||||
var fileAsBase64 = Convert.ToBase64String(bytes);
|
||||
logger.LogDebug($"bytes {bytes}");
|
||||
|
||||
var enriched = await enricher.EnrichTorrentBytes(bytes);
|
||||
logger.LogDebug($"enriched bytes {enriched}");
|
||||
|
||||
String fileAsBase64;
|
||||
|
||||
if (enriched.SequenceEqual(bytes))
|
||||
{
|
||||
fileAsBase64 = Convert.ToBase64String(bytes);
|
||||
logger.LogDebug($"bytes {bytes}");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileAsBase64 = Convert.ToBase64String(enriched);
|
||||
logger.LogDebug($"enriched bytes {enriched}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
monoTorrent = await MonoTorrent.Torrent.LoadAsync(enriched);
|
||||
monoTorrent = await MonoTorrent.Torrent.LoadAsync(bytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Net.Http.Headers;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
|
|
@ -107,9 +109,17 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac
|
|||
logger.LogDebug("Fetching tracker list from URL: {TrackerUrl}", trackerUri);
|
||||
|
||||
var httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString();
|
||||
|
||||
var currentVersion = version != null && version.LastIndexOf('.') > 0
|
||||
? $"v{version[..version.LastIndexOf('.')]}"
|
||||
: "";
|
||||
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("RdtClient", currentVersion));
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||
var token = cts.Token;
|
||||
var response = await httpClient.GetAsync(trackerUri, token).ConfigureAwait(false);
|
||||
var response = await httpClient.GetAsync(trackerUri, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
|
||||
|
||||
using (response)
|
||||
{
|
||||
|
|
@ -123,6 +133,8 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac
|
|||
|
||||
try
|
||||
{
|
||||
var trackerRejectionCount = 0;
|
||||
|
||||
trackers = result
|
||||
.Split([
|
||||
"\r\n", "\n"
|
||||
|
|
@ -135,21 +147,36 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac
|
|||
{
|
||||
if (!Uri.TryCreate(t, UriKind.Absolute, out var uri))
|
||||
{
|
||||
logger.LogDebug("Rejected tracker: {TrackerUrl} - Reason: Invalid format or unsupported scheme.", t);
|
||||
trackerRejectionCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
return (uri.Scheme == Uri.UriSchemeHttp ||
|
||||
uri.Scheme == Uri.UriSchemeHttps ||
|
||||
uri.Scheme.Equals("udp", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase)) &&
|
||||
!t.Contains("..") &&
|
||||
!t.Contains("\\") &&
|
||||
!t.Any(Char.IsControl) &&
|
||||
uri.Host.All(c => Char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '_' || c == ':') &&
|
||||
uri.Host.Length > 0;
|
||||
var isIpv6 = uri.Host.StartsWith('[') && uri.Host.Contains(']');
|
||||
|
||||
var valid = ((isIpv6 && uri.Host.All(c => Char.IsLetterOrDigit(c) || c == '.' || c == ':' || c == '[' || c == ']')) ||
|
||||
(!isIpv6 && uri.Host.All(c => Char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '_'))) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp ||
|
||||
uri.Scheme == Uri.UriSchemeHttps ||
|
||||
uri.Scheme.Equals("udp", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase)) &&
|
||||
!t.Contains("..") &&
|
||||
!t.Contains("\\") &&
|
||||
!t.Any(Char.IsControl) &&
|
||||
uri.Host.Length > 0;
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
logger.LogDebug("Enrichment tracker rejected: {TrackerUrl} - Reason: Invalid format or unsupported scheme.", t);
|
||||
trackerRejectionCount++;
|
||||
}
|
||||
|
||||
return valid;
|
||||
})
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
logger.LogInformation("{TrackerRejectionCount} trackers were rejected during enrichment.", trackerRejectionCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue