Skip to content

Commit ca99246

Browse files
authored
Merge pull request #763 from HubSpot/b64-encode
Add Base 64 encode and decode filters
2 parents 10899fd + b74c4e0 commit ca99246

File tree

5 files changed

+252
-2
lines changed

5 files changed

+252
-2
lines changed

src/main/java/com/hubspot/jinjava/el/ExpressionResolver.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ public Object resolveExpression(String expression) {
196196
TemplateError.fromException(
197197
new TemplateSyntaxException(
198198
expression,
199-
StringUtils.endsWith(originatingException, e.getMessage())
199+
(
200+
e.getCause() == null ||
201+
StringUtils.endsWith(originatingException, e.getCause().getMessage())
202+
)
200203
? e.getMessage()
201204
: combinedMessage,
202205
interpreter.getLineNumber(),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.hubspot.jinjava.lib.filter;
2+
3+
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
4+
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
5+
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
6+
import com.hubspot.jinjava.interpret.InvalidArgumentException;
7+
import com.hubspot.jinjava.interpret.InvalidReason;
8+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
9+
import java.nio.charset.Charset;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Base64;
12+
13+
@JinjavaDoc(
14+
value = "Decode a base 64 input into a string.",
15+
input = @JinjavaParam(
16+
value = "input",
17+
type = "string",
18+
desc = "The base 64 input to decode.",
19+
required = true
20+
),
21+
params = {
22+
@JinjavaParam(
23+
value = "encoding",
24+
type = "string",
25+
desc = "The string encoding charset to use.",
26+
defaultValue = "UTF-8"
27+
)
28+
},
29+
snippets = {
30+
@JinjavaSnippet(
31+
desc = "Decode a Base 64-encoded ASCII string into a UTF-8 string",
32+
code = "{{ 'eydmb28nOiBbJ2JhciddfQ=='|b64decode }}"
33+
),
34+
@JinjavaSnippet(
35+
desc = "Decode a Base 64-encoded ASCII string into a UTF-16 Little Endian string",
36+
code = "{{ 'Adg33A=='|b64decode(encoding='utf-16le') }}"
37+
)
38+
}
39+
)
40+
public class Base64DecodeFilter implements Filter {
41+
public static final String NAME = "b64decode";
42+
43+
@Override
44+
public String getName() {
45+
return NAME;
46+
}
47+
48+
@Override
49+
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
50+
if (!(var instanceof String)) {
51+
throw new InvalidArgumentException(interpreter, this, InvalidReason.STRING, 0, var);
52+
}
53+
Charset charset = Base64EncodeFilter.checkCharset(interpreter, this, args);
54+
return new String(
55+
Base64.getDecoder().decode((var.toString()).getBytes(StandardCharsets.US_ASCII)),
56+
charset
57+
);
58+
}
59+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.hubspot.jinjava.lib.filter;
2+
3+
import com.google.common.base.Joiner;
4+
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
5+
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
6+
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
7+
import com.hubspot.jinjava.interpret.InvalidArgumentException;
8+
import com.hubspot.jinjava.interpret.InvalidReason;
9+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
10+
import com.hubspot.jinjava.lib.Importable;
11+
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
12+
import java.nio.charset.Charset;
13+
import java.nio.charset.StandardCharsets;
14+
import java.nio.charset.UnsupportedCharsetException;
15+
import java.util.Base64;
16+
import org.apache.commons.lang3.StringUtils;
17+
import org.apache.commons.net.util.Charsets;
18+
19+
@JinjavaDoc(
20+
value = "Encode the string input into base 64.",
21+
input = @JinjavaParam(
22+
value = "input",
23+
type = "object",
24+
desc = "The string input to encode into base 64.",
25+
required = true
26+
),
27+
params = {
28+
@JinjavaParam(
29+
value = "encoding",
30+
type = "string",
31+
desc = "The string encoding charset to use.",
32+
defaultValue = "UTF-8"
33+
)
34+
},
35+
snippets = {
36+
@JinjavaSnippet(
37+
desc = "Encode a value with UTF-8 encoding into a Base 64 ASCII string",
38+
code = "{{ 'abcd'|b64encode }}"
39+
),
40+
@JinjavaSnippet(
41+
desc = "Encode a value with UTF-16 Little Endian encoding into a Base 64 ASCII string",
42+
code = "{{ '\uD801\uDC37'|b64encode(encoding='utf-16le') }}"
43+
)
44+
}
45+
)
46+
public class Base64EncodeFilter implements Filter {
47+
public static final String NAME = "b64encode";
48+
public static final String AVAILABLE_CHARSETS = Joiner
49+
.on(", ")
50+
.join(
51+
StandardCharsets.US_ASCII.name(),
52+
StandardCharsets.ISO_8859_1.name(),
53+
StandardCharsets.UTF_8.name(),
54+
StandardCharsets.UTF_16BE.name(),
55+
StandardCharsets.UTF_16LE.name(),
56+
StandardCharsets.UTF_16.name()
57+
);
58+
59+
static Charset checkCharset(
60+
JinjavaInterpreter interpreter,
61+
Importable filter,
62+
String... args
63+
) {
64+
Charset charset = StandardCharsets.UTF_8;
65+
if (args.length > 0) {
66+
try {
67+
charset = Charsets.toCharset(StringUtils.upperCase(args[0]));
68+
} catch (UnsupportedCharsetException e) {
69+
throw new InvalidArgumentException(
70+
interpreter,
71+
filter,
72+
InvalidReason.ENUM,
73+
1,
74+
args[0],
75+
AVAILABLE_CHARSETS
76+
);
77+
}
78+
}
79+
return charset;
80+
}
81+
82+
@Override
83+
public String getName() {
84+
return NAME;
85+
}
86+
87+
@Override
88+
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
89+
Charset charset = Base64EncodeFilter.checkCharset(interpreter, this, args);
90+
byte[] bytes;
91+
if (var instanceof byte[]) {
92+
bytes = (byte[]) var;
93+
} else {
94+
bytes = PyishObjectMapper.getAsUnquotedPyishString(var).getBytes(charset);
95+
}
96+
return Base64.getEncoder().encodeToString(bytes);
97+
}
98+
}

src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ protected void registerDefaults() {
112112
FromJsonFilter.class,
113113
ToYamlFilter.class,
114114
FromYamlFilter.class,
115-
RenderFilter.class
115+
RenderFilter.class,
116+
Base64EncodeFilter.class,
117+
Base64DecodeFilter.class
116118
);
117119
}
118120

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.hubspot.jinjava.lib.filter;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.hubspot.jinjava.BaseJinjavaTest;
6+
import com.hubspot.jinjava.interpret.InvalidArgumentException;
7+
import com.hubspot.jinjava.interpret.RenderResult;
8+
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
9+
import java.util.Collections;
10+
import org.junit.Test;
11+
12+
public class Base64FilterTest extends BaseJinjavaTest {
13+
14+
@Test
15+
public void itEncodesWithDefaultCharset() {
16+
assertThat(jinjava.render("{{ 'ß'|b64encode }}", Collections.emptyMap()))
17+
.isEqualTo("w58=");
18+
}
19+
20+
@Test
21+
public void itEncodesWithUtf16Le() {
22+
assertThat(
23+
jinjava.render(
24+
"{{ '\uD801\uDC37'|b64encode(encoding='utf-16le') }}",
25+
Collections.emptyMap()
26+
)
27+
)
28+
.isEqualTo("Adg33A==");
29+
}
30+
31+
@Test
32+
public void itDecodesWithUtf16Le() {
33+
assertThat(
34+
jinjava.render(
35+
"{{ 'Adg33A=='|b64decode(encoding='utf-16le') }}",
36+
Collections.emptyMap()
37+
)
38+
)
39+
.isEqualTo("\uD801\uDC37");
40+
}
41+
42+
@Test
43+
public void itEncodesAndDecodesDefaultCharset() {
44+
assertThat(
45+
jinjava.render("{{ 123456789|b64encode|b64decode }}", Collections.emptyMap())
46+
)
47+
.isEqualTo("123456789");
48+
}
49+
50+
@Test
51+
public void itEncodesAndDecodesUtf16Le() {
52+
assertThat(
53+
jinjava.render(
54+
"{{ 123456789|b64encode(encoding='utf-16le')|b64decode(encoding='utf-16le') }}",
55+
Collections.emptyMap()
56+
)
57+
)
58+
.isEqualTo("123456789");
59+
}
60+
61+
@Test
62+
public void itEncodesObject() {
63+
assertThat(jinjava.render("{{ {'foo': ['bar']}|b64encode }}", Collections.emptyMap()))
64+
.isEqualTo("eydmb28nOiBbJ2JhciddfQ==");
65+
}
66+
67+
@Test
68+
public void itHandlesInvalidDecode() {
69+
RenderResult renderResult = jinjava.renderForResult(
70+
"{{ 'ß'|b64decode }}",
71+
Collections.emptyMap()
72+
);
73+
assertThat(renderResult.getErrors()).hasSize(1);
74+
assertThat(renderResult.getErrors().get(0).getException())
75+
.isInstanceOf(TemplateSyntaxException.class);
76+
}
77+
78+
@Test
79+
public void itThrowsErrorForNonStringDecode() {
80+
RenderResult renderResult = jinjava.renderForResult(
81+
"{{ 123|b64decode }}",
82+
Collections.emptyMap()
83+
);
84+
assertThat(renderResult.getErrors()).hasSize(1);
85+
assertThat(renderResult.getErrors().get(0).getException())
86+
.isInstanceOf(InvalidArgumentException.class);
87+
}
88+
}

0 commit comments

Comments
 (0)