106 lines
6.1 KiB
C#
106 lines
6.1 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Xml.Linq;
|
|
using WxAgent.Core;
|
|
using Xunit;
|
|
using ZstdSharp;
|
|
|
|
namespace WxAgent.Core.Tests;
|
|
|
|
public sealed class WechatMergedChatTests
|
|
{
|
|
[Fact]
|
|
public void ParsesEmbeddedTextMetadataWithoutDeduplicatingRepeatedMessages()
|
|
{
|
|
var result = WechatMergedChatParser.Parse(Wrap(Record(Item("same\n\ntext"), Item("same\n\ntext"))));
|
|
Assert.Equal(2, result.Messages.Count);
|
|
Assert.Equal("0", result.Messages[0].Path);
|
|
Assert.Equal("1", result.Messages[1].Path);
|
|
Assert.Equal("same\n\ntext", result.Messages[0].Text);
|
|
Assert.Equal("Synthetic sender", result.Messages[0].SenderName);
|
|
Assert.Equal("opaque-hash", result.Messages[0].SenderHash);
|
|
Assert.Equal("18446744073709551615", result.Messages[0].SourceServerId);
|
|
Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1700000000), result.Messages[0].Timestamp);
|
|
}
|
|
|
|
[Fact]
|
|
public void SupportsNestedRecordsAndExplicitAttachmentMetadata()
|
|
{
|
|
var attachment = new XElement("dataitem", new XAttribute("datatype", 8),
|
|
new XElement("datatitle", "example.txt"), new XElement("datafmt", "txt"), new XElement("datasize", 42),
|
|
new XElement("cdn_dataurl", "must-not-be-exported"), new XElement("aeskey", "must-not-be-exported"));
|
|
var nested = new XElement("dataitem", new XAttribute("datatype", 17), Record(Item("nested")));
|
|
var result = WechatMergedChatParser.Parse(Record(attachment, nested).ToString());
|
|
Assert.Null(result.Messages[0].Text);
|
|
Assert.Equal(42, result.Messages[0].SizeBytes);
|
|
Assert.Equal("1/0", result.Messages[1].NestedRecord!.Messages[0].Path);
|
|
Assert.DoesNotContain("must-not-be-exported", JsonSerializer.Serialize(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void PreservesMixedImageVideoFileAndNestedAttachmentMetadata()
|
|
{
|
|
static XElement Attachment(int type, string title, string format, long size) => new("dataitem",
|
|
new XAttribute("datatype", type), new XElement("datatitle", title), new XElement("datafmt", format), new XElement("datasize", size));
|
|
|
|
var image = Attachment(2, "image.jpg", "jpg", 12);
|
|
var video = Attachment(4, "clip.mp4", "mp4", 34);
|
|
var file = Attachment(8, "notes.pdf", "pdf", 56);
|
|
var nested = new XElement("dataitem", new XAttribute("datatype", 17), Record(Attachment(2, "nested.png", "png", 78)));
|
|
|
|
var result = WechatMergedChatParser.Parse(Record(image, video, file, nested).ToString());
|
|
|
|
Assert.Equal([2, 4, 8, 17], result.Messages.Select(message => message.DataType));
|
|
Assert.Equal([12L, 34L, 56L], result.Messages.Take(3).Select(message => message.SizeBytes));
|
|
Assert.Equal(["image.jpg", "clip.mp4", "notes.pdf"], result.Messages.Take(3).Select(message => message.Title));
|
|
var nestedMessage = result.Messages[3].NestedRecord!.Messages[0];
|
|
Assert.Equal("3/0", nestedMessage.Path);
|
|
Assert.Equal(2, nestedMessage.DataType);
|
|
}
|
|
|
|
[Fact]
|
|
public void RejectsTruncationMissingPayloadAndInvalidTimestamp()
|
|
{
|
|
var record = Record(Item("hello"));
|
|
record.Element("datalist")!.SetAttributeValue("count", 2);
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(record.ToString()));
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(Record(new XElement("dataitem", new XAttribute("datatype", 1))).ToString()));
|
|
var item = Item("hello");
|
|
item.Element("srcMsgCreateTime")!.Value = long.MaxValue.ToString();
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(Record(item).ToString()));
|
|
}
|
|
|
|
[Fact]
|
|
public void RejectsDtdAndBoundViolations()
|
|
{
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse("<!DOCTYPE recordinfo [<!ENTITY x SYSTEM 'file:///not-read'>]><recordinfo>&x;</recordinfo>"));
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(new string('x', WechatMergedChatParser.MaxXmlCharacters + 1)));
|
|
var nested = Record(Item("innermost"));
|
|
for (var i = 0; i < WechatMergedChatParser.MaxNesting; i++)
|
|
nested = Record(new XElement("dataitem", new XAttribute("datatype", 17), nested));
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(nested.ToString()));
|
|
Assert.Throws<InvalidDataException>(() => WechatMergedChatParser.Parse(Record(Enumerable.Range(0, WechatMergedChatParser.MaxMessages + 1).Select(_ => Item("x")).ToArray()).ToString()));
|
|
}
|
|
|
|
[Fact]
|
|
public void PreservesComposite64BitMessageTypeAndBoundsZstdDecoding()
|
|
{
|
|
var message = new DbMessage(149, 123, "filehelper", null, null, null, 81604378673L, "xml", DateTimeOffset.UnixEpoch, null);
|
|
using var json = JsonDocument.Parse(JsonSerializer.Serialize(message));
|
|
Assert.Equal(81604378673L, json.RootElement.GetProperty("Type").GetInt64());
|
|
using var output = new MemoryStream();
|
|
using (var compressed = new CompressionStream(output))
|
|
compressed.Write(Encoding.UTF8.GetBytes(new string('x', WechatMergedChatParser.MaxXmlCharacters + 1)));
|
|
Assert.Throws<InvalidDataException>(() => WechatDbMessage.DecodeContent(Convert.ToHexString(output.ToArray()), true));
|
|
}
|
|
|
|
private static XElement Item(string text) => new("dataitem", new XAttribute("datatype", 1), new XAttribute("dataid", "repeated-id"),
|
|
new XElement("datadesc", text), new XElement("sourcename", "Synthetic sender"), new XElement("srcMsgLocalId", "7"),
|
|
new XElement("srcMsgCreateTime", "1700000000"), new XElement("fromnewmsgid", "18446744073709551615"),
|
|
new XElement("dataitemsource", new XElement("hashusername", "opaque-hash")));
|
|
private static XElement Record(params XElement[] items) => new("recordinfo", new XElement("title", "Synthetic record"),
|
|
new XElement("datalist", new XAttribute("count", items.Length), items));
|
|
private static string Wrap(XElement record) => new XElement("msg", new XElement("appmsg", new XElement("type", 19),
|
|
new XElement("recorditem", new XCData(record.ToString(SaveOptions.DisableFormatting))))).ToString();
|
|
}
|