mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-14 17:21:28 +00:00
Use pipelines to read request bodies instead of stream
This commit is contained in:
parent
30cffef2f2
commit
bff915e304
3 changed files with 70 additions and 25 deletions
|
@ -48,7 +48,7 @@ public class MatchController : ControllerBase
|
||||||
|
|
||||||
if (bodyString.Length == 0 || bodyString[0] != '[') return this.BadRequest();
|
if (bodyString.Length == 0 || bodyString[0] != '[') return this.BadRequest();
|
||||||
|
|
||||||
Logger.Info("Received match data: " + bodyString, LogArea.Match);
|
Logger.Debug("Received match data: " + bodyString, LogArea.Match);
|
||||||
|
|
||||||
IMatchCommand? matchData;
|
IMatchCommand? matchData;
|
||||||
try
|
try
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
@ -12,7 +15,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Extensions;
|
namespace LBPUnion.ProjectLighthouse.Extensions;
|
||||||
|
|
||||||
public static class ControllerExtensions
|
public static partial class ControllerExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
public static GameToken GetToken(this ControllerBase controller)
|
public static GameToken GetToken(this ControllerBase controller)
|
||||||
|
@ -23,25 +26,72 @@ public static class ControllerExtensions
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddStringToBuilder(StringBuilder builder, in ReadOnlySequence<byte> readOnlySequence)
|
||||||
|
{
|
||||||
|
// Separate method because Span/ReadOnlySpan cannot be used in async methods
|
||||||
|
ReadOnlySpan<byte> span = readOnlySequence.IsSingleSegment
|
||||||
|
? readOnlySequence.First.Span
|
||||||
|
: readOnlySequence.ToArray().AsSpan();
|
||||||
|
builder.Append(Encoding.UTF8.GetString(span));
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string> ReadBodyAsync(this ControllerBase controller)
|
public static async Task<string> ReadBodyAsync(this ControllerBase controller)
|
||||||
{
|
{
|
||||||
controller.Request.Body.Position = 0;
|
StringBuilder builder = new();
|
||||||
|
|
||||||
using StreamReader bodyReader = new(controller.Request.Body);
|
while (true)
|
||||||
return await bodyReader.ReadToEndAsync();
|
{
|
||||||
|
ReadResult readResult = await controller.Request.BodyReader.ReadAsync();
|
||||||
|
ReadOnlySequence<byte> buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
SequencePosition? position;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Look for a EOL in the buffer
|
||||||
|
position = buffer.PositionOf((byte)'\n');
|
||||||
|
if (position == null) continue;
|
||||||
|
|
||||||
|
ReadOnlySequence<byte> readOnlySequence = buffer.Slice(0, position.Value);
|
||||||
|
AddStringToBuilder(builder, in readOnlySequence);
|
||||||
|
|
||||||
|
// Skip the line + the \n character (basically position)
|
||||||
|
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
|
||||||
}
|
}
|
||||||
|
while (position != null);
|
||||||
|
|
||||||
|
|
||||||
|
if (readResult.IsCompleted && buffer.Length > 0)
|
||||||
|
{
|
||||||
|
AddStringToBuilder(builder, in buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.Request.BodyReader.AdvanceTo(buffer.Start, buffer.End);
|
||||||
|
|
||||||
|
// At this point, buffer will be updated to point one byte after the last
|
||||||
|
// \n character.
|
||||||
|
if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("&(?!(amp|apos|quot|lt|gt);)")]
|
||||||
|
private static partial Regex CharacterEscapeRegex();
|
||||||
|
|
||||||
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
|
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
|
||||||
{
|
{
|
||||||
controller.Request.Body.Position = 0;
|
controller.Request.Body.Position = 0;
|
||||||
|
|
||||||
using StreamReader bodyReader = new(controller.Request.Body);
|
string bodyString = await controller.ReadBodyAsync();
|
||||||
string bodyString = await bodyReader.ReadToEndAsync();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Prevent unescaped ampersands from causing deserialization to fail
|
// Prevent unescaped ampersands from causing deserialization to fail
|
||||||
bodyString = Regex.Replace(bodyString, "&(?!(amp|apos|quot|lt|gt);)", "&");
|
bodyString = CharacterEscapeRegex().Replace(bodyString, "&");
|
||||||
|
|
||||||
XmlRootAttribute? root = null;
|
XmlRootAttribute? root = null;
|
||||||
if (rootElements.Length > 0)
|
if (rootElements.Length > 0)
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LBPUnion.ProjectLighthouse.Match.MatchCommands;
|
using LBPUnion.ProjectLighthouse.Match.MatchCommands;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Helpers;
|
namespace LBPUnion.ProjectLighthouse.Helpers;
|
||||||
|
|
||||||
public static class MatchHelper
|
public static partial class MatchHelper
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<int, string?> UserLocations = new();
|
public static readonly Dictionary<int, string?> UserLocations = new();
|
||||||
public static readonly Dictionary<int, List<int>?> UserRecentlyDivedIn = new();
|
public static readonly Dictionary<int, List<int>?> UserRecentlyDivedIn = new();
|
||||||
|
@ -38,27 +37,23 @@ public static class MatchHelper
|
||||||
|
|
||||||
public static bool ClearUserRecentDiveIns(int userId) => UserRecentlyDivedIn.Remove(userId);
|
public static bool ClearUserRecentDiveIns(int userId) => UserRecentlyDivedIn.Remove(userId);
|
||||||
|
|
||||||
|
[GeneratedRegex("^\\[([^,]*),\\[(.*)\\]\\]")]
|
||||||
|
private static partial Regex MatchJsonRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"0x[a-fA-F0-9]{7,8}")]
|
||||||
|
private static partial Regex LocationHexRegex();
|
||||||
|
|
||||||
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
|
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
|
||||||
public static IMatchCommand? Deserialize(string data)
|
public static IMatchCommand? Deserialize(string data)
|
||||||
{
|
{
|
||||||
string matchType = "";
|
System.Text.RegularExpressions.Match match = MatchJsonRegex().Match(data);
|
||||||
|
|
||||||
int i = 1;
|
string matchType = match.Groups[1].Value;
|
||||||
while (true)
|
// Wraps the actual match data in curly braces to parse it as a json object
|
||||||
{
|
string matchData = $"{{{match.Groups[2].Value}}}";
|
||||||
if (data[i] == ',') break;
|
|
||||||
|
|
||||||
matchType += data[i];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
string matchData = $"{{{string.Concat(data.Skip(matchType.Length + 3).SkipLast(2))}}}"; // unfuck formatting so we can parse it as json
|
|
||||||
|
|
||||||
// JSON does not like the hex value that location comes in (0x7f000001) so, convert it to int
|
// JSON does not like the hex value that location comes in (0x7f000001) so, convert it to int
|
||||||
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{8}", m => Convert.ToInt32(m.Value, 16).ToString());
|
matchData = LocationHexRegex().Replace(matchData, m => Convert.ToInt32(m.Value, 16).ToString());
|
||||||
// oh, but it gets better than that! LBP also likes to send hex values with an uneven amount of digits (0xa000064, 7 digits). in any case, we handle it here:
|
|
||||||
matchData = Regex.Replace(matchData, @"0x[a-fA-F0-9]{7}", m => Convert.ToInt32(m.Value, 16).ToString());
|
|
||||||
// i'm actually crying about it.
|
|
||||||
|
|
||||||
return Deserialize(matchType, matchData);
|
return Deserialize(matchType, matchData);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue