mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 17:47:24 +02:00
125 lines
3.7 KiB
C#
125 lines
3.7 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Security.Cryptography;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Robust.Server.ServerStatus;
|
|
using Robust.Shared;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Network.Messages.Transfer;
|
|
using Robust.Shared.Network.Transfer;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Server.Network.Transfer;
|
|
|
|
internal sealed class ServerTransferImplWebSocket : TransferImplWebSocket
|
|
{
|
|
private readonly IConfigurationManager _cfg;
|
|
private readonly INetManager _netManager;
|
|
private readonly SemaphoreSlim _apiLock = new(1, 1);
|
|
private readonly TaskCompletionSource _connectTcs = new();
|
|
|
|
// To authenticate the client doing the HTTP request,
|
|
// we ask that they provide a key we gave them via Lidgren traffic.
|
|
public byte[]? Key;
|
|
|
|
public ServerTransferImplWebSocket(
|
|
ISawmill sawmill,
|
|
BaseTransferManager parent,
|
|
IConfigurationManager cfg,
|
|
INetManager netManager,
|
|
INetChannel channel)
|
|
: base(sawmill, parent, channel)
|
|
{
|
|
_cfg = cfg;
|
|
_netManager = netManager;
|
|
}
|
|
|
|
public override Task ServerInit()
|
|
{
|
|
Key = RandomNumberGenerator.GetBytes(RandomKeyBytes);
|
|
|
|
var uriBuilder = new UriBuilder(string.Concat(
|
|
_cfg.GetCVar(CVars.TransferHttpEndpoint).TrimEnd("/"),
|
|
ServerTransferManager.TransferApiUrl));
|
|
|
|
uriBuilder.Scheme = uriBuilder.Scheme switch
|
|
{
|
|
"http" => "ws",
|
|
"https" => "wss",
|
|
_ => throw new InvalidOperationException($"Invalid API endpoint scheme: {uriBuilder.Scheme}")
|
|
};
|
|
|
|
var url = uriBuilder.ToString();
|
|
|
|
Sawmill.Verbose($"Transfer API URL is '{url}'");
|
|
|
|
var initMsg = new MsgTransferInit();
|
|
initMsg.HttpInfo = (url, Key);
|
|
|
|
_netManager.ServerSendMessage(initMsg, Channel);
|
|
|
|
return _connectTcs.Task;
|
|
}
|
|
|
|
public override Task ClientInit(CancellationToken cancel)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public async Task HandleApiRequest(NetUserId userId, IStatusHandlerContext context)
|
|
{
|
|
using var _ = await _apiLock.WaitGuardAsync();
|
|
|
|
if (Key == null)
|
|
{
|
|
Sawmill.Warning($"HTTP request failed: UserID '{userId}' tried to connect twice");
|
|
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
|
|
return;
|
|
}
|
|
|
|
if (!context.RequestHeaders.TryGetValue(KeyHeaderName, out var keyValue) || keyValue is not [{ } keyValueStr])
|
|
{
|
|
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
|
|
return;
|
|
}
|
|
|
|
var buf = new byte[RandomKeyBytes];
|
|
|
|
if (!Convert.TryFromBase64String(keyValueStr, buf, out var written) || written != RandomKeyBytes)
|
|
{
|
|
Sawmill.Verbose("HTTP request failed: key is not valid base64 or wrong length");
|
|
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
|
|
return;
|
|
}
|
|
|
|
if (!CryptographicOperations.FixedTimeEquals(buf, Key))
|
|
{
|
|
Sawmill.Warning("HTTP request failed: key is wrong");
|
|
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
|
|
return;
|
|
}
|
|
|
|
Sawmill.Debug("Client connect to transfer WS channel: {UserId}", userId);
|
|
|
|
WebSocket = await context.AcceptWebSocketAsync();
|
|
|
|
// We've connected.
|
|
// Clear key so this can't be reconnected to.
|
|
Key = null;
|
|
|
|
_connectTcs.TrySetResult();
|
|
|
|
ReadThread();
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
_connectTcs.TrySetCanceled();
|
|
|
|
base.Dispose();
|
|
}
|
|
}
|