Skip to content

Commit b55cf2b

Browse files
committed
Make all Markdown extensions configurable
Allow site admins to selectively enable or disable Markdown extensions. This can help a site avoid creating documentation that isn't compatible with other CommonMark parsers. Change-Id: I0466b03ef213a398d79f943af2ddf95a7e0853e7
1 parent 5c34e09 commit b55cf2b

File tree

8 files changed

+142
-38
lines changed

8 files changed

+142
-38
lines changed

Documentation/config.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,51 @@ The image limit places an upper bound on the byte size of input.
5252
imageLimit = 256K
5353
```
5454

55+
### Extensions
56+
57+
The following extensions can be enabled/disabled in the markdown
58+
section:
59+
60+
* `githubFlavor`: enable extensions that mirror GitHub Flavor
61+
Markdown behavior. Default is true.
62+
63+
* `autolink`: automatically convert plain URLs and email
64+
addresses into links. Default follows `gihubFlavor`.
65+
66+
* `blocknote`: Gitiles style note/promo/aside blocks to raise
67+
awareness to important content. Default false.
68+
69+
* `ghthematicbreak`: accept `--` for `<hr>`, like GitHub Flavor
70+
Markdown. Default follows `githubFlavor`.
71+
72+
* `multicolumn`: Gitiles extension to layout content in a 12 cell
73+
grid, delinated by section headers. Default false.
74+
75+
* `namedanchor`: Gitiles extension to extract named anchors using
76+
`#{id}` syntax. Default false.
77+
78+
* `safehtml`: Gitiles extension to accept very limited HTML; for
79+
security reasons all other HTML is dropped regardless of this
80+
setting. Default follows `githubFlavor`.
81+
82+
* `smartquote`: Gitiles extension to convert single and double quote
83+
ASCII characters to Unicode smart quotes when in prose. Default
84+
false.
85+
86+
* `strikethrough`: strikethrough text with GitHub Flavor Markdown
87+
style `~~`. Default follows `githubFlavor`.
88+
89+
* `tables`: format tables with GitHub Flavor Markdown. Default
90+
follows `githubFlavor`.
91+
92+
* `toc`: Gitiles extension to replace `[TOC]` in a paragraph by itself
93+
with a server-side generated table of contents extracted from section
94+
headers. Default true.
95+
5596
### IFrames
5697

98+
IFrame support requires `markdown.safehtml` to be true.
99+
57100
IFrame source URLs can be whitelisted by providing a list of allowed
58101
URLs. URLs ending with a `/` are treated as prefixes, allowing any source
59102
URL beginning with that prefix.

Documentation/markdown.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ least 2 leading spaces:
150150

151151
### Tables
152152

153+
Requires `markdown.tables` to be true (default).
154+
153155
Simple tables are supported with column alignment. The first line is
154156
the header row and subsequent lines are data rows:
155157

@@ -199,6 +201,8 @@ opening `*` with \ such as `\*bold*`.
199201

200202
### Strikethrough
201203

204+
Requires `markdown.strikethrough` to be true (default).
205+
202206
Text can be ~~struck out~~ within a paragraph:
203207

204208
```
@@ -313,9 +317,10 @@ Supported languages include:
313317

314318
### Horizontal rules
315319

316-
A horizontal rule can be inserted using GitHub style `--` surrounded
317-
by blank lines. Alternatively repeating `-` or `*` and space on a
318-
line will also create a horizontal rule:
320+
If `markdown.ghthematicbreak` is true, a horizontal rule can be
321+
inserted using GitHub style `--` surrounded by blank lines.
322+
Alternatively repeating `-` or `*` and space on a line will also
323+
create a horizontal rule:
319324

320325
```
321326
---
@@ -380,7 +385,7 @@ will display the syntax highlighted source.
380385
### Named anchors
381386

382387
Explicit anchors can be inserted anywhere in the document using
383-
`<a name="tag"></a>` or `{#tag}`.
388+
`<a name="tag"></a>`, or `{#tag}` if `markdown.namedanchor` is true.
384389

385390
Implicit anchors are automatically created for each
386391
[heading](#Headings). For example `## Section 1` will have
@@ -460,9 +465,9 @@ Most HTML tags are not supported. HTML will be dropped on the floor
460465
by the parser with no warnings, and no output from that section of the
461466
document.
462467

463-
There are small exceptions for `<br>`, `<hr>`, `<a name>` and
464-
`<iframe>` elements, see [named anchor](#Named-anchors) and
465-
[HTML IFrame](#HTML-IFrame).
468+
If `markdown.safehtml` is true there are small exceptions for `<br>`,
469+
`<hr>`, `<a name>` and `<iframe>` elements, see [named anchor](#Named-anchors)
470+
and [HTML IFrame](#HTML-IFrame).
466471

467472
## Markdown extensions
468473

@@ -471,6 +476,8 @@ make documentation writing for the web easier without using raw HTML.
471476

472477
### Table of contents
473478

479+
Requires `markdown.toc` to be true.
480+
474481
Place `[TOC]` surrounded by blank lines to insert a generated
475482
table of contents extracted from the H1, H2, and H3 headers
476483
used within the document:
@@ -496,6 +503,8 @@ Anchors are automatically extracted from the headers, see
496503

497504
### Notification, aside, promotion blocks
498505

506+
Requires `markdown.blocknote` to be true.
507+
499508
Similar to fenced code blocks these blocks start and end with `***`,
500509
are surrounded by blank lines, and include the type of block on the
501510
opening line.
@@ -538,6 +547,8 @@ Promotions can raise awareness of an important concept.
538547

539548
### Column layout
540549

550+
Requires `markdown.multicolumn` to be true.
551+
541552
Gitiles markdown includes support for up to 12 columns of text across
542553
the width of the page. By default space is divided equally between
543554
the columns.
@@ -608,6 +619,8 @@ renders as:
608619

609620
### HTML IFrame
610621

622+
Requires `markdown.safehtml` to be true (default).
623+
611624
Although HTML is stripped the parser has special support for a limited
612625
subset of `<iframe>` elements:
613626

gitiles-servlet/src/main/java/com/google/gitiles/ReadmeHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ SanitizedContent render() {
9898
.setReader(reader)
9999
.setRootTree(rootTree)
100100
.build()
101-
.toSoyHtml(GitilesMarkdown.parse(raw));
101+
.toSoyHtml(GitilesMarkdown.parse(config, raw));
102102
} catch (RuntimeException | IOException err) {
103103
log.error(
104104
String.format(

gitiles-servlet/src/main/java/com/google/gitiles/doc/DocServlet.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ private void showDoc(
167167
MarkdownFile srcFile)
168168
throws IOException {
169169
Map<String, Object> data = new HashMap<>();
170-
data.putAll(buildNavbar(fmt, navFile));
170+
data.putAll(buildNavbar(cfg, fmt, navFile));
171171

172-
Node doc = GitilesMarkdown.parse(srcFile.consumeContent());
172+
Node doc = GitilesMarkdown.parse(cfg, srcFile.consumeContent());
173173
data.put("pageTitle", pageTitle(doc, srcFile));
174174
if (view.getType() != GitilesView.Type.ROOTED_DOC) {
175175
data.put("sourceUrl", GitilesView.show().copyFrom(view).toUrl());
@@ -190,11 +190,12 @@ private void showDoc(
190190
}
191191
}
192192

193-
private Map<String, Object> buildNavbar(MarkdownToHtml.Builder fmt, MarkdownFile navFile) {
193+
private Map<String, Object> buildNavbar(
194+
MarkdownConfig cfg, MarkdownToHtml.Builder fmt, MarkdownFile navFile) {
194195
Navbar navbar = new Navbar();
195196
if (navFile != null) {
196197
navbar.setFormatter(fmt.setFilePath(navFile.path).build());
197-
navbar.setMarkdown(navFile.consumeContent());
198+
navbar.setMarkdown(cfg, navFile.consumeContent());
198199
}
199200
return navbar.toSoyData();
200201
}

gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
package com.google.gitiles.doc;
1616

17-
import com.google.common.collect.ImmutableList;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import org.commonmark.Extension;
1820
import org.commonmark.ext.autolink.AutolinkExtension;
1921
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
2022
import org.commonmark.ext.gfm.tables.TablesExtension;
@@ -24,28 +26,43 @@
2426

2527
/** Parses Gitiles style CommonMark Markdown. */
2628
public class GitilesMarkdown {
27-
private static final Parser PARSER =
28-
Parser.builder()
29-
.extensions(
30-
ImmutableList.of(
31-
AutolinkExtension.create(),
32-
BlockNoteExtension.create(),
33-
GitilesHtmlExtension.create(),
34-
GitHubThematicBreakExtension.create(),
35-
MultiColumnExtension.create(),
36-
NamedAnchorExtension.create(),
37-
SmartQuotedExtension.create(),
38-
StrikethroughExtension.create(),
39-
TablesExtension.create(),
40-
TocExtension.create()))
41-
.build();
42-
43-
public static Node parse(byte[] md) {
44-
return parse(RawParseUtils.decode(md));
29+
public static Node parse(MarkdownConfig cfg, byte[] md) {
30+
return parse(cfg, RawParseUtils.decode(md));
4531
}
4632

47-
public static Node parse(String md) {
48-
return PARSER.parse(md);
33+
public static Node parse(MarkdownConfig cfg, String md) {
34+
List<Extension> ext = new ArrayList<>();
35+
if (cfg.autoLink) {
36+
ext.add(AutolinkExtension.create());
37+
}
38+
if (cfg.blockNote) {
39+
ext.add(BlockNoteExtension.create());
40+
}
41+
if (cfg.safeHtml) {
42+
ext.add(GitilesHtmlExtension.create());
43+
}
44+
if (cfg.ghThematicBreak) {
45+
ext.add(GitHubThematicBreakExtension.create());
46+
}
47+
if (cfg.multiColumn) {
48+
ext.add(MultiColumnExtension.create());
49+
}
50+
if (cfg.namedAnchor) {
51+
ext.add(NamedAnchorExtension.create());
52+
}
53+
if (cfg.smartQuote) {
54+
ext.add(SmartQuotedExtension.create());
55+
}
56+
if (cfg.strikethrough) {
57+
ext.add(StrikethroughExtension.create());
58+
}
59+
if (cfg.tables) {
60+
ext.add(TablesExtension.create());
61+
}
62+
if (cfg.toc) {
63+
ext.add(TocExtension.create());
64+
}
65+
return Parser.builder().extensions(ext).build().parse(md);
4966
}
5067

5168
private GitilesMarkdown() {}

gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownConfig.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ public MarkdownConfig parse(Config cfg) {
4141
final int imageLimit;
4242
final String analyticsId;
4343

44+
final boolean autoLink;
45+
final boolean blockNote;
46+
final boolean ghThematicBreak;
47+
final boolean multiColumn;
48+
final boolean namedAnchor;
49+
final boolean safeHtml;
50+
final boolean smartQuote;
51+
final boolean strikethrough;
52+
final boolean tables;
53+
final boolean toc;
54+
4455
private final boolean allowAnyIFrame;
4556
private final ImmutableList<String> allowIFrame;
4657

@@ -50,7 +61,22 @@ public MarkdownConfig parse(Config cfg) {
5061
imageLimit = cfg.getInt("markdown", "imageLimit", IMAGE_LIMIT);
5162
analyticsId = Strings.emptyToNull(cfg.getString("google", null, "analyticsId"));
5263

53-
String[] f = cfg.getStringList("markdown", null, "allowiframe");
64+
boolean githubFlavor = cfg.getBoolean("markdown", "githubFlavor", true);
65+
autoLink = cfg.getBoolean("markdown", "autolink", githubFlavor);
66+
blockNote = cfg.getBoolean("markdown", "blocknote", false);
67+
ghThematicBreak = cfg.getBoolean("markdown", "ghthematicbreak", githubFlavor);
68+
multiColumn = cfg.getBoolean("markdown", "multicolumn", false);
69+
namedAnchor = cfg.getBoolean("markdown", "namedanchor", false);
70+
safeHtml = cfg.getBoolean("markdown", "safehtml", githubFlavor);
71+
smartQuote = cfg.getBoolean("markdown", "smartquote", false);
72+
strikethrough = cfg.getBoolean("markdown", "strikethrough", githubFlavor);
73+
tables = cfg.getBoolean("markdown", "tables", githubFlavor);
74+
toc = cfg.getBoolean("markdown", "toc", true);
75+
76+
String[] f = {};
77+
if (safeHtml) {
78+
f = cfg.getStringList("markdown", null, "allowiframe");
79+
}
5480
allowAnyIFrame = f.length == 1 && StringUtils.toBooleanOrNull(f[0]) == Boolean.TRUE;
5581
if (allowAnyIFrame) {
5682
allowIFrame = ImmutableList.of();

gitiles-servlet/src/main/java/com/google/gitiles/doc/Navbar.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ Navbar setFormatter(MarkdownToHtml html) {
4242
return this;
4343
}
4444

45-
Navbar setMarkdown(byte[] md) {
45+
Navbar setMarkdown(MarkdownConfig cfg, byte[] md) {
4646
if (md != null && md.length > 0) {
47-
parse(RawParseUtils.decode(md));
47+
parse(cfg, RawParseUtils.decode(md));
4848
}
4949
return this;
5050
}
@@ -73,8 +73,8 @@ private Object logo() {
7373
}
7474
}
7575

76-
private void parse(String markdown) {
77-
node = GitilesMarkdown.parse(markdown);
76+
private void parse(MarkdownConfig cfg, String markdown) {
77+
node = GitilesMarkdown.parse(cfg, markdown);
7878

7979
extractSiteTitle();
8080
extractRefLinks(markdown);

gitiles-servlet/src/test/java/com/google/gitiles/TestGitilesAccess.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ public RepositoryDescription getRepositoryDescription() {
7878
@Override
7979
public Config getConfig() {
8080
Config config = new Config();
81+
config.setBoolean("markdown", null, "blocknote", true);
82+
config.setBoolean("markdown", null, "multicolumn", true);
83+
config.setBoolean("markdown", null, "namedanchor", true);
84+
config.setBoolean("markdown", null, "smartquote", true);
8185
config.setStringList(
8286
"gitiles", null, "allowOriginRegex", ImmutableList.of("http://localhost"));
8387
return config;

0 commit comments

Comments
 (0)