Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
<local:BoolSettingViewer x:Name="readResponseContentSetting" />
<local:BoolSettingViewer x:Name="decodeHtmlSetting" />
<local:StringSettingViewer x:Name="codePagesEncodingSetting" />
<local:BoolSettingViewer x:Name="useProxySetting" />

<Label
Content="Advanced settings:"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ private void BindSettings()
maxNumberOfRedirectsSetting.Setting = vm.HttpRequestBlock.Settings["maxNumberOfRedirects"];
absoluteUriInFirstLineSetting.Setting = vm.HttpRequestBlock.Settings["absoluteUriInFirstLine"];
readResponseContentSetting.Setting = vm.HttpRequestBlock.Settings["readResponseContent"];
decodeHtmlSetting.Setting = vm.HttpRequestBlock.Settings["decodeHtml"];
codePagesEncodingSetting.Setting = vm.HttpRequestBlock.Settings["codePagesEncoding"];
useProxySetting.Setting = vm.HttpRequestBlock.Settings["useProxy"];
httpLibrarySetting.Setting = vm.HttpRequestBlock.Settings["httpLibrary"];
curlImpersonateBrowserProfileSetting.Setting = vm.HttpRequestBlock.Settings["curlImpersonateBrowserProfile"];
curlUseBrowserHeadersSetting.Setting = vm.HttpRequestBlock.Settings["curlUseBrowserHeaders"];
Expand Down
3 changes: 3 additions & 0 deletions OpenBullet2.Native/Utils/Suggestions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public static IEnumerable<string> GetInputVariableSuggestions(BlockSetting setti
}

var suggestions = new List<string> {
"input.proxy.ip", "input.proxy.host", "input.proxy.port",
"input.proxy.username", "input.proxy.password", "input.proxy.type",
"input.proxy.country", "input.proxy.ping", "input.proxy.inUse",
"data.SOURCE", "data.ERROR", "data.ADDRESS",
"data.HEADERS[\"name\"]", "data.COOKIES[\"name\"]",
"data.STATUS", "data.RESPONSECODE", "data.RAWSOURCE", "data.Line.Data" };
Expand Down
36 changes: 34 additions & 2 deletions RuriLib.Tests/Functions/Http/HttpClientRequestHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
using RuriLib.Models.Configs;
using RuriLib.Models.Data;
using RuriLib.Models.Environment;
using RuriLib.Models.Proxies;
using RuriLib.Tests.Utils.Mockup;
using System;
using System.Linq;
using System.Net.Http;
using Xunit;

Expand Down Expand Up @@ -57,13 +59,43 @@ public void TryParseCookie_AttributesWithoutCookiePair_ReturnsFalse()
Assert.Null(cookieValue);
}

private static BotData NewBotData() => new(
[Theory]
// No proxy assigned at all: the block setting must be irrelevant, never an error
[InlineData(false, true, false)]
[InlineData(false, false, false)]
// Proxy assigned and the job uses it: only useProxy=false bypasses
[InlineData(true, true, true)]
[InlineData(true, false, false)]
public void ResolveProxy_BypassesOnlyWhenRequested(
bool botUsesProxy, bool blockUseProxy, bool expectProxyUsed)
{
var proxy = new Proxy("1.2.3.4", 8080);
var data = NewBotData(proxy, botUsesProxy);

var resolved = HttpRequestHandler.ResolveProxy(data, new StandardHttpRequestOptions
{
UseProxy = blockUseProxy
});

Assert.Equal(expectProxyUsed, resolved is not null);
Assert.Equal(expectProxyUsed ? proxy : null, resolved);

// The notice fires only when a proxy was actually in use and the block opted out.
// A job that simply has proxies disabled is not "bypassing" anything, so it stays quiet.
var expectNotice = botUsesProxy && !blockUseProxy;
Assert.Equal(expectNotice ? 1 : 0,
data.Logger.Entries.Count(e => e.Message.Contains("[NO PROXY]", StringComparison.Ordinal)));
}

private static BotData NewBotData(Proxy? proxy = null, bool useProxy = false) => new(
new(null!)
{
ProxySettings = new MockedProxySettingsProvider(),
Security = new MockedSecurityProvider()
},
new ConfigSettings(),
new BotLogger(),
new DataLine("", new WordlistType()));
new DataLine("", new WordlistType()),
proxy,
useProxy);
}
33 changes: 33 additions & 0 deletions RuriLib.Tests/Functions/Http/HttpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ public class HttpTests
null,
false);

[Theory]
[InlineData(HttpLibrary.RuriLibHttp, true)]
[InlineData(HttpLibrary.RuriLibHttp, false)]
[InlineData(HttpLibrary.SystemNet, true)]
[InlineData(HttpLibrary.SystemNet, false)]
[InlineData(HttpLibrary.CurlImpersonate, true)]
[InlineData(HttpLibrary.CurlImpersonate, false)]
public async Task HttpRequestStandard_Get_WithoutAnyProxy_Succeeds(HttpLibrary library, bool blockUseProxy)
{
var httpBin = await TestHttpBin.BuildUrl("anything");

// A config that does not use proxies at all: no proxy assigned, bot-level UseProxy off
var data = NewBotData();

var options = new StandardHttpRequestOptions
{
Url = httpBin,
Method = HttpMethod.GET,
HttpLibrary = library,
UseProxy = blockUseProxy
};

await Methods.HttpRequestStandard(data, options);

var response = DeserializeHttpBinResponse(data.SOURCE);
Assert.Equal("GET", response.Method);
Assert.Equal(200, (int)data.RESPONSECODE);

// With no proxy assigned there is nothing to bypass, so the notice must stay silent
Assert.DoesNotContain(data.Logger.Entries,
entry => entry.Message.Contains("[NO PROXY]", StringComparison.Ordinal));
}

[Theory]
[InlineData(HttpLibrary.RuriLibHttp)]
[InlineData(HttpLibrary.SystemNet)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,43 @@ public void MultipartSettingsGroups_DefaultContentTypes_ArePreserved()
Assert.IsType<ByteArraySetting>(rawContent.Data.FixedSetting);
}

[Fact]
public void ToSyntax_UseProxy_IsEmittedAndDefaultsToTrue()
{
var block = CreateStandardRequestBlock();

Assert.True(Assert.IsType<BoolSetting>(block.Settings["useProxy"].FixedSetting).Value);
Assert.Contains("UseProxy = true", RenderSyntax(block));

(block.Settings["useProxy"].FixedSetting as BoolSetting)!.Value = false;

Assert.Contains("UseProxy = false", RenderSyntax(block));
}

[Fact]
public void ToLC_UseProxy_IsOmittedWhenDefaultAndWrittenWhenChanged()
{
var block = BlockFactory.GetBlock<HttpRequestBlockInstance>("HttpRequest");

Assert.DoesNotContain("useProxy =", block.ToLC());

(block.Settings["useProxy"].FixedSetting as BoolSetting)!.Value = false;

Assert.Contains($" useProxy = False{_nl}", block.ToLC());
}

[Fact]
public void FromLC_UseProxy_RoundTrips()
{
var block = BlockFactory.GetBlock<HttpRequestBlockInstance>("HttpRequest");
var script = $" useProxy = false{_nl}";
var lineNumber = 0;

block.FromLC(ref script, ref lineNumber);

Assert.False(Assert.IsType<BoolSetting>(block.Settings["useProxy"].FixedSetting).Value);
}

private static HttpRequestBlockInstance CreateStandardRequestBlock(bool safe = false)
{
var block = CreateBaseBlock(safe);
Expand Down
98 changes: 96 additions & 2 deletions RuriLib.Tests/Models/Bots/ScriptGlobalsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using RuriLib.Models.Configs;
using RuriLib.Models.Data;
using RuriLib.Models.Environment;
using RuriLib.Models.Proxies;
using RuriLib.Tests.Utils.Mockup;
using Xunit;

Expand Down Expand Up @@ -57,7 +58,98 @@ public void Constructor_UrlEncodesSlices_WhenConfigured()
Assert.Equal("hello%20world", input["value"]);
}

private static BotData NewBotData(DataLine line)
[Fact]
public void Constructor_PopulatesProxyVariables()
{
dynamic globals = new ExpandoObject();
var proxy = new Proxy("1.2.3.4", 8080, ProxyType.Socks5, "user", "pass")
{
Country = "Italy",
Ping = 120
};
var botData = NewBotData(NewLine(), proxy, useProxy: true);

var proxyFields = GetProxyFields(new ScriptGlobals(botData, globals));

Assert.Equal("1.2.3.4", proxyFields["ip"]);
Assert.Equal("1.2.3.4", proxyFields["host"]);
Assert.Equal(8080, proxyFields["port"]);
Assert.Equal("user", proxyFields["username"]);
Assert.Equal("pass", proxyFields["password"]);
Assert.Equal("socks5", proxyFields["type"]);
Assert.Equal("Italy", proxyFields["country"]);
Assert.Equal(120, proxyFields["ping"]);
Assert.Equal(true, proxyFields["inUse"]);
}

[Fact]
public void Constructor_ProxyVariables_AreEmpty_WhenNoProxyAssigned()
{
dynamic globals = new ExpandoObject();

var proxyFields = GetProxyFields(new ScriptGlobals(NewBotData(NewLine()), globals));

Assert.Equal(string.Empty, proxyFields["ip"]);
Assert.Equal(string.Empty, proxyFields["host"]);
Assert.Equal(0, proxyFields["port"]);
Assert.Equal(string.Empty, proxyFields["username"]);
Assert.Equal(string.Empty, proxyFields["password"]);
Assert.Equal(string.Empty, proxyFields["type"]);
Assert.Equal(string.Empty, proxyFields["country"]);
Assert.Equal(0, proxyFields["ping"]);
Assert.Equal(false, proxyFields["inUse"]);
}

[Fact]
public void Constructor_ProxyVariables_AreExposedButNotInUse_WhenJobHasProxiesDisabled()
{
dynamic globals = new ExpandoObject();
var proxy = new Proxy("1.2.3.4", 8080);
var botData = NewBotData(NewLine(), proxy, useProxy: false);

var proxyFields = GetProxyFields(new ScriptGlobals(botData, globals));

// The fields stay readable so configs can log them, but the request goes out directly
Assert.Equal("1.2.3.4", proxyFields["ip"]);
Assert.Equal(false, proxyFields["inUse"]);
}

[Fact]
public void Constructor_SliceNamedProxy_TakesPrecedence()
{
dynamic globals = new ExpandoObject();
var botData = NewBotData(
new DataLine("fromWordlist", new WordlistType
{
Separator = string.Empty,
Slices = ["proxy"]
}),
new Proxy("1.2.3.4", 8080),
useProxy: true);

var input = (IDictionary<string, object?>)new ScriptGlobals(botData, globals).input;

// Existing configs slicing into "proxy" must keep working unchanged
Assert.IsType<string>(input["proxy"]);
Assert.Equal("fromWordlist", input["proxy"]);
}

private static DataLine NewLine()
=> new("hello:world", new WordlistType
{
Separator = ":",
Slices = ["left", "right"]
});

private static IDictionary<string, object?> GetProxyFields(ScriptGlobals scriptGlobals)
{
var input = (IDictionary<string, object?>)scriptGlobals.input;
Assert.True(input.ContainsKey("proxy"));

return Assert.IsAssignableFrom<IDictionary<string, object?>>(input["proxy"]);
}

private static BotData NewBotData(DataLine line, Proxy? proxy = null, bool useProxy = false)
=> new(
new global::RuriLib.Models.Bots.Providers(null!)
{
Expand All @@ -66,5 +158,7 @@ private static BotData NewBotData(DataLine line)
},
new ConfigSettings(),
new BotLogger(),
line);
line,
proxy,
useProxy);
}
8 changes: 4 additions & 4 deletions RuriLib/Functions/Http/HttpClientRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public override async Task HttpRequestStandard(BotData data, StandardHttpRequest
data,
options,
capturedRequestHeaders is null ? null : capturedRequestHeaders.Add);
using var client = clientFactory(data.UseProxy ? data.Proxy : null, clientOptions, cookieContainer);
using var client = clientFactory(ResolveProxy(data, options), clientOptions, cookieContainer);

var requestVersion = Version.Parse(options.HttpVersion);
using var request = new HttpRequestMessage
Expand Down Expand Up @@ -125,7 +125,7 @@ public override async Task HttpRequestRaw(BotData data, RawHttpRequestOptions op
data,
options,
capturedRequestHeaders is null ? null : capturedRequestHeaders.Add);
using var client = clientFactory(data.UseProxy ? data.Proxy : null, clientOptions, cookieContainer);
using var client = clientFactory(ResolveProxy(data, options), clientOptions, cookieContainer);

var requestVersion = Version.Parse(options.HttpVersion);
using var request = new HttpRequestMessage
Expand Down Expand Up @@ -183,7 +183,7 @@ public override async Task HttpRequestBasicAuth(BotData data, BasicAuthHttpReque
data,
options,
capturedRequestHeaders is null ? null : capturedRequestHeaders.Add);
using var client = clientFactory(data.UseProxy ? data.Proxy : null, clientOptions, cookieContainer);
using var client = clientFactory(ResolveProxy(data, options), clientOptions, cookieContainer);

var requestVersion = Version.Parse(options.HttpVersion);
using var request = new HttpRequestMessage
Expand Down Expand Up @@ -241,7 +241,7 @@ public override async Task HttpRequestMultipart(BotData data, MultipartHttpReque
data,
options,
capturedRequestHeaders is null ? null : capturedRequestHeaders.Add);
using var client = clientFactory(data.UseProxy ? data.Proxy : null, clientOptions, cookieContainer);
using var client = clientFactory(ResolveProxy(data, options), clientOptions, cookieContainer);

if (string.IsNullOrWhiteSpace(options.Boundary))
{
Expand Down
27 changes: 27 additions & 0 deletions RuriLib/Functions/Http/HttpRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using RuriLib.Functions.Http.Options;
using RuriLib.Models.Blocks.Custom.HttpRequest.Multipart;
using RuriLib.Models.Bots;
using RuriLib.Models.Proxies;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -186,6 +187,32 @@ protected static HttpOptions GetClientOptions(BotData data, Options.HttpRequestO
CurlRequestHeadersCallback = curlRequestHeadersCallback
};

/// <summary>
/// Resolves the proxy to use for a single request.
/// The bot's proxy is only used when the job has proxies enabled
/// (<see cref="BotData.UseProxy"/>) and the block didn't opt out through
/// <see cref="Options.HttpRequestOptions.UseProxy"/>.
/// </summary>
/// <param name="data">The bot data holding the proxy assigned to this bot.</param>
/// <param name="options">The options of the block performing the request.</param>
/// <returns>The proxy to route through, or null to send the request directly.</returns>
internal static Proxy? ResolveProxy(BotData data, Options.HttpRequestOptions options)
{
// The job is only proxied when it both wants to be and has something assigned
if (!data.UseProxy || data.Proxy is null)
{
return null;
}

if (options.UseProxy)
{
return data.Proxy;
}

data.Logger.Log("[NO PROXY] This block is configured to bypass the bot proxy", LogColors.DarkOrange);
return null;
}

protected static void LogNegotiatedHttpVersion(BotData data, Version requestedVersion, Version responseVersion)
{
var requested = FormatHttpVersion(requestedVersion);
Expand Down
6 changes: 6 additions & 0 deletions RuriLib/Functions/Http/Options/HttpRequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ public class HttpRequestOptions
/// headers when <see cref="HttpLibrary"/> is <see cref="HttpLibrary.CurlImpersonate"/>.
/// </summary>
public bool CurlUseBrowserHeaders { get; set; } = true;

/// <summary>
/// Gets or sets whether the proxy assigned to the bot is used for this request.
/// When false, the request is always sent directly, even if the job has proxies enabled.
/// </summary>
public bool UseProxy { get; set; } = true;
}

/// <summary>
Expand Down
Loading
Loading