Merge "Remove html commentlink functionality."
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index d50e69d..3e82254 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1727,9 +1727,7 @@
will match typical Gerrit Change-Id values and create a hyperlink
to changes which reference it. The second configuration 'bugzilla'
will hyperlink terms such as 'bug 42' to an external bug tracker,
-supplying the argument record number '42' for display. The third
-configuration 'tracker' uses raw HTML to more precisely control
-how the replacement is displayed to the user.
+supplying the argument record number '42' for display.
commentlinks supports link:#reloadConfig[configuration reloads]. Though a
link:cmd-flush-caches.html[flush-caches] of "projects" is needed for the
@@ -1746,16 +1744,10 @@
prefix = $1
suffix = $4
text = $2$3
-
-[commentlink "tracker"]
- match = ([Bb]ug:\\s+)(\\d+)
- html = $1<a href=\"http://trak.example.com/$2\">$2</a>
----
Comment links can also be specified in `project.config` and sections in
-children override those in parents. The only restriction is that to
-avoid injecting arbitrary user-supplied HTML in the page, comment links
-defined in `project.config` may only supply `link`, not `html`.
+children override those in parents.
[[commentlink.name.match]]commentlink.<name>.match::
+
@@ -1789,38 +1781,23 @@
+
The URL to direct the user to whenever the regular expression is
matched. Groups in the match expression may be accessed as `$'n'`.
-+
-The link property is used only when the html property is not present.
[[commentlink.name.prefix]]commentlink.<name>.prefix::
+
The text inserted before the link. Groups in the match expression may be
accessed as `$'n'`.
-+
-The link property is used only when the html property is not present.
[[commentlink.name.suffix]]commentlink.<name>.suffix::
+
The text inserted after the link. Groups in the match expression may be
accessed as `$'n'`.
-+
-The link property is used only when the html property is not present.
[[commentlink.name.text]]commentlink.<name>.text::
+
The text content of the link. Groups in the match expression may be
accessed as `$'n'`.
+
-The link property is used only when the html property is not present.
-
-[[commentlink.name.html]]commentlink.<name>.html::
-+
-HTML to replace the entire matched string with. If present,
-this property overrides the link property above. Groups in the
-match expression may be accessed as `$'n'`.
-+
-The configuration file eats double quotes, so escaping them as
-`\"` is necessary to protect them from the parser.
+If not specified defaults to `$&` (the matched text).
[[commentlink.name.enabled]]commentlink.<name>.enabled::
+
@@ -1828,11 +1805,6 @@
section in a parent or the site-wide config that is disabled by
specifying `enabled = true`.
+
-Disabling sections in `gerrit.config` can be used by site administrators
-to create a library of comment links with `html` set that are not
-user-supplied and thus can be verified to be XSS-free, but are only
-enabled for a subset of projects.
-+
By default, true.
+
Note that the names and contents of disabled sections are visible even
diff --git a/java/com/google/gerrit/entities/StoredCommentLinkInfo.java b/java/com/google/gerrit/entities/StoredCommentLinkInfo.java
index 75bc034..5ee76da 100644
--- a/java/com/google/gerrit/entities/StoredCommentLinkInfo.java
+++ b/java/com/google/gerrit/entities/StoredCommentLinkInfo.java
@@ -31,7 +31,7 @@
public abstract String getMatch();
/**
- * The link to replace the match with. This can only be set if html is {@code null}.
+ * The link to replace the match with.
*
* <p>The constructed link is using {@link #getLink()} {@link #getPrefix()} {@link #getSuffix()}
* and {@link #getText()}, and has the shape of
@@ -41,31 +41,18 @@
@Nullable
public abstract String getLink();
- /**
- * The text before the link tag that the match is replaced with. This can only be set if link is
- * not {@code null}.
- */
+ /** The optional text before the link tag that the match is replaced with. */
@Nullable
public abstract String getPrefix();
- /**
- * The text after the link tag that the match is replaced with. This can only be set if link is
- * not {@code null}.
- */
+ /** The optional text after the link tag that the match is replaced with. */
@Nullable
public abstract String getSuffix();
- /**
- * The content of the link tag that the match is replaced with. This can only be set if link is
- * not {@code null}.
- */
+ /** The content of the link tag that the match is replaced with. If not set full match is used. */
@Nullable
public abstract String getText();
- /** The html to replace the match with. This can only be set if link is {@code null}. */
- @Nullable
- public abstract String getHtml();
-
/** Weather this comment link is active. {@code null} means true. */
@Nullable
public abstract Boolean getEnabled();
@@ -103,7 +90,6 @@
.setPrefix(src.prefix)
.setSuffix(src.suffix)
.setText(src.text)
- .setHtml(src.html)
.setEnabled(enabled)
.setOverrideOnly(false)
.build();
@@ -118,7 +104,6 @@
info.prefix = getPrefix();
info.suffix = getSuffix();
info.text = getText();
- info.html = getHtml();
info.enabled = getEnabled();
return info;
}
@@ -137,25 +122,21 @@
public abstract Builder setText(@Nullable String value);
- public abstract Builder setHtml(@Nullable String value);
-
public abstract Builder setEnabled(@Nullable Boolean value);
public abstract Builder setOverrideOnly(boolean value);
public StoredCommentLinkInfo build() {
checkArgument(getName() != null, "invalid commentlink.name");
- setLink(Strings.emptyToNull(getLink()));
setPrefix(Strings.emptyToNull(getPrefix()));
setSuffix(Strings.emptyToNull(getSuffix()));
setText(Strings.emptyToNull(getText()));
- setHtml(Strings.emptyToNull(getHtml()));
if (!getOverrideOnly()) {
checkArgument(
!Strings.isNullOrEmpty(getMatch()), "invalid commentlink.%s.match", getName());
checkArgument(
- (getLink() != null && getHtml() == null) || (getLink() == null && getHtml() != null),
- "commentlink.%s must have either link or html",
+ !Strings.isNullOrEmpty(getLink()),
+ "commentlink.%s must have link specified",
getName());
}
return autoBuild();
@@ -175,8 +156,6 @@
protected abstract String getText();
- protected abstract String getHtml();
-
protected abstract boolean getOverrideOnly();
}
}
diff --git a/java/com/google/gerrit/extensions/api/projects/CommentLinkInfo.java b/java/com/google/gerrit/extensions/api/projects/CommentLinkInfo.java
index b45fcee..5c47ac3 100644
--- a/java/com/google/gerrit/extensions/api/projects/CommentLinkInfo.java
+++ b/java/com/google/gerrit/extensions/api/projects/CommentLinkInfo.java
@@ -24,7 +24,6 @@
public String prefix;
public String suffix;
public String text;
- public String html;
public Boolean enabled; // null means true
public transient String name;
@@ -41,7 +40,6 @@
&& Objects.equals(this.prefix, that.prefix)
&& Objects.equals(this.suffix, that.suffix)
&& Objects.equals(this.text, that.text)
- && Objects.equals(this.html, that.html)
&& Objects.equals(this.enabled, that.enabled);
}
return false;
@@ -49,7 +47,7 @@
@Override
public int hashCode() {
- return Objects.hash(match, link, html, enabled);
+ return Objects.hash(match, link, prefix, suffix, text, enabled);
}
@Override
@@ -61,7 +59,6 @@
.add("prefix", prefix)
.add("suffix", suffix)
.add("text", text)
- .add("html", html)
.add("enabled", enabled)
.toString();
}
diff --git a/java/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializer.java b/java/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializer.java
index 5aa7a2a..5ac9ac4 100644
--- a/java/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializer.java
+++ b/java/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializer.java
@@ -30,7 +30,6 @@
.setPrefix(emptyToNull(proto.getPrefix()))
.setSuffix(emptyToNull(proto.getSuffix()))
.setText(emptyToNull(proto.getText()))
- .setHtml(emptyToNull(proto.getHtml()))
.setEnabled(proto.getEnabled())
.setOverrideOnly(proto.getOverrideOnly())
.build();
@@ -44,7 +43,6 @@
.setPrefix(nullToEmpty(autoValue.getPrefix()))
.setSuffix(nullToEmpty(autoValue.getSuffix()))
.setText(nullToEmpty(autoValue.getText()))
- .setHtml(nullToEmpty(autoValue.getHtml()))
.setEnabled(Optional.ofNullable(autoValue.getEnabled()).orElse(true))
.setOverrideOnly(autoValue.getOverrideOnly())
.build();
diff --git a/java/com/google/gerrit/server/project/CommentLinkProvider.java b/java/com/google/gerrit/server/project/CommentLinkProvider.java
index c2ac68a..88f045e 100644
--- a/java/com/google/gerrit/server/project/CommentLinkProvider.java
+++ b/java/com/google/gerrit/server/project/CommentLinkProvider.java
@@ -48,7 +48,7 @@
ImmutableList.builderWithExpectedSize(subsections.size());
for (String name : subsections) {
try {
- StoredCommentLinkInfo cl = ProjectConfig.buildCommentLink(cfg, name, true);
+ StoredCommentLinkInfo cl = ProjectConfig.buildCommentLink(cfg, name);
if (cl.getOverrideOnly()) {
logger.atWarning().log("commentlink %s empty except for \"enabled\"", name);
continue;
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 2dd7970..a964ee1 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.project;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.entities.Permission.isPermission;
@@ -131,7 +130,6 @@
KEY_SR_OVERRIDE_IN_CHILD_PROJECTS);
public static final String KEY_MATCH = "match";
- private static final String KEY_HTML = "html";
public static final String KEY_LINK = "link";
public static final String KEY_PREFIX = "prefix";
public static final String KEY_SUFFIX = "suffix";
@@ -318,7 +316,7 @@
return builder.build();
}
- public static StoredCommentLinkInfo buildCommentLink(Config cfg, String name, boolean allowRaw)
+ public static StoredCommentLinkInfo buildCommentLink(Config cfg, String name)
throws IllegalArgumentException {
String match = cfg.getString(COMMENTLINK, name, KEY_MATCH);
if (match != null) {
@@ -335,9 +333,6 @@
String linkSuffix = cfg.getString(COMMENTLINK, name, KEY_SUFFIX);
String linkText = cfg.getString(COMMENTLINK, name, KEY_TEXT);
- String html = cfg.getString(COMMENTLINK, name, KEY_HTML);
- boolean hasHtml = !Strings.isNullOrEmpty(html);
-
String rawEnabled = cfg.getString(COMMENTLINK, name, KEY_ENABLED);
Boolean enabled;
if (rawEnabled != null) {
@@ -345,12 +340,8 @@
} else {
enabled = null;
}
- checkArgument(allowRaw || !hasHtml, "Raw html replacement not allowed");
- if (Strings.isNullOrEmpty(match)
- && Strings.isNullOrEmpty(link)
- && !hasHtml
- && enabled != null) {
+ if (Strings.isNullOrEmpty(match) && Strings.isNullOrEmpty(link) && enabled != null) {
if (enabled) {
return StoredCommentLinkInfo.enabled(name);
}
@@ -362,7 +353,6 @@
.setPrefix(linkPrefix)
.setSuffix(linkSuffix)
.setText(linkText)
- .setHtml(html)
.setEnabled(enabled)
.setOverrideOnly(false)
.build();
@@ -1215,7 +1205,7 @@
commentLinkSections = new LinkedHashMap<>(subsections.size());
for (String name : subsections) {
try {
- commentLinkSections.put(name, buildCommentLink(rc, name, false));
+ commentLinkSections.put(name, buildCommentLink(rc, name));
} catch (PatternSyntaxException e) {
error(
String.format(
@@ -1417,9 +1407,9 @@
unsetSection(rc, COMMENTLINK);
if (commentLinkSections != null) {
for (StoredCommentLinkInfo cm : commentLinkSections.values()) {
- rc.setString(COMMENTLINK, cm.getName(), KEY_MATCH, cm.getMatch());
- if (!Strings.isNullOrEmpty(cm.getHtml())) {
- rc.setString(COMMENTLINK, cm.getName(), KEY_HTML, cm.getHtml());
+ // Match and Link can be empty if the commentlink is override only.
+ if (!Strings.isNullOrEmpty(cm.getMatch())) {
+ rc.setString(COMMENTLINK, cm.getName(), KEY_MATCH, cm.getMatch());
}
if (!Strings.isNullOrEmpty(cm.getLink())) {
rc.setString(COMMENTLINK, cm.getName(), KEY_LINK, cm.getLink());
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index d4077c8..d4b30c2 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -313,7 +313,7 @@
cfg.setString(COMMENTLINK, name, KEY_TEXT, value.text);
}
cfg.setBoolean(COMMENTLINK, name, KEY_ENABLED, value.enabled == null || value.enabled);
- projectConfig.addCommentLinkSection(ProjectConfig.buildCommentLink(cfg, name, false));
+ projectConfig.addCommentLinkSection(ProjectConfig.buildCommentLink(cfg, name));
} else {
// Delete the commentlink section
projectConfig.removeCommentLinkSection(name);
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/CachedProjectConfigSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/CachedProjectConfigSerializerTest.java
index c7e09dc..bf8a071 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/entities/CachedProjectConfigSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/CachedProjectConfigSerializerTest.java
@@ -52,7 +52,7 @@
.addNotifySection(NotifyConfigSerializerTest.ALL_VALUES_SET)
.addLabelSection(LabelTypeSerializerTest.ALL_VALUES_SET)
.addSubscribeSection(SubscribeSectionSerializerTest.ALL_VALUES_SET)
- .addCommentLinkSection(StoredCommentLinkInfoSerializerTest.HTML_ONLY)
+ .addCommentLinkSection(StoredCommentLinkInfoSerializerTest.LINK_ONLY)
.setRevision(Optional.of(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")))
.setRulesId(Optional.of(ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")))
.setExtensionPanelSections(ImmutableMap.of("key1", ImmutableList.of("val1", "val2")))
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializerTest.java
index e293493..aa6cfef 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/StoredCommentLinkInfoSerializerTest.java
@@ -22,27 +22,16 @@
import org.junit.Test;
public class StoredCommentLinkInfoSerializerTest {
- static final StoredCommentLinkInfo HTML_ONLY =
+ static final StoredCommentLinkInfo LINK_ONLY =
StoredCommentLinkInfo.builder("name")
.setEnabled(true)
- .setHtml("<p>html")
+ .setLink("a.com/b.html")
.setMatch("*")
.build();
@Test
- public void htmlOnly_roundTrip() {
- assertThat(deserialize(serialize(HTML_ONLY))).isEqualTo(HTML_ONLY);
- }
-
- @Test
public void linkOnly_roundTrip() {
- StoredCommentLinkInfo autoValue =
- StoredCommentLinkInfo.builder("name")
- .setEnabled(true)
- .setLink("<p>html")
- .setMatch("*")
- .build();
- assertThat(deserialize(serialize(autoValue))).isEqualTo(autoValue);
+ assertThat(deserialize(serialize(LINK_ONLY))).isEqualTo(LINK_ONLY);
}
@Test
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index ef92139..3b7ad1e 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -537,14 +537,14 @@
StoredCommentLinkInfo cm =
StoredCommentLinkInfo.builder("Test")
.setMatch("abc.*")
- .setHtml("<a>link</a>")
+ .setLink("link")
.setEnabled(true)
.setOverrideOnly(false)
.build();
cfg.addCommentLinkSection(cm);
rev = commit(cfg);
assertThat(text(rev, "project.config"))
- .isEqualTo("[commentlink \"Test\"]\n\tmatch = abc.*\n\thtml = <a>link</a>\n");
+ .isEqualTo("[commentlink \"Test\"]\n\tmatch = abc.*\n\tlink = link\n");
}
@Test
@@ -722,7 +722,7 @@
}
@Test
- public void readCommentLinksNoHtmlOrLinkButEnabled() throws Exception {
+ public void readCommentLinksNoLinkButEnabled() throws Exception {
RevCommit rev =
tr.commit().add("project.config", "[commentlink \"bugzilla\"]\n \tenabled = true").create();
ProjectConfig cfg = read(rev);
@@ -732,7 +732,7 @@
}
@Test
- public void readCommentLinksNoHtmlOrLinkAndDisabled() throws Exception {
+ public void readCommentLinksNoLinkAndDisabled() throws Exception {
RevCommit rev =
tr.commit()
.add("project.config", "[commentlink \"bugzilla\"]\n \tenabled = false")
@@ -744,7 +744,7 @@
}
@Test
- public void readCommentLinksNoHtmlOrLinkAndMissingEnabled() throws Exception {
+ public void readCommentLinksMissingEnabled() throws Exception {
RevCommit rev =
tr.commit()
.add(
@@ -792,26 +792,7 @@
}
@Test
- public void readCommentLinkRawHtml() throws Exception {
- RevCommit rev =
- tr.commit()
- .add(
- "project.config",
- "[commentlink \"bugzilla\"]\n"
- + "\tmatch = \"(bugs#?)(d+)\"\n"
- + "\thtml = http://bugs.example.com/show_bug.cgi?id=$2")
- .create();
- ProjectConfig cfg = read(rev);
- assertThat(cfg.getCommentLinkSections()).isEmpty();
- assertThat(cfg.getValidationErrors())
- .containsExactly(
- ValidationError.create(
- "project.config: Error in pattern \"(bugs#?)(d+)\" in commentlink.bugzilla.match: "
- + "Raw html replacement not allowed"));
- }
-
- @Test
- public void readCommentLinkMatchButNoHtmlOrLink() throws Exception {
+ public void readCommentLinkMatchButNoLink() throws Exception {
RevCommit rev =
tr.commit()
.add("project.config", "[commentlink \"bugzilla\"]\n" + "\tmatch = \"(bugs#?)(d+)\"\n")
@@ -822,7 +803,7 @@
.containsExactly(
ValidationError.create(
"project.config: Error in pattern \"(bugs#?)(d+)\" in commentlink.bugzilla.match: "
- + "commentlink.bugzilla must have either link or html"));
+ + "commentlink.bugzilla must have link specified"));
}
@Test
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index 1768552..1e8f78f 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -452,12 +452,11 @@
*/
export declare interface CommentLinkInfo {
match: string;
- link?: string;
+ link: string;
prefix?: string;
suffix?: string;
text?: string;
enabled?: boolean;
- html?: string;
}
export declare interface CommentLinks {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 218c04e..61ea5a1 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -47,10 +47,6 @@
match: '(LinkRewriteMe)',
link: 'http://google.com/$1',
},
- customHtmlRewrite: {
- match: 'HTMLRewriteMe',
- html: '<div>HTMLRewritten</div>',
- },
complexLinkRewrite: {
match: '(^|\\s)A Link (\\d+)($|\\s)',
link: '/page?id=$2',
@@ -101,11 +97,13 @@
await setCommentLinks({
capitalizeFoo: {
match: 'foo',
- html: 'FOO',
+ prefix: 'FOO',
+ link: 'a.b.c',
},
lowercaseFoo: {
match: 'FOO',
- html: 'foo',
+ prefix: 'foo',
+ link: 'c.d.e',
},
});
element.content = 'foo';
@@ -115,9 +113,8 @@
element,
/* HTML */ `
<pre class="plaintext">
- FOO
- </pre
- >
+ FOO<a href="a.b.c" rel="noopener" target="_blank">foo</a>
+ </pre>
`
);
});
@@ -126,11 +123,15 @@
await setCommentLinks({
bracketNum: {
match: '(Start:) ([0-9]+)',
- html: '$1 [$2]',
+ prefix: '$1 ',
+ link: 'bug/$2',
+ text: 'bug/$2',
},
bracketNum2: {
match: '(Start: [0-9]+) ([0-9]+)',
- html: '$1 [$2]',
+ prefix: '$1 ',
+ link: 'bug/$2',
+ text: 'bug/$2',
},
});
element.content = 'Start: 123 456';
@@ -140,9 +141,14 @@
element,
/* HTML */ `
<pre class="plaintext">
- Start: [123] [456]
- </pre
- >
+ Start:
+ <a href="bug/123" rel="noopener" target="_blank">
+ bug/123
+ </a>
+ <a href="bug/456" rel="noopener" target="_blank">
+ bug/456
+ </a>
+ </pre>
`
);
});
@@ -150,8 +156,7 @@
test('renders text with links and rewrites', async () => {
element.content = `text with plain link: google.com
\ntext with config link: LinkRewriteMe
- \ntext with complex link: A Link 12
- \ntext with config html: HTMLRewriteMe`;
+ \ntext with complex link: A Link 12`;
await element.updateComplete;
assert.shadowDom.equal(
@@ -178,8 +183,6 @@
>
Link 12
</a>
- text with config html:
- <div>HTMLRewritten</div>
</pre>
`
);
@@ -217,8 +220,7 @@
\ntext with plain link: google.com
\ntext with config link: LinkRewriteMe
\ntext without a link: NotA Link 15 cats
- \ntext with complex link: A Link 12
- \ntext with config html: HTMLRewriteMe`;
+ \ntext with complex link: A Link 12`;
await element.updateComplete;
assert.shadowDom.equal(
@@ -254,9 +256,6 @@
Link 12
</a>
</p>
- <p>text with config html:</p>
- <div>HTMLRewritten</div>
- <p></p>
</div>
</marked-element>
`
@@ -271,8 +270,7 @@
\n##### h5-heading
\n###### h6-heading
\n# heading with plain link: google.com
- \n# heading with config link: LinkRewriteMe
- \n# heading with config html: HTMLRewriteMe`;
+ \n# heading with config link: LinkRewriteMe`;
await element.updateComplete;
assert.shadowDom.equal(
@@ -302,10 +300,6 @@
LinkRewriteMe
</a>
</h1>
- <h1>
- heading with config html:
- <div>HTMLRewritten</div>
- </h1>
</div>
</marked-element>
`
@@ -315,8 +309,7 @@
test('renders inline-code without linking or rewriting', async () => {
element.content = `\`inline code\`
\n\`inline code with plain link: google.com\`
- \n\`inline code with config link: LinkRewriteMe\`
- \n\`inline code with config html: HTMLRewriteMe\``;
+ \n\`inline code with config link: LinkRewriteMe\``;
await element.updateComplete;
assert.shadowDom.equal(
@@ -333,9 +326,6 @@
<p>
<code>inline code with config link: LinkRewriteMe</code>
</p>
- <p>
- <code>inline code with config html: HTMLRewriteMe</code>
- </p>
</div>
</marked-element>
`
@@ -345,8 +335,7 @@
test('renders multiline-code without linking or rewriting', async () => {
element.content = `\`\`\`\nmultiline code\n\`\`\`
\n\`\`\`\nmultiline code with plain link: google.com\n\`\`\`
- \n\`\`\`\nmultiline code with config link: LinkRewriteMe\n\`\`\`
- \n\`\`\`\nmultiline code with config html: HTMLRewriteMe\n\`\`\``;
+ \n\`\`\`\nmultiline code with config link: LinkRewriteMe\n\`\`\``;
await element.updateComplete;
assert.shadowDom.equal(
@@ -363,9 +352,6 @@
<pre>
<code>multiline code with config link: LinkRewriteMe</code>
</pre>
- <pre>
- <code>multiline code with config html: HTMLRewriteMe</code>
- </pre>
</div>
</marked-element>
`
@@ -496,8 +482,7 @@
test('renders block quotes with links and rewrites', async () => {
element.content = `> block quote
\n> block quote with plain link: google.com
- \n> block quote with config link: LinkRewriteMe
- \n> block quote with config html: HTMLRewriteMe`;
+ \n> block quote with config link: LinkRewriteMe`;
await element.updateComplete;
assert.shadowDom.equal(
@@ -528,11 +513,6 @@
</a>
</p>
</blockquote>
- <blockquote>
- <p>block quote with config html:</p>
- <div>HTMLRewritten</div>
- <p></p>
- </blockquote>
</div>
</marked-element>
`
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 08ac65f..23a5794 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -136,9 +136,13 @@
nanosecondSuffix) as Timestamp;
}
-export function createCommentLink(match = 'test'): CommentLinkInfo {
+export function createCommentLink(
+ match = 'test',
+ link = 'http://test.com'
+): CommentLinkInfo {
return {
match,
+ link,
};
}
diff --git a/polygerrit-ui/app/utils/link-util.ts b/polygerrit-ui/app/utils/link-util.ts
index b5b9025..ec1e7e7 100644
--- a/polygerrit-ui/app/utils/link-util.ts
+++ b/polygerrit-ui/app/utils/link-util.ts
@@ -45,8 +45,7 @@
): RewriteResult[] {
const enabledRewrites = Object.values(repoCommentLinks).filter(
commentLinkInfo =>
- commentLinkInfo.enabled !== false &&
- (commentLinkInfo.link !== undefined || commentLinkInfo.html !== undefined)
+ commentLinkInfo.enabled !== false && commentLinkInfo.link !== undefined
);
return enabledRewrites.flatMap(rewrite => {
const regexp = new RegExp(rewrite.match, 'g');
@@ -127,25 +126,19 @@
matchedText: string,
rewrite: CommentLinkInfo
): string {
- if (rewrite.link !== undefined) {
- const replacementHref = rewrite.link.startsWith('/')
- ? `${getBaseUrl()}${rewrite.link}`
- : rewrite.link;
- const regexp = new RegExp(rewrite.match, 'g');
- return matchedText.replace(
- regexp,
- createLinkTemplate(
- replacementHref,
- rewrite.text ?? '$&',
- rewrite.prefix,
- rewrite.suffix
- )
- );
- } else if (rewrite.html !== undefined) {
- return matchedText.replace(new RegExp(rewrite.match, 'g'), rewrite.html);
- } else {
- throw new Error('commentLinkInfo is not a link or html rewrite');
- }
+ const replacementHref = rewrite.link.startsWith('/')
+ ? `${getBaseUrl()}${rewrite.link}`
+ : rewrite.link;
+ const regexp = new RegExp(rewrite.match, 'g');
+ return matchedText.replace(
+ regexp,
+ createLinkTemplate(
+ replacementHref,
+ rewrite.text ?? '$&',
+ rewrite.prefix,
+ rewrite.suffix
+ )
+ );
}
/**
diff --git a/polygerrit-ui/app/utils/link-util_test.ts b/polygerrit-ui/app/utils/link-util_test.ts
index 61d6bff..f5c13e8 100644
--- a/polygerrit-ui/app/utils/link-util_test.ts
+++ b/polygerrit-ui/app/utils/link-util_test.ts
@@ -76,67 +76,6 @@
);
});
});
- suite('html rewrites', () => {
- test('basic case', () => {
- assert.equal(
- linkifyUrlsAndApplyRewrite('foo', {
- foo: {
- match: '(foo)',
- html: '<div>$1</div>',
- },
- }),
- '<div>foo</div>'
- );
- });
-
- test('only inserts', () => {
- assert.equal(
- linkifyUrlsAndApplyRewrite('foo', {
- foo: {
- match: 'foo',
- html: 'foo bar',
- },
- }),
- 'foo bar'
- );
- });
-
- test('only deletes', () => {
- assert.equal(
- linkifyUrlsAndApplyRewrite('foo bar baz', {
- bar: {
- match: 'bar',
- html: '',
- },
- }),
- 'foo baz'
- );
- });
-
- test('multiple matches', () => {
- assert.equal(
- linkifyUrlsAndApplyRewrite('foo foo', {
- foo: {
- match: '(foo)',
- html: '<div>$1</div>',
- },
- }),
- '<div>foo</div> <div>foo</div>'
- );
- });
-
- test('does not apply within normal links', () => {
- assert.equal(
- linkifyUrlsAndApplyRewrite('google.com', {
- ogle: {
- match: 'ogle',
- html: '<div>gerritcodereview.com<div>',
- },
- }),
- link('google.com', 'http://google.com')
- );
- });
- });
test('for overlapping rewrites prefer the latest ending', () => {
assert.equal(
@@ -147,14 +86,14 @@
},
foobarbaz: {
match: 'foobarbaz',
- html: '<div>foobarbaz.gov</div>',
+ link: 'foobarbaz.gov',
},
foobar: {
match: 'foobar',
link: 'foobar.gov',
},
}),
- '<div>foobarbaz.gov</div>'
+ link('foobarbaz', 'foobarbaz.gov')
);
});
@@ -167,14 +106,14 @@
},
foobarbaz: {
match: 'foobarbaz',
- html: '<div>FooBarBaz.gov</div>',
+ link: 'FooBarBaz.gov',
},
foobar: {
match: 'barbaz',
link: 'BarBaz.gov',
},
}),
- '<div>FooBarBaz.gov</div>'
+ link('foobarbaz', 'FooBarBaz.gov')
);
});
@@ -183,18 +122,18 @@
linkifyUrlsAndApplyRewrite('foobarbaz', {
foo: {
match: 'foo',
- html: 'FOO',
+ link: 'FOO',
},
oobarba: {
match: 'oobarba',
- html: 'OOBARBA',
+ link: 'OOBARBA',
},
baz: {
match: 'baz',
- html: 'BAZ',
+ link: 'BAZ',
},
}),
- 'FOObarBAZ'
+ `${link('foo', 'FOO')}bar${link('baz', 'BAZ')}`
);
});
@@ -203,18 +142,27 @@
linkifyUrlsAndApplyRewrite('bugs: 123 234 345', {
bug1: {
match: '(bugs:) (\\d+)',
- html: '$1 <div>bug/$2</div>',
+ prefix: '$1 ',
+ link: 'bug/$2',
+ text: 'bug/$2',
},
bug2: {
match: '(bugs:) (\\d+) (\\d+)',
- html: '$1 $2 <div>bug/$3</div>',
+ prefix: '$1 $2 ',
+ link: 'bug/$3',
+ text: 'bug/$3',
},
bug3: {
match: '(bugs:) (\\d+) (\\d+) (\\d+)',
- html: '$1 $2 $3 <div>bug/$4</div>',
+ prefix: '$1 $2 $3 ',
+ link: 'bug/$4',
+ text: 'bug/$4',
},
}),
- 'bugs: <div>bug/123</div> <div>bug/234</div> <div>bug/345</div>'
+ `bugs: ${link('bug/123', 'bug/123')} ${link('bug/234', 'bug/234')} ${link(
+ 'bug/345',
+ 'bug/345'
+ )}`
);
});
diff --git a/proto/cache.proto b/proto/cache.proto
index 9aadf0f..0cecdea 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -535,9 +535,10 @@
// Serialized form of com.google.gerrit.entities.StoredCommentLinkInfo.
// Next ID: 10
message StoredCommentLinkInfoProto {
+ reserved 4; // html
+
string name = 1;
string match = 2;
- string html = 4;
bool enabled = 5;
bool override_only = 6;
string link = 3;
diff --git a/tools/migration/html_to_link_commentlink.md b/tools/migration/html_to_link_commentlink.md
new file mode 100644
index 0000000..45570ac
--- /dev/null
+++ b/tools/migration/html_to_link_commentlink.md
@@ -0,0 +1,47 @@
+# Overview
+
+**Raw html substitution will no longer be an option for comment links.**
+
+The raw-html option for commentlink sections is deprecated and removed.
+Example:
+
+```
+[commentlink "issue b/"]
+ match = (^|\\s)b/(\\d+)
+ html = $1<a href=\"http://b/issue?id=$2&query=$2\" target=\"_blank\">b/$2</a>
+```
+
+Before it allowed to find and replace text matches in commit messages and
+comments with arbitrary html. When misconfigured this has in the past enabled
+injecting undesired html code and XSS attacks by writing a comment.
+
+Even though the sanitization of the resulting html has improved. This feature is
+more powerful than needed. In almost all cases across host configurations html
+is only used to either configure text of the link, or limit the link to wrap
+only a portion of the matched text.
+
+To fill the gap in functionality from deprecating the option additional optional
+parameters (prefix, suffix and text) have been added. They allow to generate
+links that look like:
+```
+ PREFIX<a href="LINK">TEXT</a>SUFFIX
+```
+With substitution being strictly plaintext and all html escaped.
+
+The comment link section in project configs (in refs/meta/config) never
+supported the raw-html option and don't need to be updated.
+
+# Config migration command
+
+```
+CONFIG_FILE=<path to gerrit.config file>
+perl -0pe 's/([ \t]*)html\s*=\s*\"(.*)<a.* href=(?:\\\"(\S+)\\\"|(\S+)(?=\s|>))(?: .*)?>(.*)<\/a>(.*)(?<!\\)\"/$1link = \"$3$4\"\n$1prefix = \"$2\"\n$1text = \"$5\"\n$1suffix = \"$6\"/g' $CONFIG_FILE |
+perl -0pe 's/([ \t]*)html\s*=\s*(\S.*)?<a.* href=(?:\\\"(\S+)\\\"|(\S+)(?=\s|>))(?: .*)?>(.*)<\/a>(.*\S)?/$1link = \"$3$4\"\n$1prefix = \"$2\"\n$1text = \"$5\"\n$1suffix = \"$6\"/g' |
+perl -ne 'print if !/\s*(prefix|suffix|text)\s*=\s*\"\"/'
+```
+
+The command does 3 simple string replace passes:
+
+1. Replace `html=<value>` with quote-escaped value.
+2. Replace `html=<value>` with value without quotes.
+3. Remove empty `prefix`, `suffix`, `text` fields.