Skip to content

Commit

Permalink
Add configuration entry to allow adding additional elements.
Browse files Browse the repository at this point in the history
  • Loading branch information
lhupfeldt committed Jan 7, 2021
1 parent da04c88 commit 0799d58
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 17 deletions.
4 changes: 3 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
</scm>

<properties>
<jenkins.version>2.121.3</jenkins.version>
<!-- <jenkins.version>2.235.1</jenkins.version> -->
<!-- <jenkins.version>2.121.3</jenkins.version> -->
<jenkins.version>2.164</jenkins.version>
<java.level>8</java.level>
<jcasc.version>1.35</jcasc.version>
<hpi.compatibleSinceVersion>2.0</hpi.compatibleSinceVersion>
Expand Down
39 changes: 29 additions & 10 deletions src/main/java/hudson/markup/BasicPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,38 @@
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;

import hudson.markup.ParseAdditionalAllowed;
import hudson.markup.ElementInfo;

public class BasicPolicy {
public static final PolicyFactory POLICY_DEFINITION;
public static final PolicyFactory ADDITIONS = new HtmlPolicyBuilder().allowElements("dl", "dt", "dd", "hr", "pre").toFactory();
public final PolicyFactory POLICY_DEFINITION;

public BasicPolicy(final String additionalAllowed) {
// User defined additional elements and attributes
PolicyFactory USER_ADDITIONS = new HtmlPolicyBuilder().toFactory();
String[] oneElt = new String[1];

public static final PolicyFactory ADDITIONS = new HtmlPolicyBuilder().allowElements("dl", "dt", "dd", "hr", "pre").toFactory();
for (ElementInfo eltInf : new ParseAdditionalAllowed(additionalAllowed)) {
System.out.println("ELTINF:" + eltInf.dump());
oneElt[0] = eltInf.tag;
if (eltInf.attributes.isEmpty()) {
USER_ADDITIONS = USER_ADDITIONS.and(new HtmlPolicyBuilder().allowElements(oneElt).toFactory());
} else {
String[] attributes = new String[eltInf.attributes.size()];
eltInf.attributes.toArray(attributes);
USER_ADDITIONS = USER_ADDITIONS.and(new HtmlPolicyBuilder().allowElements(oneElt).allowAttributes(attributes).onElements(oneElt).toFactory());
}
}

static {
POLICY_DEFINITION = Sanitizers.BLOCKS.
and(Sanitizers.FORMATTING).
and(Sanitizers.IMAGES).
and(Sanitizers.LINKS).
and(Sanitizers.STYLES).
and(Sanitizers.TABLES).
and(ADDITIONS);
// Deafult allowed + all additions
this.POLICY_DEFINITION = Sanitizers.BLOCKS.
and(Sanitizers.FORMATTING).
and(Sanitizers.IMAGES).
and(Sanitizers.LINKS).
and(Sanitizers.STYLES).
and(Sanitizers.TABLES).
and(ADDITIONS).
and(USER_ADDITIONS);
}
}
37 changes: 37 additions & 0 deletions src/main/java/hudson/markup/ElementInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hudson.markup;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class ElementInfo {
public final String tag;
public final List<String> attributes;

public ElementInfo(String tag, List<String> attributes) {
this.tag = tag;
this.attributes = attributes;
}

public ElementInfo(String tag, String[] attributes) {
this.tag = tag;
this.attributes = Arrays.asList(attributes);
}

public ElementInfo(String tag) {
this.tag = tag;
this.attributes = new ArrayList<String>();
}

public boolean equals(ElementInfo other) {
if (other.tag.equals(this.tag) && other.attributes.equals(this.attributes)) {
return true;
}

return false;
}

public String dump() {
return this.toString() + "\n " + tag + "\n " + attributes;
}
}
91 changes: 91 additions & 0 deletions src/main/java/hudson/markup/ParseAdditionalAllowed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package hudson.markup;

import java.util.ArrayList;
import java.text.ParseException;
import java.util.Iterator;

import hudson.markup.ElementInfo;


public class ParseAdditionalAllowed implements Iterable<ElementInfo> {
private ArrayList<ElementInfo> elements = new ArrayList<ElementInfo>();

public static ArrayList<ElementInfo> validateAdditionalAllowed(final String additionalAllowed) throws ParseException {
ArrayList<ElementInfo> elements = new ArrayList<ElementInfo>();
String tag = null;
ArrayList<String> attributes = null;

if (additionalAllowed.trim().equals("")) {
return elements;
}

if (additionalAllowed.endsWith(",")) {
throw new java.text.ParseException("Could not parse: '" + additionalAllowed + "', found trailing ','", 0);
}

for ( String word : additionalAllowed.split("(?!^)\\b") ) {
// System.out.println("word: '" + word + "'");
word = word.trim();

if (word.startsWith(",")) {
if (!word.equals(",")) {
throw new java.text.ParseException("Could not parse: '" + additionalAllowed + "', unexpected '" + word + "'", 0);
}

if (attributes == null ) {
elements.add(new ElementInfo(tag));
// System.out.println(tag + attributes.toString());
tag = null;
}
} else if (word.startsWith("[")) {
if (!word.equals("[") || attributes != null) {
throw new java.text.ParseException("Could not parse: '" + additionalAllowed + "', unexpected '" + word + "'", 0);
}

attributes = new ArrayList<String>();
// System.out.println("Start attr:" + tag + attributes.toString());
} else if (word.startsWith("]")) {
if (!(word.equals("]") || word.equals("],")) || attributes == null) {
throw new java.text.ParseException("Could not parse: '" + additionalAllowed + "', unexpected '" + word + "'", 0);
}

elements.add(new ElementInfo(tag, attributes));
// System.out.println(tag + attributes.toString());
tag = null;
attributes = null;
} else {
// System.out.println("not delim: " + word);
if ( attributes != null ) {
attributes.add(word);
} else {
tag = word;
}
}
}

if (tag != null) {
elements.add(new ElementInfo(tag));
// System.out.println(tag + attributes.toString());
}

if (attributes != null) {
throw new java.text.ParseException("Could not parse: '" + additionalAllowed + "', expected ']'", 0);
}

return elements;
}

public ParseAdditionalAllowed(final String additionalAllowed) {
try {
this.elements = validateAdditionalAllowed(additionalAllowed);
} catch (ParseException ex) {
// This should never happen as validateAdditionalAllowed should have been called by Jenkins
System.err.println("Plugin initialization error:" + ex);
}
}

@Override
public Iterator<ElementInfo> iterator() {
return this.elements.iterator();
}
}
31 changes: 26 additions & 5 deletions src/main/java/hudson/markup/RawHtmlMarkupFormatter.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package hudson.markup;

import com.google.common.base.Throwables;
import hudson.util.FormValidation;
import hudson.Extension;
import hudson.markup.ParseAdditionalAllowed;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.owasp.html.HtmlSanitizer;
import org.owasp.html.HtmlStreamRenderer;

import java.io.IOException;
import java.io.Writer;
import java.text.ParseException;
import java.lang.UnsupportedOperationException;


/**
* {@link MarkupFormatter} that sanitizes HTML, allowing some safe (formatting) HTML.
Expand All @@ -17,18 +23,25 @@
*
*/
public class RawHtmlMarkupFormatter extends MarkupFormatter {

final boolean disableSyntaxHighlighting;
final String additionalAllowed;
private final transient BasicPolicy policy;

@DataBoundConstructor
public RawHtmlMarkupFormatter(final boolean disableSyntaxHighlighting) {
public RawHtmlMarkupFormatter(final boolean disableSyntaxHighlighting, final String additionalAllowed) {
this.disableSyntaxHighlighting = disableSyntaxHighlighting;
this.additionalAllowed = additionalAllowed;
this.policy = new BasicPolicy(additionalAllowed);
}

public boolean isDisableSyntaxHighlighting() {
return disableSyntaxHighlighting;
}

public String getAdditionalAllowed() {
return additionalAllowed;
}

@Override
public void translate(String markup, Writer output) throws IOException {
HtmlStreamRenderer renderer = HtmlStreamRenderer.create(
Expand All @@ -41,7 +54,8 @@ public void translate(String markup, Writer output) throws IOException {
throw new Error(x);
}
);
HtmlSanitizer.sanitize(markup, BasicPolicy.POLICY_DEFINITION.apply(renderer));
// BUG: policy is null here until the config page has been visited
HtmlSanitizer.sanitize(markup, this.policy.POLICY_DEFINITION.apply(renderer));
}

public String getCodeMirrorMode() {
Expand All @@ -58,7 +72,14 @@ public static class DescriptorImpl extends MarkupFormatterDescriptor {
public String getDisplayName() {
return "Safe HTML";
}
}

public static final MarkupFormatter INSTANCE = new RawHtmlMarkupFormatter(false);
public FormValidation doCheckAdditionalAllowed(@QueryParameter String value) throws IOException {
try {
ParseAdditionalAllowed.validateAdditionalAllowed(value);
return FormValidation.ok();
} catch (ParseException ex) {
return FormValidation.error(ex.toString());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@
<f:entry field="disableSyntaxHighlighting">
<f:checkbox title="${%disableSyntaxHighlighting}"/>
</f:entry>
<f:entry field="additionalAllowed" title="${%additionalAllowed}">
<f:textbox/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
blurb=Treats the text as HTML and sanitizes it, removing potentially unsafe elements like <code>&lt;script&gt;</code>.
disableSyntaxHighlighting=Disable syntax highlighting
additionalAllowed=Allow additional elements. The format is "tag1,tag2[attribute1,attribute2,...],tag3...", e.g. "span[title],font[color]"
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
# THE SOFTWARE.

disableSyntaxHighlighting=Sl\u00E5 syntaks markering fra
additionalAllowed=Tillad yderligere elementer. Formatet er "tag1,tag2[attribute1,attribute2,...],tag3...", f.eks. "span[title],font[color]"
20 changes: 19 additions & 1 deletion src/test/java/hudson/markup/BasicPolicyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void testPolicy() {
assertReject("link", "<link rel='stylesheet' type='text/css' href='http://www.microsoft.com/'>");
assertIntact("<div style='background-color:white'>inline CSS</div>");
assertIntact("<br /><hr />");
assertIntact("<span title='World'>Hello</span>", "span[title]");

assertReject("sun.com", "<form method='post' action='http://sun.com/'><input type='text' name='foo'><input type='password' name='pass'></form>");
}
Expand All @@ -62,6 +63,11 @@ private void assertIntact(String input) {
input = input.replace('\'','\"');
assertSanitize(input,input);
}

private void assertIntact(String input, String additionalAllowed) {
input = input.replace('\'','\"');
assertSanitize(input, input, additionalAllowed);
}

private void assertReject(String problematic, String input) {
String out = sanitize(input);
Expand All @@ -72,9 +78,21 @@ private void assertSanitize(String expected, String input) {
assertEquals(expected.replace('\'','\"'),sanitize(input));
}

private void assertSanitize(String expected, String input, String additionalAllowed) {
assertEquals(expected.replace('\'','\"'), sanitize(input, additionalAllowed));
}

private String sanitize(String input) {
try {
return new RawHtmlMarkupFormatter(false).translate(input);
return new RawHtmlMarkupFormatter(false, "").translate(input);
} catch (IOException ex) {
throw new AssertionError(ex);
}
}

private String sanitize(String input, String additionalAllowed) {
try {
return new RawHtmlMarkupFormatter(false, additionalAllowed).translate(input);
} catch (IOException ex) {
throw new AssertionError(ex);
}
Expand Down
Loading

0 comments on commit 0799d58

Please sign in to comment.