Skip to content

Commit

Permalink
Merge pull request #763 from HubSpot/b64-encode
Browse files Browse the repository at this point in the history
Add Base 64 encode and decode filters
  • Loading branch information
jasmith-hs authored Oct 6, 2021
2 parents 10899fd + b74c4e0 commit ca99246
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/main/java/com/hubspot/jinjava/el/ExpressionResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ public Object resolveExpression(String expression) {
TemplateError.fromException(
new TemplateSyntaxException(
expression,
StringUtils.endsWith(originatingException, e.getMessage())
(
e.getCause() == null ||
StringUtils.endsWith(originatingException, e.getCause().getMessage())
)
? e.getMessage()
: combinedMessage,
interpreter.getLineNumber(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.hubspot.jinjava.lib.filter;

import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.InvalidReason;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@JinjavaDoc(
value = "Decode a base 64 input into a string.",
input = @JinjavaParam(
value = "input",
type = "string",
desc = "The base 64 input to decode.",
required = true
),
params = {
@JinjavaParam(
value = "encoding",
type = "string",
desc = "The string encoding charset to use.",
defaultValue = "UTF-8"
)
},
snippets = {
@JinjavaSnippet(
desc = "Decode a Base 64-encoded ASCII string into a UTF-8 string",
code = "{{ 'eydmb28nOiBbJ2JhciddfQ=='|b64decode }}"
),
@JinjavaSnippet(
desc = "Decode a Base 64-encoded ASCII string into a UTF-16 Little Endian string",
code = "{{ 'Adg33A=='|b64decode(encoding='utf-16le') }}"
)
}
)
public class Base64DecodeFilter implements Filter {
public static final String NAME = "b64decode";

@Override
public String getName() {
return NAME;
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
if (!(var instanceof String)) {
throw new InvalidArgumentException(interpreter, this, InvalidReason.STRING, 0, var);
}
Charset charset = Base64EncodeFilter.checkCharset(interpreter, this, args);
return new String(
Base64.getDecoder().decode((var.toString()).getBytes(StandardCharsets.US_ASCII)),
charset
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.hubspot.jinjava.lib.filter;

import com.google.common.base.Joiner;
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.InvalidReason;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.lib.Importable;
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.util.Charsets;

@JinjavaDoc(
value = "Encode the string input into base 64.",
input = @JinjavaParam(
value = "input",
type = "object",
desc = "The string input to encode into base 64.",
required = true
),
params = {
@JinjavaParam(
value = "encoding",
type = "string",
desc = "The string encoding charset to use.",
defaultValue = "UTF-8"
)
},
snippets = {
@JinjavaSnippet(
desc = "Encode a value with UTF-8 encoding into a Base 64 ASCII string",
code = "{{ 'abcd'|b64encode }}"
),
@JinjavaSnippet(
desc = "Encode a value with UTF-16 Little Endian encoding into a Base 64 ASCII string",
code = "{{ '\uD801\uDC37'|b64encode(encoding='utf-16le') }}"
)
}
)
public class Base64EncodeFilter implements Filter {
public static final String NAME = "b64encode";
public static final String AVAILABLE_CHARSETS = Joiner
.on(", ")
.join(
StandardCharsets.US_ASCII.name(),
StandardCharsets.ISO_8859_1.name(),
StandardCharsets.UTF_8.name(),
StandardCharsets.UTF_16BE.name(),
StandardCharsets.UTF_16LE.name(),
StandardCharsets.UTF_16.name()
);

static Charset checkCharset(
JinjavaInterpreter interpreter,
Importable filter,
String... args
) {
Charset charset = StandardCharsets.UTF_8;
if (args.length > 0) {
try {
charset = Charsets.toCharset(StringUtils.upperCase(args[0]));
} catch (UnsupportedCharsetException e) {
throw new InvalidArgumentException(
interpreter,
filter,
InvalidReason.ENUM,
1,
args[0],
AVAILABLE_CHARSETS
);
}
}
return charset;
}

@Override
public String getName() {
return NAME;
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
Charset charset = Base64EncodeFilter.checkCharset(interpreter, this, args);
byte[] bytes;
if (var instanceof byte[]) {
bytes = (byte[]) var;
} else {
bytes = PyishObjectMapper.getAsUnquotedPyishString(var).getBytes(charset);
}
return Base64.getEncoder().encodeToString(bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ protected void registerDefaults() {
FromJsonFilter.class,
ToYamlFilter.class,
FromYamlFilter.class,
RenderFilter.class
RenderFilter.class,
Base64EncodeFilter.class,
Base64DecodeFilter.class
);
}

Expand Down
88 changes: 88 additions & 0 deletions src/test/java/com/hubspot/jinjava/lib/filter/Base64FilterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.hubspot.jinjava.lib.filter;

import static org.assertj.core.api.Assertions.assertThat;

import com.hubspot.jinjava.BaseJinjavaTest;
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.RenderResult;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import java.util.Collections;
import org.junit.Test;

public class Base64FilterTest extends BaseJinjavaTest {

@Test
public void itEncodesWithDefaultCharset() {
assertThat(jinjava.render("{{ 'ß'|b64encode }}", Collections.emptyMap()))
.isEqualTo("w58=");
}

@Test
public void itEncodesWithUtf16Le() {
assertThat(
jinjava.render(
"{{ '\uD801\uDC37'|b64encode(encoding='utf-16le') }}",
Collections.emptyMap()
)
)
.isEqualTo("Adg33A==");
}

@Test
public void itDecodesWithUtf16Le() {
assertThat(
jinjava.render(
"{{ 'Adg33A=='|b64decode(encoding='utf-16le') }}",
Collections.emptyMap()
)
)
.isEqualTo("\uD801\uDC37");
}

@Test
public void itEncodesAndDecodesDefaultCharset() {
assertThat(
jinjava.render("{{ 123456789|b64encode|b64decode }}", Collections.emptyMap())
)
.isEqualTo("123456789");
}

@Test
public void itEncodesAndDecodesUtf16Le() {
assertThat(
jinjava.render(
"{{ 123456789|b64encode(encoding='utf-16le')|b64decode(encoding='utf-16le') }}",
Collections.emptyMap()
)
)
.isEqualTo("123456789");
}

@Test
public void itEncodesObject() {
assertThat(jinjava.render("{{ {'foo': ['bar']}|b64encode }}", Collections.emptyMap()))
.isEqualTo("eydmb28nOiBbJ2JhciddfQ==");
}

@Test
public void itHandlesInvalidDecode() {
RenderResult renderResult = jinjava.renderForResult(
"{{ 'ß'|b64decode }}",
Collections.emptyMap()
);
assertThat(renderResult.getErrors()).hasSize(1);
assertThat(renderResult.getErrors().get(0).getException())
.isInstanceOf(TemplateSyntaxException.class);
}

@Test
public void itThrowsErrorForNonStringDecode() {
RenderResult renderResult = jinjava.renderForResult(
"{{ 123|b64decode }}",
Collections.emptyMap()
);
assertThat(renderResult.getErrors()).hasSize(1);
assertThat(renderResult.getErrors().get(0).getException())
.isInstanceOf(InvalidArgumentException.class);
}
}

0 comments on commit ca99246

Please sign in to comment.