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
2 changes: 2 additions & 0 deletions Robust.Client/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public sealed class BaseClient : IBaseClient, IPostInjectInit
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IClientGameStateManager _gameStates = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IHttpManagerInternal _http = default!;

/// <inheritdoc />
public ushort DefaultPort { get; } = 1212;
Expand Down Expand Up @@ -255,6 +256,7 @@ private void GameStartedSetup()

private void GameStoppedReset()
{
_http.Shutdown();
_configManager.FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
Expand Down
4 changes: 3 additions & 1 deletion Robust.Server/BaseServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;
using Robust.Shared.Toolshed;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Toolshed;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
using Serilog.Debugging;
Expand Down Expand Up @@ -111,6 +111,7 @@ internal sealed class BaseServer : IBaseServerInternal, IPostInjectInit
[Dependency] private readonly IReflectionManager _refMan = default!;
[Dependency] private readonly ITransferManager _transfer = default!;
[Dependency] private readonly ServerTransferTestManager _transferTest = default!;
[Dependency] private readonly IHttpManagerInternal _http = default!;

private readonly Stopwatch _uptimeStopwatch = new();

Expand Down Expand Up @@ -675,6 +676,7 @@ private bool CheckIfShouldAutoPause()
// called right before main loop returns, do all saving/cleanup in here
public void Cleanup()
{
_http.Shutdown();
_replay.Shutdown();

_modLoader.Shutdown();
Expand Down
39 changes: 39 additions & 0 deletions Robust.Shared.Tests/Networking/HttpManagerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using NUnit.Framework;
using Robust.Shared.Network;

namespace Robust.Shared.Tests.Networking;

[TestFixture]
[TestOf(typeof(HttpManager))]
public sealed class HttpManagerTest
{
[Test]
[TestCase("http://www.google.com")]
[TestCase("https://www.google.com")]
[TestCase("http://google.com")]
[TestCase("https://google.com")]
public void TestNoError(string url)
{
var manager = new HttpManager();
var uri = new Uri(url);
Assert.DoesNotThrowAsync(() => manager.GetStreamAsync(uri));
Assert.DoesNotThrowAsync(() => manager.GetStringAsync(uri));
}

[Test]
[TestCase("http://192.168.0.0")]
[TestCase("http://192.168.0.1")]
[TestCase("http://192.168.255.255")]
[TestCase("http://10.0.0.0")]
[TestCase("http://10.255.255.255")]
[TestCase("http://172.16.0.0")]
[TestCase("http://172.31.255.255")]
public void TestLocalIPError(string url)
{
var manager = new HttpManager();
var uri = new Uri(url);
Assert.ThrowsAsync<InvalidAddressException>(() => manager.GetStreamAsync(uri));
Assert.ThrowsAsync<InvalidAddressException>(() => manager.GetStringAsync(uri));
Assert.ThrowsAsync<InvalidAddressException>(() => manager.GetFromJsonAsync<string>(uri));
}
}
114 changes: 114 additions & 0 deletions Robust.Shared/Network/HttpManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Utility;

namespace Robust.Shared.Network;

public sealed class HttpManager : IHttpManagerInternal
{
private readonly HttpClient _client = new();

void IHttpManagerInternal.Shutdown()
{
_client.CancelPendingRequests();
}

public async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancel = default)
{
// Uri can be inherited which means it could be inherited by the user
// Am I going to check if that means they could modify it after the
// local address check?
// No, so we copy the original string instead just in case
// !!FUN!!
uri = new Uri(uri.OriginalString);
await ThrowIfLocalUri(uri);
return await _client.GetStreamAsync(uri, cancel);
}

public async Task<string> GetStringAsync(Uri uri, CancellationToken cancel = default)
{
uri = new Uri(uri.OriginalString);
await ThrowIfLocalUri(uri);
return await _client.GetStringAsync(uri, cancel);
}

public async Task<T?> GetFromJsonAsync<T>(Uri uri, CancellationToken cancel = default)
{
uri = new Uri(uri.OriginalString);
await ThrowIfLocalUri(uri);
return await _client.GetFromJsonAsync<T>(uri, cancel);
}

private async Task ThrowIfLocalUri(Uri uri)
{
if (IPAddress.TryParse(uri.Host, out var ip))
ThrowIfLocalIP(ip);

var addresses = await Dns.GetHostAddressesAsync(uri.Host);
foreach (var dnsIP in addresses)
{
ThrowIfLocalIP(dnsIP);
}
}

private void ThrowIfLocalIP(IPAddress ip)
{
// IPv4
var ipv4 = ip.ToString()
.Split(".")
.Select(s => (int?) (int.TryParse(s, out var i) ? i : null))
.Where(i => i != null)
.ToArray();
ipv4.TryGetValue(0, out var first);
ipv4.TryGetValue(1, out var second);
switch (first)
{
case 10:
case 192 when second == 168:
case 172 when second is >= 16 and <= 31:
ThrowLocalAddressException(ip);
break;
}

// IPv6
if (IPAddress.IsLoopback(ip) ||
ip.IsIPv6LinkLocal ||
ip.IsIPv6SiteLocal ||
ip.IsIPv6UniqueLocal)
{
ThrowLocalAddressException(ip);
}
}

private void ThrowLocalAddressException(IPAddress ip)
{
throw new InvalidAddressException($"{ip.ToString()} is a local address");
}
}

public sealed class InvalidAddressException : ArgumentException
{
internal InvalidAddressException()
{
}

[Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")]
internal InvalidAddressException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}

internal InvalidAddressException(string? message) : base(message)
{
}

internal InvalidAddressException(string? message, Exception? innerException) : base(message, innerException)
{
}
}
15 changes: 15 additions & 0 deletions Robust.Shared/Network/IHttpManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Robust.Shared.Network;

public interface IHttpManager
{
Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancel);

Task<string> GetStringAsync(Uri uri, CancellationToken cancel);

Task<T?> GetFromJsonAsync<T>(Uri uri, CancellationToken cancel = default);
}
6 changes: 6 additions & 0 deletions Robust.Shared/Network/IHttpManagerInternal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Robust.Shared.Network;

public interface IHttpManagerInternal : IHttpManager
{
void Shutdown();
}
5 changes: 2 additions & 3 deletions Robust.Shared/SharedIoC.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
Expand Down Expand Up @@ -50,6 +47,8 @@ public static void RegisterIoC(IDependencyCollection deps)
deps.Register<HttpClientHolder>();
deps.Register<RobustMemoryManager>();
deps.Register<EntityConsoleHost>();
deps.Register<IHttpManager, HttpManager>();
deps.Register<IHttpManagerInternal, HttpManager>();
}
}
}
Loading