diff --git a/base64/base64.go b/base64/base64.go new file mode 100644 index 000000000..e98962630 --- /dev/null +++ b/base64/base64.go @@ -0,0 +1,25 @@ +package base64 + +import ( + b64 "encoding/base64" + "log" +) + +// Encode - Encode data in base64 format +func Encode(in []byte) string { + return b64.StdEncoding.EncodeToString(in) +} + +// Decode - Decode a base64-encoded string +func Decode(in string) []byte { + o, err := b64.StdEncoding.DecodeString(in) + if err != nil { + // maybe it's in the URL variant? + o, err = b64.URLEncoding.DecodeString(in) + if err != nil { + // ok, just give up... + log.Fatal(err) + } + } + return o +} diff --git a/base64/base64_test.go b/base64/base64_test.go new file mode 100644 index 000000000..524d95125 --- /dev/null +++ b/base64/base64_test.go @@ -0,0 +1,27 @@ +package base64 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncode(t *testing.T) { + assert.Equal(t, "", Encode([]byte(""))) + assert.Equal(t, "Zg==", Encode([]byte("f"))) + assert.Equal(t, "Zm8=", Encode([]byte("fo"))) + assert.Equal(t, "Zm9v", Encode([]byte("foo"))) + assert.Equal(t, "Zm9vYg==", Encode([]byte("foob"))) + assert.Equal(t, "Zm9vYmE=", Encode([]byte("fooba"))) + assert.Equal(t, "Zm9vYmFy", Encode([]byte("foobar"))) +} + +func TestDecode(t *testing.T) { + assert.Equal(t, []byte(""), Decode("")) + assert.Equal(t, []byte("f"), Decode("Zg==")) + assert.Equal(t, []byte("fo"), Decode("Zm8=")) + assert.Equal(t, []byte("foo"), Decode("Zm9v")) + assert.Equal(t, []byte("foob"), Decode("Zm9vYg==")) + assert.Equal(t, []byte("fooba"), Decode("Zm9vYmE=")) + assert.Equal(t, []byte("foobar"), Decode("Zm9vYmFy")) +} diff --git a/docs/content/functions/base64.md b/docs/content/functions/base64.md new file mode 100644 index 000000000..894718a2c --- /dev/null +++ b/docs/content/functions/base64.md @@ -0,0 +1,64 @@ +--- +title: base64 functions +menu: + main: + parent: functions +--- + +## `base64.Encode` + +Encode data as a Base64 string. Specifically, this uses the standard Base64 encoding as defined in [RFC4648 §4](https://tools.ietf.org/html/rfc4648#section-4) (and _not_ the URL-safe encoding). + +### Usage + +```go +base64.Encode input +``` + +### Arguments + +| name | description | +|--------|-------| +| `input` | The data to encode. Can be a string, a byte array, or a buffer. Other types will be converted to strings first. | + +### Examples + +```console +$ gomplate -i '{{ base64.Encode "hello world" }}' +aGVsbG8gd29ybGQ= +``` + +```console +$ gomplate -i '{{ "hello world" | base64.Encode }}' +aGVsbG8gd29ybGQ= +``` + +## `base64.Decode` + +Decode a Base64 string. This supports both standard ([RFC4648 §4](https://tools.ietf.org/html/rfc4648#section-4)) and URL-safe ([RFC4648 §5](https://tools.ietf.org/html/rfc4648#section-5)) encodings. + +This implementation outputs the data as a string, so it may not be appropriate for decoding binary data. If this functionality is desired, [file an issue](https://github.com/hairyhenderson/gomplate/issues/new). + +### Usage + +```go +base64.Decode input +``` + +### Arguments + +| name | description | +|--------|-------| +| `input` | The base64 string to decode | + +### Examples + +```console +$ gomplate -i '{{ base64.Decode "aGVsbG8gd29ybGQ=" }}' +hello world +``` + +```console +$ gomplate -i '{{ "aGVsbG8gd29ybGQ=" | base64.Decode }}' +hello world +``` \ No newline at end of file diff --git a/funcs.go b/funcs.go index ac7900909..bd1367f44 100644 --- a/funcs.go +++ b/funcs.go @@ -52,5 +52,6 @@ func initFuncs(data *Data) template.FuncMap { "include": data.include, } funcs.AWSFuncs(f) + funcs.AddBase64Funcs(f) return f } diff --git a/funcs/base64.go b/funcs/base64.go new file mode 100644 index 000000000..b66f7a08d --- /dev/null +++ b/funcs/base64.go @@ -0,0 +1,84 @@ +package funcs + +import ( + "fmt" + "strconv" + "sync" + + "github.com/hairyhenderson/gomplate/base64" +) + +var ( + bf *Base64Funcs + bfInit sync.Once +) + +// Base64NS - the base64 namespace +func Base64NS() *Base64Funcs { + bfInit.Do(func() { bf = &Base64Funcs{} }) + return bf +} + +// AddBase64Funcs - +func AddBase64Funcs(f map[string]interface{}) { + f["base64"] = Base64NS +} + +// Base64Funcs - +type Base64Funcs struct{} + +// Encode - +func (f *Base64Funcs) Encode(in interface{}) string { + b := toBytes(in) + return base64.Encode(b) +} + +// Decode - +func (f *Base64Funcs) Decode(in interface{}) string { + return string(base64.Decode(toString(in))) +} + +type byter interface { + Bytes() []byte +} + +func toBytes(in interface{}) []byte { + if in == nil { + return []byte{} + } + if s, ok := in.([]byte); ok { + return s + } + if s, ok := in.(byter); ok { + return s.Bytes() + } + if s, ok := in.(string); ok { + return []byte(s) + } + return []byte(fmt.Sprintf("%s", in)) +} + +func toString(in interface{}) string { + if s, ok := in.(string); ok { + return s + } + if s, ok := in.(fmt.Stringer); ok { + return s.String() + } + if i, ok := in.(int); ok { + return strconv.Itoa(i) + } + if u, ok := in.(uint64); ok { + return strconv.FormatUint(u, 10) + } + if f, ok := in.(float64); ok { + return strconv.FormatFloat(f, 'f', -1, 64) + } + if b, ok := in.(bool); ok { + return strconv.FormatBool(b) + } + if in == nil { + return "nil" + } + return fmt.Sprintf("%s", in) +} diff --git a/funcs/base64_test.go b/funcs/base64_test.go new file mode 100644 index 000000000..8e23f4192 --- /dev/null +++ b/funcs/base64_test.go @@ -0,0 +1,30 @@ +package funcs + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBase64Encode(t *testing.T) { + bf := &Base64Funcs{} + assert.Equal(t, "Zm9vYmFy", bf.Encode("foobar")) +} + +func TestBase64Decode(t *testing.T) { + bf := &Base64Funcs{} + assert.Equal(t, "foobar", bf.Decode("Zm9vYmFy")) + // assert.Equal(t, "", bf.Decode(nil)) +} + +func TestToBytes(t *testing.T) { + assert.Equal(t, []byte{0, 1, 2, 3}, toBytes([]byte{0, 1, 2, 3})) + + buf := &bytes.Buffer{} + buf.WriteString("hi") + assert.Equal(t, []byte("hi"), toBytes(buf)) + + assert.Equal(t, []byte{}, toBytes(nil)) + +} diff --git a/test/integration/base64.bats b/test/integration/base64.bats new file mode 100644 index 000000000..43b623d61 --- /dev/null +++ b/test/integration/base64.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats + +load helper + +tmpdir=$(mktemp -u) + +function setup () { + mkdir -p $tmpdir +} + +function teardown () { + rm -rf $tmpdir +} + +@test "'base64.Encode'" { + gomplate -i '{{ "foo" | base64.Encode }}' + [ "$status" -eq 0 ] + [[ "${output}" == "Zm9v" ]] +} + +@test "'base64.Decode'" { + gomplate -i '{{ "Zm9v" | base64.Decode }}' + [ "$status" -eq 0 ] + [[ "${output}" == "foo" ]] +} \ No newline at end of file