mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace RadicadeServer.Tests;
|
||||
|
||||
public class ImageSignatureRequestTests
|
||||
{
|
||||
private static byte[] Metadata(params string[] literals)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream);
|
||||
writer.Write(0xFAB11BAFu);
|
||||
writer.Write(27);
|
||||
writer.Write(24);
|
||||
writer.Write(literals.Length * 8);
|
||||
writer.Write(24 + literals.Length * 8);
|
||||
writer.Write(literals.Sum(s => Encoding.UTF8.GetByteCount(s)));
|
||||
int index = 0;
|
||||
foreach (var literal in literals)
|
||||
{
|
||||
int size = Encoding.UTF8.GetByteCount(literal);
|
||||
writer.Write(size);
|
||||
writer.Write(index);
|
||||
index += size;
|
||||
}
|
||||
foreach (var literal in literals) writer.Write(Encoding.UTF8.GetBytes(literal));
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".rec.net")]
|
||||
[InlineData(".radicade.internal")]
|
||||
public void ClientMetadataControlsHeaderEvenOnWrongRoute(string tail)
|
||||
{
|
||||
var key = ImageSignatureRequest.DetectKeyId(Metadata("https://img.rec.net", tail));
|
||||
Assert.Equal("KEY:RSA:p1" + tail, key);
|
||||
var url = NameserverRedirect.Rewrite("https://img.rec.net/photo?width=64&sig=p1", "https://private.example");
|
||||
url = ImageSignatureRequest.AddKeyId(url, "https://private.example", key);
|
||||
Assert.Contains("width=64&sig=p1&rcsig=" + Uri.EscapeDataString(key), url);
|
||||
var header = RadicadeServer.Images.ImageAPI.SignatureFor(key, "bare", RadicadeServer.Images.ImageAPI.StockKeyId);
|
||||
Assert.StartsWith("key-id=" + key + "; data=", header);
|
||||
Assert.Equal(url, ImageSignatureRequest.AddKeyId(url, "https://private.example", key));
|
||||
}
|
||||
|
||||
// Rec Room's own metadata does not use the plain v27 header: the four literal-table
|
||||
// fields are shuffled into other 32-bit slots, so the reader discovers the layout.
|
||||
// This writes the same shape (the 20230831 build puts table offset at 0x74, table
|
||||
// size at 0x24, data offset at 0xA0, data size at 0x3C) at 0x100-aligned offsets.
|
||||
private static byte[] ShuffledMetadata(params string[] literals)
|
||||
{
|
||||
// Real metadata files carry tens of thousands of literals; the reader refuses
|
||||
// tables that are too small to be one, so the fixture is padded past that floor.
|
||||
var padded = literals.Concat(Enumerable.Range(0, 40).Select(i => $"filler literal {i}")).ToArray();
|
||||
int tableOffset = 0x100;
|
||||
int tableSize = padded.Length * 8;
|
||||
int dataOffset = tableOffset + tableSize;
|
||||
int dataSize = padded.Sum(s => Encoding.UTF8.GetByteCount(s));
|
||||
var file = new byte[dataOffset + dataSize];
|
||||
BitConverter.GetBytes(0x2EBE0u).CopyTo(file, 0);
|
||||
BitConverter.GetBytes(tableSize).CopyTo(file, 0x24);
|
||||
BitConverter.GetBytes(dataSize).CopyTo(file, 0x3C);
|
||||
BitConverter.GetBytes(tableOffset).CopyTo(file, 0x74);
|
||||
BitConverter.GetBytes(dataOffset).CopyTo(file, 0xA0);
|
||||
int index = 0;
|
||||
for (int i = 0; i < padded.Length; i++)
|
||||
{
|
||||
int size = Encoding.UTF8.GetByteCount(padded[i]);
|
||||
BitConverter.GetBytes(size).CopyTo(file, tableOffset + 8 * i);
|
||||
BitConverter.GetBytes(index).CopyTo(file, tableOffset + 8 * i + 4);
|
||||
Encoding.UTF8.GetBytes(padded[i]).CopyTo(file, dataOffset + index);
|
||||
index += size;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".rec.net")]
|
||||
[InlineData(".radicade.internal")]
|
||||
public void ShuffledHeaderMetadataIsDiscoveredStructurally(string tail)
|
||||
{
|
||||
var metadata = ShuffledMetadata("https://img.rec.net", tail, "another literal");
|
||||
Assert.Equal("KEY:RSA:p1" + tail, ImageSignatureRequest.DetectKeyIdFromGameMetadata(metadata));
|
||||
Assert.Equal("KEY:RSA:p1" + tail, ImageSignatureRequest.DetectKeyIdFromGameMetadata(metadata));
|
||||
// A plain v27 file keeps working through the same entry point.
|
||||
Assert.Equal("KEY:RSA:p1" + tail, ImageSignatureRequest.DetectKeyIdFromGameMetadata(Metadata(tail)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShuffledHeaderAmbiguityAndGarbageStayUndecided()
|
||||
{
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyIdFromGameMetadata(
|
||||
ShuffledMetadata(".rec.net", ".radicade.internal", "x")));
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyIdFromGameMetadata(
|
||||
ShuffledMetadata("https://img.rec.net", "no key tail here")));
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyIdFromGameMetadata(new byte[512]));
|
||||
var random = new byte[4096];
|
||||
new Random(7).NextBytes(random);
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyIdFromGameMetadata(random));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidOrAmbiguousMetadataDoesNotGuess()
|
||||
{
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyId(new byte[2]));
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyId(Metadata(".rec.net", ".radicade.internal")));
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyId(Metadata("https://img.rec.net")));
|
||||
var invalid = Metadata(".rec.net");
|
||||
Array.Copy(BitConverter.GetBytes(int.MaxValue), 0, invalid, 12, 4);
|
||||
Assert.Null(ImageSignatureRequest.DetectKeyId(invalid));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://other.example/images/photo")]
|
||||
[InlineData("https://private.example.evil/images/photo")]
|
||||
[InlineData("https://private.example/api/photo")]
|
||||
[InlineData("http://private.example/images/photo")]
|
||||
public void OnlyConfiguredServerImagesReceiveHint(string url)
|
||||
=> Assert.Equal(url, ImageSignatureRequest.AddKeyId(url, "https://private.example", "KEY:RSA:p1.rec.net"));
|
||||
|
||||
[Fact]
|
||||
public void PatchedRouteAndServerBasePathKeepParametersAndReplaceStaleHint()
|
||||
{
|
||||
var url = ImageSignatureRequest.AddKeyId("https://private.example/base/images/img/photo?rcsig=old&cropSquare=1#f",
|
||||
"https://private.example/base/", "KEY:RSA:p1.radicade.internal");
|
||||
Assert.Equal("https://private.example/base/images/img/photo?cropSquare=1&rcsig=KEY%3ARSA%3Ap1.radicade.internal#f", url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user