Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading;
Expand Down Expand Up @@ -34,6 +35,11 @@ public async ValueTask WriteReportAsync(
createdAt: DateTime.UtcNow,
generatorVersion: Constants.Version);

// Serialize the dataset to JSON, then HTML-encode it for safe embedding in a data attribute.
// This pattern avoids XSS vulnerabilities that can occur when embedding JSON directly in script blocks.
string json = JsonSerializer.Serialize(dataset, JsonUtilities.Compact.DatasetTypeInfo);
string htmlEncodedJson = WebUtility.HtmlEncode(json);

using var stream =
new FileStream(
reportFilePath,
Expand All @@ -47,22 +53,12 @@ public async ValueTask WriteReportAsync(

#if NET
await writer.WriteAsync(HtmlTemplateBefore.AsMemory(), cancellationToken).ConfigureAwait(false);
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
#else
await writer.WriteAsync(HtmlTemplateBefore).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
#endif

await JsonSerializer.SerializeAsync(
stream,
dataset,
JsonUtilities.Compact.DatasetTypeInfo,
cancellationToken).ConfigureAwait(false);

#if NET
await writer.WriteAsync(htmlEncodedJson.AsMemory(), cancellationToken).ConfigureAwait(false);
await writer.WriteAsync(HtmlTemplateAfter.AsMemory(), cancellationToken).ConfigureAwait(false);
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
#else
await writer.WriteAsync(HtmlTemplateBefore).ConfigureAwait(false);
await writer.WriteAsync(htmlEncodedJson).ConfigureAwait(false);
await writer.WriteAsync(HtmlTemplateAfter).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
#endif
Expand All @@ -86,8 +82,8 @@ static HtmlReportWriter()
using var reader = new StreamReader(resourceStream);
string all = reader.ReadToEnd();

// This is the placeholder for the results array in the template.
const string SearchString = @"{scenarioRunResults:[]}";
// This is the placeholder in the data-dataset attribute on the root div.
const string SearchString = "##DATASETS##";

int start = all.IndexOf(SearchString, StringComparison.Ordinal);
if (start == -1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</head>

<body>
<div id="root"></div>
<div id="root" data-dataset="##DATASETS##"></div>
<script type="module" src="./src/main.tsx"></script>
</body>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ import { ReportContextProvider } from '../../components/ReportContext.tsx';

let dataset: Dataset = { scenarioRunResults: [] };

const rootElement = document.getElementById('root')!;

if (!import.meta.env.PROD) {
// This only runs in development. In production the data is embedded into the dataset variable declaration above.
// This only runs in development. In production the data is read from the data-dataset attribute on the root element.
// run `node init-devdata.js` to populate the data file from the most recent execution.
const imported = await import("../devdata.json");
dataset = imported.default as unknown as Dataset;
} else {
// In production, the data is HTML-encoded and placed in a data-dataset attribute.
// This pattern avoids XSS vulnerabilities that can occur when embedding JSON in script blocks.
const datasetJson = rootElement.getAttribute('data-dataset');
if (datasetJson) {
dataset = JSON.parse(datasetJson) as Dataset;
}
}

const scoreSummary = createScoreSummary(dataset);

createRoot(document.getElementById('root')!).render(
createRoot(rootElement).render(
<FluentProvider theme={webLightTheme}>
<StrictMode>
<ReportContextProvider dataset={dataset} scoreSummary={scoreSummary}>
Expand Down
Loading