96 lines
3.4 KiB
C#
96 lines
3.4 KiB
C#
using System;
|
|
using System.IO;
|
|
using Paca.Browser.Content;
|
|
using Paca.Browser.Extensions;
|
|
using Paca.Core.Net;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
public class SecurityAndRateLimitingExtremeRedTests
|
|
{
|
|
// ==========================================
|
|
// 1. ExtensionCspValidator
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void ExtensionCspValidator_Validate_ManifestV3_UnsafeInlineInScriptSrc_ReturnsInvalid()
|
|
{
|
|
var result = ExtensionCspValidator.Validate("script-src 'self' 'unsafe-inline'; object-src 'self';", manifestVersion: 3);
|
|
Assert.False(result.IsValid);
|
|
Assert.NotEmpty(result.Violations);
|
|
Assert.Contains(result.Violations, v => v.Contains("unsafe-inline", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("script-src 'self' data:;")]
|
|
[InlineData("script-src 'self' blob:;")]
|
|
public void ExtensionCspValidator_Validate_ManifestV3_DataOrBlobInScriptSrc_ReturnsInvalid(string csp)
|
|
{
|
|
var result = ExtensionCspValidator.Validate(csp, manifestVersion: 3);
|
|
Assert.False(result.IsValid);
|
|
Assert.NotEmpty(result.Violations);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(";; ; ;")]
|
|
[InlineData(" ; ; ")]
|
|
public void ExtensionCspValidator_Validate_EmptySemicolons_FallsBackToDefaultCsp(string csp)
|
|
{
|
|
var result = ExtensionCspValidator.Validate(csp, manifestVersion: 3);
|
|
Assert.True(result.IsValid);
|
|
Assert.Equal(ExtensionCspValidator.DefaultManifestV3Csp, result.NormalizedCsp);
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. SiteAutoMutePolicy
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void SiteAutoMutePolicy_AddAutoMutePattern_WildcardAsterisk_MutesAllSites()
|
|
{
|
|
var policy = new SiteAutoMutePolicy();
|
|
policy.AddAutoMutePattern("*");
|
|
|
|
Assert.True(policy.IsAutoMute("https://youtube.com/watch?v=123"));
|
|
Assert.True(policy.IsAutoMute("https://news.naver.com"));
|
|
Assert.True(policy.IsAutoMute("https://example.org"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SiteAutoMutePolicy_Save_InvalidOrReadOnlyPath_DoesNotThrowUnhandledException()
|
|
{
|
|
// Using an invalid directory path that cannot be created on Windows
|
|
var policy = new SiteAutoMutePolicy("Z:\\nonexistent_drive_9999\\invalid\\mute.json");
|
|
var ex = Record.Exception(() => policy.AddAutoMutePattern("example.com"));
|
|
Assert.Null(ex);
|
|
}
|
|
|
|
// ==========================================
|
|
// 3. HostRateLimiter
|
|
// ==========================================
|
|
|
|
[Theory]
|
|
[InlineData("example.com:8080/api/v1", "example.com")]
|
|
[InlineData("https://sub.4cdn.org/image.png", "sub.4cdn.org")]
|
|
[InlineData("user:pass@reddit.com:443/r/funny", "reddit.com")]
|
|
[InlineData(null, "")]
|
|
[InlineData(" ", "")]
|
|
public void HostRateLimiter_NormalizeHost_VariousInputs_NormalizesCleanly(string? input, string expected)
|
|
{
|
|
var host = HostRateLimiter.NormalizeHost(input);
|
|
Assert.Equal(expected, host);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("i.4cdn.org", 1200)]
|
|
[InlineData("boards.4channel.org", 1200)]
|
|
[InlineData("v.redd.it", 800)]
|
|
[InlineData("www.reddit.com", 800)]
|
|
[InlineData("generic-site.com", 200)]
|
|
public void HostRateLimiter_GetIntervalMs_RecognizesDomainPresets(string host, int expectedMs)
|
|
{
|
|
var interval = HostRateLimiter.GetIntervalMs(host);
|
|
Assert.Equal(expectedMs, interval);
|
|
}
|
|
}
|