Skip to content

Commit ce4e1bd

Browse files
committed
Escape HTML tags in issue titles
Closes gh-80
1 parent 4a3c56b commit ce4e1bd

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

src/main/java/io/spring/githubchangeloggenerator/ChangelogGenerator.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.FileWriter;
2121
import java.io.IOException;
2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.Collections;
2425
import java.util.Comparator;
2526
import java.util.List;
@@ -55,7 +56,7 @@ public class ChangelogGenerator {
5556
private static final Comparator<Issue> TITLE_COMPARATOR = Comparator.comparing(Issue::getTitle,
5657
String.CASE_INSENSITIVE_ORDER);
5758

58-
private static final Pattern ghUserMentionPattern = Pattern.compile("(^|[^\\w`])(@[\\w-]+)");
59+
private static final List<Escape> escapes = Arrays.asList(gitHubUserMentions(), htmlTags());
5960

6061
private final GitHubService service;
6162

@@ -160,7 +161,9 @@ private void sort(IssueSort sort, List<Issue> issues) {
160161

161162
private String getFormattedIssue(Issue issue) {
162163
String title = issue.getTitle();
163-
title = ghUserMentionPattern.matcher(title).replaceAll("$1`$2`");
164+
for (Escape escape : escapes) {
165+
title = escape.apply(title);
166+
}
164167
return String.format("- %s %s%n", title, getLinkToIssue(issue));
165168
}
166169

@@ -236,4 +239,36 @@ private void writeContentToFile(String content, String path) throws IOException
236239
FileCopyUtils.copy(content, new FileWriter(new File(path)));
237240
}
238241

242+
private static Escape gitHubUserMentions() {
243+
return new PatternEscape(Pattern.compile("(^|[^\\w`])(@[\\w-]+)"), "$1`$2`");
244+
}
245+
246+
private static Escape htmlTags() {
247+
return new PatternEscape(Pattern.compile("(^|[^\\w`])(<[\\w\\-/<>]+>)"), "$1`$2`");
248+
}
249+
250+
private interface Escape {
251+
252+
String apply(String input);
253+
254+
}
255+
256+
private static final class PatternEscape implements Escape {
257+
258+
private final Pattern pattern;
259+
260+
private final String replacement;
261+
262+
private PatternEscape(Pattern pattern, String replacement) {
263+
this.pattern = pattern;
264+
this.replacement = replacement;
265+
}
266+
267+
@Override
268+
public String apply(String input) {
269+
return this.pattern.matcher(input).replaceAll(this.replacement);
270+
}
271+
272+
}
273+
239274
}

src/test/java/io/spring/githubchangeloggenerator/ChangelogGeneratorTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,32 @@ void generateWhenEscapedUserMentionIsInIssueTitleItIsNotEscapedAgain() throws IO
236236
assertThat(new String(Files.readAllBytes(file))).contains("Bug 1 for `@Value`");
237237
}
238238

239+
@Test
240+
void generateWhenHtmlTagIsInIssueTitleItIsEscaped() throws IOException {
241+
setupGenerator(MilestoneReference.TITLE);
242+
List<Issue> issues = new ArrayList<>();
243+
issues.add(newIssue("Bug 1 for <td>", "1", "bug-1-url", Type.BUG));
244+
issues.add(newIssue("Bug 2 for <td/>", "2", "bug-2-url", Type.BUG));
245+
issues.add(newIssue("Bug 3 for <td></td>", "3", "bug-3-url", Type.BUG));
246+
given(this.service.getMilestoneNumber("v2.3", REPO)).willReturn(23);
247+
given(this.service.getIssuesForMilestone(23, REPO)).willReturn(issues);
248+
Path file = generateChangelog("v2.3");
249+
assertThat(new String(Files.readAllBytes(file))).contains("Bug 1 for `<td>`");
250+
assertThat(new String(Files.readAllBytes(file))).contains("Bug 2 for `<td/>`");
251+
assertThat(new String(Files.readAllBytes(file))).contains("Bug 3 for `<td></td>`");
252+
}
253+
254+
@Test
255+
void generateWhenEscapedHtmlTagIsInIssueTitleItIsNotEscapedAgain() throws IOException {
256+
setupGenerator(MilestoneReference.TITLE);
257+
List<Issue> issues = new ArrayList<>();
258+
issues.add(newIssue("Bug 1 for `<td>`", "1", "bug-1-url", Type.BUG));
259+
given(this.service.getMilestoneNumber("v2.3", REPO)).willReturn(23);
260+
given(this.service.getIssuesForMilestone(23, REPO)).willReturn(issues);
261+
Path file = generateChangelog("v2.3");
262+
assertThat(new String(Files.readAllBytes(file))).contains("Bug 1 for `<td>`");
263+
}
264+
239265
@Test
240266
void generateWhenSectionSortedByTitle() throws Exception {
241267
List<Section> sections = new ArrayList<>();

0 commit comments

Comments
 (0)