Skip to content

Commit d5f086c

Browse files
authored
Fix console escaping of placeholder values (#2029)
1 parent 8ec4cda commit d5f086c

File tree

3 files changed

+116
-18
lines changed

3 files changed

+116
-18
lines changed

docs/syntax/code.md

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,9 @@ render(input2);
135135

136136
::::
137137

138+
#### Automatic callouts
138139

139-
140-
141-
#### Magic Callouts
142-
143-
If a code block contains code comments in the form of `//` or `#`, callouts will be magically created 🪄.
140+
If a code block contains code comments in the form of `//` or `#`, callouts are automatically created.
144141

145142

146143
::::{tab-set}
@@ -231,9 +228,9 @@ bazbazbaz: 3 <3>
231228
232229
::::
233230
234-
#### Disable callouts
231+
#### Turn off callouts
235232
236-
You can disable callouts by adding a code block argument `callouts=false`.
233+
You can turn off callouts by adding a code block argument `callouts=false`.
237234

238235
::::{tab-set}
239236

@@ -276,27 +273,52 @@ In a console code block, the first line is highlighted as a dev console string a
276273
:::{tab-item} Output
277274

278275
```console
279-
GET /mydocuments/_search
276+
POST _reindex
280277
{
281-
"from": 1,
278+
"source": {
279+
"remote": {
280+
"host": "<OTHER_HOST_URL>",
281+
"username": "user",
282+
"password": "pass"
283+
},
284+
"index": "my-index-000001",
282285
"query": {
283-
"match_all" {}
286+
"match": {
287+
"test": "data"
288+
}
284289
}
290+
},
291+
"dest": {
292+
"index": "my-new-index-000001"
293+
}
285294
}
286295
```
287296

297+
288298
:::
289299

290300
:::{tab-item} Markdown
291301

292302
````markdown
293303
```console
294-
GET /mydocuments/_search
304+
POST _reindex
295305
{
296-
"from": 1,
306+
"source": {
307+
"remote": {
308+
"host": "<OTHER_HOST_URL>",
309+
"username": "user",
310+
"password": "pass"
311+
},
312+
"index": "my-index-000001",
297313
"query": {
298-
"match_all" {}
314+
"match": {
315+
"test": "data"
316+
}
299317
}
318+
},
319+
"dest": {
320+
"index": "my-new-index-000001"
321+
}
300322
}
301323
```
302324
````
@@ -402,7 +424,7 @@ This `code` is inline.
402424

403425
::::
404426

405-
## Supported Languages
427+
## Supported languages
406428

407-
Please refer to [hljs.ts](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Site/Assets/hljs.ts)
429+
Refer to [hljs.ts](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation.Site/Assets/hljs.ts)
408430
for a complete list of supported languages.

src/Elastic.Markdown/Myst/CodeBlocks/CodeViewModel.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.Net;
56
using Elastic.Markdown.Helpers;
67
using Microsoft.AspNetCore.Html;
78

@@ -33,11 +34,11 @@ public HtmlString RenderBlock()
3334
public HtmlString RenderLineWithCallouts(string content, int lineNumber)
3435
{
3536
if (EnhancedCodeBlock?.CallOuts == null)
36-
return new HtmlString(content);
37+
return new HtmlString(WebUtility.HtmlEncode(content));
3738

3839
var callouts = EnhancedCodeBlock.CallOuts.Where(c => c.Line == lineNumber);
3940
if (!callouts.Any())
40-
return new HtmlString(content);
41+
return new HtmlString(WebUtility.HtmlEncode(content));
4142

4243
var line = content;
4344
var html = new System.Text.StringBuilder();
@@ -50,7 +51,7 @@ public HtmlString RenderLineWithCallouts(string content, int lineNumber)
5051
}
5152
line = line.TrimEnd();
5253

53-
_ = html.Append(line);
54+
_ = html.Append(WebUtility.HtmlEncode(line));
5455

5556
// Add callout HTML after the line
5657
foreach (var callout in callouts)

tests/Elastic.Markdown.Tests/CodeBlocks/ConsoleCodeBlockTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,78 @@ public void RendersCalloutsInJsonContent()
346346
public void HasNoErrors() => Collector.Diagnostics.Should().BeEmpty();
347347
}
348348

349+
public class ConsoleWithHtmlCharsTests(ITestOutputHelper output) : ConsoleCodeBlockTests(output,
350+
"""
351+
```console
352+
POST /auth/login
353+
{
354+
"username": "<username>",
355+
"password": "<password>",
356+
"token": "<api_token>"
357+
}
358+
```
359+
"""
360+
)
361+
{
362+
[Fact]
363+
public void CreatesApiSegmentWithHtmlChars()
364+
{
365+
Block!.ApiSegments.Should().HaveCount(1);
366+
var segment = Block.ApiSegments[0];
367+
segment.Header.Should().Be("POST /auth/login");
368+
segment.ContentLines.Should().Contain(line => line.Contains("<username>"));
369+
segment.ContentLines.Should().Contain(line => line.Contains("<password>"));
370+
segment.ContentLines.Should().Contain(line => line.Contains("<api_token>"));
371+
}
372+
373+
[Fact]
374+
public void EscapesHtmlCharsInRenderedOutput()
375+
{
376+
var viewModel = new CodeViewModel
377+
{
378+
ApiSegments = Block!.ApiSegments,
379+
Language = Block.Language,
380+
Caption = null,
381+
CrossReferenceName = null,
382+
RawIncludedFileContents = null,
383+
EnhancedCodeBlock = Block
384+
};
385+
386+
var contentHtml = viewModel.RenderContentLinesWithCallouts(Block.ApiSegments[0].ContentLinesWithNumbers);
387+
388+
// HTML characters should be escaped in the output
389+
contentHtml.Value.Should().Contain("&lt;username&gt;");
390+
contentHtml.Value.Should().Contain("&lt;password&gt;");
391+
contentHtml.Value.Should().Contain("&lt;api_token&gt;");
392+
393+
// Original unescaped versions should NOT be present
394+
contentHtml.Value.Should().NotContain("\"username\": \"<username>\"");
395+
contentHtml.Value.Should().NotContain("\"password\": \"<password>\"");
396+
contentHtml.Value.Should().NotContain("\"token\": \"<api_token>\"");
397+
}
398+
399+
[Fact]
400+
public void EscapesHtmlCharsInHeader()
401+
{
402+
var viewModel = new CodeViewModel
403+
{
404+
ApiSegments = Block!.ApiSegments,
405+
Language = Block.Language,
406+
Caption = null,
407+
CrossReferenceName = null,
408+
RawIncludedFileContents = null,
409+
EnhancedCodeBlock = Block
410+
};
411+
412+
// Test header with HTML chars
413+
var headerWithHtml = "GET /api/<resource>";
414+
var headerHtml = viewModel.RenderLineWithCallouts(headerWithHtml, 1);
415+
416+
headerHtml.Value.Should().Contain("&lt;resource&gt;");
417+
headerHtml.Value.Should().NotContain("<resource>");
418+
}
419+
420+
[Fact]
421+
public void HasNoErrors() => Collector.Diagnostics.Should().BeEmpty();
422+
}
423+

0 commit comments

Comments
 (0)