Skip to content

Commit d1aaca2

Browse files
authored
Add escapeHtml parameter to all DataBoundTokenMacro implementations (#118)
1 parent 361b095 commit d1aaca2

File tree

11 files changed

+206
-14
lines changed

11 files changed

+206
-14
lines changed

src/main/java/org/jenkinsci/plugins/tokenmacro/DataBoundTokenMacro.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
package org.jenkinsci.plugins.tokenmacro;
2525

2626
import com.google.common.collect.ListMultimap;
27-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2827
import hudson.FilePath;
2928
import hudson.model.AbstractBuild;
3029
import hudson.model.Run;
@@ -45,6 +44,8 @@
4544

4645
import static java.lang.annotation.ElementType.*;
4746
import static java.lang.annotation.RetentionPolicy.*;
47+
48+
import org.apache.commons.lang.StringEscapeUtils;
4849
import org.apache.commons.lang.StringUtils;
4950

5051
/**
@@ -85,6 +86,8 @@ private interface Setter {
8586
}
8687

8788
private Map<String,Setter> setters;
89+
@Parameter
90+
public boolean escapeHtml = false;
8891

8992
public DataBoundTokenMacro() {
9093
buildMap();
@@ -199,13 +202,36 @@ private DataBoundTokenMacro prepare(String macroName, Map<String, String> argume
199202
@Override
200203
public String evaluate(AbstractBuild<?, ?> build, TaskListener listener, String macroName, Map<String, String> arguments, ListMultimap<String, String> argumentMultimap) throws MacroEvaluationException, IOException, InterruptedException {
201204
DataBoundTokenMacro copy = prepare(macroName,arguments,argumentMultimap);
202-
return copy.evaluate(build,listener,macroName);
205+
String res = copy.evaluate(build, listener, macroName);
206+
if (copy.escapeHtml && !copy.handlesHtmlEscapeInternally()) {
207+
res = StringEscapeUtils.escapeHtml(res);
208+
}
209+
return res;
203210
}
204211

205212
@Override
206213
public String evaluate(Run<?,?> run, FilePath workspace, TaskListener listener, String macroName, Map<String, String> arguments, ListMultimap<String, String> argumentMultimap) throws MacroEvaluationException, IOException, InterruptedException {
207214
DataBoundTokenMacro copy = prepare(macroName,arguments,argumentMultimap);
208-
return copy.evaluate(run, workspace, listener, macroName);
215+
String res = copy.evaluate(run, workspace, listener, macroName);
216+
if (copy.escapeHtml && !copy.handlesHtmlEscapeInternally()) {
217+
res = StringEscapeUtils.escapeHtml(res);
218+
}
219+
return res;
220+
}
221+
222+
/**
223+
* Indicates whether this macro handles {@link #escapeHtml} on its own inside the <code>evaluate</code> methods.
224+
*
225+
* If this method returns <code>false</code> and {@link #escapeHtml} is <code>true</code> then the returned value from
226+
* {@link #evaluate(AbstractBuild, TaskListener, String)} and {@link #evaluate(Run, FilePath, TaskListener, String)}
227+
* will be escaped. If this method returns <code>true</code> no escaping will be performed,
228+
* and it is assumed the escaping will be handled internally by the implementing class. It is then also assumed that
229+
* the <code>help.jelly</code> file for that class mentions the {@link #escapeHtml} parameter.
230+
*
231+
* @return true if the implementing class handles its own html escaping.
232+
*/
233+
public boolean handlesHtmlEscapeInternally() {
234+
return false;
209235
}
210236

211237
public abstract String evaluate(AbstractBuild<?, ?> context, TaskListener listener, String macroName) throws MacroEvaluationException, IOException, InterruptedException;

src/main/java/org/jenkinsci/plugins/tokenmacro/impl/BuildLogMacro.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import hudson.model.Run;
77
import hudson.model.TaskListener;
88
import org.apache.commons.lang.StringEscapeUtils;
9-
import org.apache.tools.ant.taskdefs.Parallel;
109
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro;
1110
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
1211

@@ -32,9 +31,6 @@ public class BuildLogMacro extends DataBoundTokenMacro {
3231
@Parameter
3332
public int truncTailLines = 0;
3433

35-
@Parameter
36-
public boolean escapeHtml = false;
37-
3834
@Parameter
3935
public int maxLineLength = MAX_LINE_LENGTH_DEFAULT_VALUE;
4036

@@ -85,4 +81,9 @@ public String evaluate(Run<?,?> run, FilePath workspace, TaskListener listener,
8581

8682
return buffer.toString();
8783
}
84+
85+
@Override
86+
public boolean handlesHtmlEscapeInternally() {
87+
return true;
88+
}
8889
}

src/main/java/org/jenkinsci/plugins/tokenmacro/impl/BuildLogMultilineRegexMacro.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
import hudson.model.AbstractBuild;
77
import hudson.model.Run;
88
import hudson.model.TaskListener;
9-
import org.apache.commons.lang.StringEscapeUtils;
109

1110
import java.io.BufferedReader;
1211
import java.io.IOException;
1312
import java.util.Collections;
1413
import java.util.List;
1514
import java.util.regex.Matcher;
1615
import java.util.regex.Pattern;
16+
17+
import org.apache.commons.lang.StringEscapeUtils;
1718
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro;
1819
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
1920

@@ -40,8 +41,6 @@ public class BuildLogMultilineRegexMacro extends DataBoundTokenMacro {
4041
@Parameter
4142
public String substText = null; // insert entire segment
4243
@Parameter
43-
public boolean escapeHtml = false;
44-
@Parameter
4544
public String matchedSegmentHtmlStyle = null;
4645

4746
private static final Pattern LINE_TERMINATOR_PATTERN = Pattern.compile("(?<=.)\\r?\\n");
@@ -209,5 +208,10 @@ private int countLineTerminators(CharSequence charSequence) {
209208
}
210209
return lineTerminatorCount;
211210
}
211+
212+
@Override
213+
public boolean handlesHtmlEscapeInternally() {
214+
return true;
215+
}
212216
}
213217

src/main/java/org/jenkinsci/plugins/tokenmacro/impl/BuildLogRegexMacro.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ public class BuildLogRegexMacro extends DataBoundTokenMacro {
5353
@Parameter
5454
public String substText = null; // insert entire line
5555
@Parameter
56-
public boolean escapeHtml = false;
57-
@Parameter
5856
public String matchedLineHtmlStyle = null;
5957
@Parameter
6058
public boolean addNewline = true;
@@ -323,4 +321,9 @@ private static class Pair<K,V> extends AbstractMap.SimpleEntry<K,V>
323321
super(key, val);
324322
}
325323
}
324+
325+
@Override
326+
public boolean handlesHtmlEscapeInternally() {
327+
return true;
328+
}
326329
}

src/main/resources/lib/token-macro/help.groovy

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro
2+
13
st = namespace("jelly:stapler")
24

35

46
org.jenkinsci.plugins.tokenmacro.TokenMacro.all().each { tm ->
57
st.include(it:tm, page: "help", optional: true)
8+
if (tm instanceof DataBoundTokenMacro && !tm.handlesHtmlEscapeInternally()) {
9+
dd() {
10+
dl() {
11+
dt("escapeHtml")
12+
dd(_("If true, any HTML specific code will be escaped. Defaults to false."))
13+
}
14+
}
15+
}
616
br()
717
}
818

src/main/resources/org/jenkinsci/plugins/tokenmacro/impl/AbstractChangesSinceMacro/help.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ dd {
2323
}
2424
}
2525
span("Following Parameters are also supported: " +
26-
"showPaths, pathFormat, showDependencies, dateFormat, regex, replace, default. " +
26+
"showPaths, pathFormat, showDependencies, dateFormat, regex, replace, default, escapeHtml. " +
2727
"See \${CHANGES_SINCE_LAST_BUILD} details.")
2828
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core">
3+
<dt>$${JSON,file="FILE",path="JSON/PATH",expr="EXPRESSION"}</dt>
4+
<dd>
5+
Expands to the result(s) of a JSON path or expression run against the given JSON file.<br/>
6+
If the path/expr evaluates to more than one value, then a semicolon-separated string is returned.<br/>
7+
The file path is relative to the build workspace root.
8+
</dd>
9+
</j:jelly>

src/test/java/org/jenkinsci/plugins/tokenmacro/impl/BuildLogRegexMacroTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jenkinsci.plugins.tokenmacro.impl;
22

3+
import com.google.common.collect.ArrayListMultimap;
34
import hudson.console.ConsoleNote;
45
import hudson.model.AbstractBuild;
56
import hudson.model.TaskListener;
@@ -11,6 +12,7 @@
1112

1213
import java.io.InputStreamReader;
1314
import java.io.StringReader;
15+
import java.util.Collections;
1416
import java.util.Map;
1517
import java.util.HashMap;
1618

@@ -436,6 +438,24 @@ public void testGetContent_matchedLineHtmlStyle()
436438
assertEquals("<pre>\n<b style=\"color: red\">error</b>\n</pre>\n", result);
437439
}
438440

441+
@Test
442+
public void testGetContent_matchedLineHtmlStyleWithHtmlEscape()
443+
throws Exception {
444+
when(build.getLogReader()).thenReturn(
445+
new StringReader("<error>"));
446+
final Map<String, String> arguments = new HashMap<>();
447+
arguments.put("escapeHtml", "true");
448+
arguments.put("showTruncatedLines", "false");
449+
arguments.put("matchedLineHtmlStyle", "color: red");
450+
final ArrayListMultimap<String, String> listMultimap = ArrayListMultimap.create();
451+
listMultimap.put("escapeHtml", "true");
452+
listMultimap.put("showTruncatedLines", "false");
453+
listMultimap.put("matchedLineHtmlStyle", "color: red");
454+
final String result = buildLogRegexMacro.evaluate(build, null, listener, BuildLogRegexMacro.MACRO_NAME, arguments, listMultimap);
455+
456+
assertEquals("<pre>\n<b style=\"color: red\">&lt;error&gt;</b>\n</pre>\n", result);
457+
}
458+
439459
@Test
440460
public void testGetContent_shouldStripOutConsoleNotes()
441461
throws Exception {

src/test/java/org/jenkinsci/plugins/tokenmacro/impl/ChangesSinceLastBuildMacroTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jenkinsci.plugins.tokenmacro.impl;
22

3+
import com.google.common.collect.ArrayListMultimap;
34
import hudson.model.AbstractBuild;
45
import hudson.model.Result;
56
import hudson.model.TaskListener;
@@ -17,10 +18,12 @@
1718
import java.util.LinkedList;
1819
import java.util.List;
1920
import java.util.Locale;
21+
import java.util.Map;
2022
import java.util.TimeZone;
2123

2224
import static junit.framework.Assert.assertEquals;
2325
import static junit.framework.Assert.assertTrue;
26+
import static org.mockito.ArgumentMatchers.anyInt;
2427
import static org.mockito.Mockito.mock;
2528
import static org.mockito.Mockito.when;
2629

@@ -185,6 +188,29 @@ public void testShouldPrintMessageWhenNoChanges()
185188
assertEquals("another default message\n", content);
186189
}
187190

191+
@Test
192+
public void testShouldDefaultToNotEscapeHtml()
193+
throws Exception {
194+
AbstractBuild currentBuild = createBuild(Result.SUCCESS, 42, "<b>bold</b>");
195+
196+
String content = changesSinceLastBuildMacro.evaluate(currentBuild, listener, ChangesSinceLastBuildMacro.MACRO_NAME);
197+
198+
assertEquals("[Ash Lux] <b>bold</b>\n\n", content);
199+
}
200+
201+
@Test
202+
public void testShouldEscapeHtmlWhenArgumentEscapeHtmlSetToTrue()
203+
throws Exception {
204+
AbstractBuild currentBuild = createBuild(Result.SUCCESS, 42, "<b>bold</b>");
205+
206+
final Map<String, String> arguments = Collections.singletonMap("escapeHtml", "true");
207+
final ArrayListMultimap<String, String> listMultimap = ArrayListMultimap.create();
208+
listMultimap.put("escapeHtml", "true");
209+
String content = changesSinceLastBuildMacro.evaluate(currentBuild, null, listener, ChangesSinceLastBuildMacro.MACRO_NAME, arguments, listMultimap);
210+
211+
assertEquals("[Ash Lux] &lt;b&gt;bold&lt;/b&gt;\n\n", content);
212+
}
213+
188214
private AbstractBuild createBuild(Result result, int buildNumber, String message) {
189215
AbstractBuild build = mock(AbstractBuild.class);
190216
when(build.getResult()).thenReturn(result);

src/test/java/org/jenkinsci/plugins/tokenmacro/impl/ChangesSinceLastSuccessfulBuildMacroTest.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jenkinsci.plugins.tokenmacro.impl;
22

3+
import com.google.common.collect.ArrayListMultimap;
34
import hudson.model.AbstractBuild;
45
import hudson.model.Result;
56
import hudson.model.TaskListener;
@@ -16,6 +17,7 @@
1617
import java.util.LinkedList;
1718
import java.util.List;
1819
import java.util.Locale;
20+
import java.util.Map;
1921
import java.util.TimeZone;
2022
import static junit.framework.Assert.assertEquals;
2123

@@ -278,7 +280,7 @@ public void testShouldPrintMessageWhenNoChanges()
278280

279281
String contentStr = content.evaluate(currentBuild, listener, ChangesSinceLastSuccessfulBuildMacro.MACRO_NAME);
280282

281-
Assert.assertEquals("Changes for Build #41\n"
283+
assertEquals("Changes for Build #41\n"
282284
+ "[Ash Lux] [DEFECT-666] Changes for a failed build.\n"
283285
+ "\n"
284286
+ "\n"
@@ -287,6 +289,49 @@ public void testShouldPrintMessageWhenNoChanges()
287289
+ "\n", contentStr);
288290
}
289291

292+
@Test
293+
public void testShouldDefaultToNotEscapeHtml()
294+
throws Exception {
295+
AbstractBuild failureBuild = createBuild(Result.FAILURE, 41, "[DEFECT-666] Changes for a failed build. <b>bold</b>");
296+
297+
AbstractBuild currentBuild = createBuildWithNoChanges(Result.SUCCESS, 42);
298+
when(currentBuild.getPreviousBuild()).thenReturn(failureBuild);
299+
when(failureBuild.getNextBuild()).thenReturn(currentBuild);
300+
301+
String contentStr = content.evaluate(currentBuild, listener, ChangesSinceLastSuccessfulBuildMacro.MACRO_NAME);
302+
303+
Assert.assertEquals("Changes for Build #41\n"
304+
+ "[Ash Lux] [DEFECT-666] Changes for a failed build. <b>bold</b>\n"
305+
+ "\n"
306+
+ "\n"
307+
+ "Changes for Build #42\n"
308+
+ "No changes\n"
309+
+ "\n", contentStr);
310+
}
311+
312+
@Test
313+
public void testShouldEscapeHtmlWhenArgumentEscapeHtmlSetToTrue()
314+
throws Exception {
315+
AbstractBuild failureBuild = createBuild(Result.FAILURE, 41, "[DEFECT-666] Changes for a failed build. <b>bold</b>");
316+
317+
AbstractBuild currentBuild = createBuildWithNoChanges(Result.SUCCESS, 42);
318+
when(currentBuild.getPreviousBuild()).thenReturn(failureBuild);
319+
when(failureBuild.getNextBuild()).thenReturn(currentBuild);
320+
321+
final Map<String, String> arguments = Collections.singletonMap("escapeHtml", "true");
322+
final ArrayListMultimap<String, String> listMultimap = ArrayListMultimap.create();
323+
listMultimap.put("escapeHtml", "true");
324+
String contentStr = content.evaluate(currentBuild, null, listener, ChangesSinceLastSuccessfulBuildMacro.MACRO_NAME, arguments, listMultimap);
325+
326+
assertEquals("Changes for Build #41\n"
327+
+ "[Ash Lux] [DEFECT-666] Changes for a failed build. &lt;b&gt;bold&lt;/b&gt;\n"
328+
+ "\n"
329+
+ "\n"
330+
+ "Changes for Build #42\n"
331+
+ "No changes\n"
332+
+ "\n", contentStr);
333+
}
334+
290335
private AbstractBuild createBuildWithNoChanges(Result result, int buildNumber) {
291336
AbstractBuild build = mock(AbstractBuild.class);
292337
when(build.getResult()).thenReturn(result);

0 commit comments

Comments
 (0)