fix(376): XML-escape access_token value in XMLTV guide output #419
@@ -60,7 +60,10 @@ public partial class GetChannelGuideHandler(
|
||||
var accessTokenUri = $"?v={mtime}";
|
||||
if (!string.IsNullOrWhiteSpace(request.AccessToken))
|
||||
{
|
||||
accessTokenUri += $"&access_token={request.AccessToken}";
|
||||
// The token value is HTTP-request-derived and interpolated raw into the pre-built XMLTV
|
||||
// cache fragments, so it must be XML-escaped like {RequestBase} above — a token containing
|
||||
// '&', '<', '>', or '"' would otherwise malform the whole guide. Opaque tokens are a no-op.
|
||||
accessTokenUri += $"&access_token={SecurityElement.Escape(request.AccessToken)}";
|
||||
}
|
||||
|
||||
string channelsFragment = await ReadAllTextShared(channelsFile, cancellationToken);
|
||||
|
||||
@@ -187,6 +187,47 @@ public class ChannelGuideGoldenTests
|
||||
xml.ShouldNotContain("a&b");
|
||||
}
|
||||
|
||||
// The access-token value is HTTP-request-derived (?access_token=) and interpolated raw into the
|
||||
// {AccessTokenUri} placeholder, so a token containing XML-special chars must be escaped too —
|
||||
// otherwise it malforms the whole guide, exactly like the {RequestBase} case above. (Finding #376.)
|
||||
[Test]
|
||||
public async Task Guide_xml_escapes_access_token()
|
||||
{
|
||||
MockFileSystem fileSystem = BuildCacheFileSystem();
|
||||
var localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
localFileSystem
|
||||
.ListFiles(FileSystemLayout.ChannelGuideCacheFolder)
|
||||
.Returns(new[]
|
||||
{
|
||||
FragmentPath(fileSystem, "channels.xml"),
|
||||
FragmentPath(fileSystem, "2.xml")
|
||||
});
|
||||
|
||||
var configElementRepository = Substitute.For<IConfigElementRepository>();
|
||||
configElementRepository
|
||||
.GetValue<string>(Arg.Any<ConfigElementKey>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Option<string>.None);
|
||||
|
||||
var handler = new GetChannelGuideHandler(
|
||||
_dbContextFactory,
|
||||
new RecyclableMemoryStreamManager(),
|
||||
fileSystem,
|
||||
localFileSystem,
|
||||
configElementRepository);
|
||||
|
||||
Either<BaseError, ChannelGuide> result = await handler.Handle(
|
||||
new GetChannelGuide(Scheme, Host, BaseUrl: "", AccessToken: "tok&<>\""),
|
||||
CancellationToken.None);
|
||||
|
||||
string xml = result.Match(
|
||||
Right: guide => guide.ToXml(),
|
||||
Left: error => throw new AssertionException($"Handler returned error: {error.Value}"));
|
||||
|
||||
// Every XML-special char in the token must be escaped; the raw token must never reach the output.
|
||||
xml.ShouldContain("access_token=tok&<>"");
|
||||
xml.ShouldNotContain("access_token=tok&<");
|
||||
}
|
||||
|
||||
// --- harness ---
|
||||
|
||||
private async Task Verify(string goldenName, GetChannelGuide request)
|
||||
|
||||
Reference in New Issue
Block a user