pkg: adding base64 encoding for strings

cue's current implementation doesn't support encoding and decoding
of strings with base64. a new package `encoding/base64` adds
the missing funcionality as builtin's. go supports different
encoding methods for now only StdEncoding is supported in cue.

Issue #61

Change-Id: Ic656dee880c25f6b0a2a710e38b3ff86fb28ab8b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2600
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index b710f73..fd2a48d 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -56,6 +56,18 @@
 		test("math", `math.Floor("foo")`),
 		`_|_(cannot use "foo" (type string) as number in argument 1 to math.Floor)`,
 	}, {
+		test("encoding/base64", `base64.Encode(null, "foo")`),
+		`"Zm9v"`,
+	}, {
+		test("encoding/base64", `base64.Decode(null, base64.Encode(null, "foo"))`),
+		`'foo'`,
+	}, {
+		test("encoding/base64", `base64.Decode(null, "foo")`),
+		`_|_(error in call to encoding/base64.Decode: illegal base64 data at input byte 0)`,
+	}, {
+		test("encoding/base64", `base64.Decode({}, "foo")`),
+		`_|_(error in call to encoding/base64.Decode: base64: unsupported encoding: cannot use value {} (type struct) as null)`,
+	}, {
 		test("encoding/hex", `hex.Encode("foo")`),
 		`"666f6f"`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index ca1e556..b7917a9 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -8,6 +8,7 @@
 	"crypto/sha1"
 	"crypto/sha256"
 	"crypto/sha512"
+	"encoding/base64"
 	"encoding/csv"
 	"encoding/hex"
 	"encoding/json"
@@ -185,6 +186,61 @@
 			},
 		}},
 	},
+	"encoding/base64": &builtinPkg{
+		native: []*builtin{{
+			Name:   "EncodedLen",
+			Params: []kind{topKind, intKind},
+			Result: intKind,
+			Func: func(c *callCtxt) {
+				encoding, n := c.value(0), c.int(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if err := encoding.Null(); err != nil {
+						return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+					}
+					return base64.StdEncoding.EncodedLen(n), nil
+				}()
+			},
+		}, {
+			Name:   "DecodedLen",
+			Params: []kind{topKind, intKind},
+			Result: intKind,
+			Func: func(c *callCtxt) {
+				encoding, x := c.value(0), c.int(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if err := encoding.Null(); err != nil {
+						return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+					}
+					return base64.StdEncoding.DecodedLen(x), nil
+				}()
+			},
+		}, {
+			Name:   "Encode",
+			Params: []kind{topKind, stringKind},
+			Result: stringKind,
+			Func: func(c *callCtxt) {
+				encoding, src := c.value(0), c.bytes(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if err := encoding.Null(); err != nil {
+						return "", fmt.Errorf("base64: unsupported encoding: %v", err)
+					}
+					return base64.StdEncoding.EncodeToString(src), nil
+				}()
+			},
+		}, {
+			Name:   "Decode",
+			Params: []kind{topKind, stringKind},
+			Result: stringKind,
+			Func: func(c *callCtxt) {
+				encoding, s := c.value(0), c.string(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if err := encoding.Null(); err != nil {
+						return nil, fmt.Errorf("base64: unsupported encoding: %v", err)
+					}
+					return base64.StdEncoding.DecodeString(s)
+				}()
+			},
+		}},
+	},
 	"encoding/csv": &builtinPkg{
 		native: []*builtin{{
 			Name:   "Encode",
diff --git a/pkg/encoding/base64/manual.go b/pkg/encoding/base64/manual.go
new file mode 100644
index 0000000..b292424
--- /dev/null
+++ b/pkg/encoding/base64/manual.go
@@ -0,0 +1,61 @@
+// Copyright 2019 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package base64 implements base64 encoding as specified by RFC 4648.
+package base64
+
+import (
+	"encoding/base64"
+	"fmt"
+
+	"cuelang.org/go/cue"
+)
+
+// EncodedLen returns the length in bytes of the base64 encoding
+// of an input buffer of length n. Encoding needs to be set to null
+// as only StdEncoding is supported for now.
+func EncodedLen(encoding cue.Value, n int) (int, error) {
+	if err := encoding.Null(); err != nil {
+		return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+	}
+	return base64.StdEncoding.EncodedLen(n), nil
+}
+
+// DecodedLen returns the maximum length in bytes of the decoded data
+// corresponding to n bytes of base64-encoded data. Encoding needs to be set to
+// null as only StdEncoding is supported for now.
+func DecodedLen(encoding cue.Value, x int) (int, error) {
+	if err := encoding.Null(); err != nil {
+		return 0, fmt.Errorf("base64: unsupported encoding: %v", err)
+	}
+	return base64.StdEncoding.DecodedLen(x), nil
+}
+
+// Encode returns the base64 encoding of src. Encoding needs to be set to null
+// as only StdEncoding is supported for now.
+func Encode(encoding cue.Value, src []byte) (string, error) {
+	if err := encoding.Null(); err != nil {
+		return "", fmt.Errorf("base64: unsupported encoding: %v", err)
+	}
+	return base64.StdEncoding.EncodeToString(src), nil
+}
+
+// Decode returns the bytes represented by the base64 string s. Encoding needs
+// to be set to null as only StdEncoding is supported for now.
+func Decode(encoding cue.Value, s string) ([]byte, error) {
+	if err := encoding.Null(); err != nil {
+		return nil, fmt.Errorf("base64: unsupported encoding: %v", err)
+	}
+	return base64.StdEncoding.DecodeString(s)
+}