Repository: gohugoio/hugo (language: go)

Issue:
Add optional encoding parameter to hmac template function
The primary driver for this enhancement is to provide binary output from `crypto.HMAC`. See:
<https://discourse.gohugo.io/t/have-hmac-outputs-raw-binary-data-rather-than-hexits-lowercase/37808/>

cc @regis


Discussion / hints:
I would like to change this issue's objective, title, and description per:
<https://discourse.gohugo.io/t/have-hmac-outputs-raw-binary-data-rather-than-hexits-lowercase/37808/6>

cc @bep @regisphilibert 

Fix patch:
diff --git a/docs/content/en/functions/hmac.md b/docs/content/en/functions/hmac.md
index 9df36d30011..b906e5abb02 100644
--- a/docs/content/en/functions/hmac.md
+++ b/docs/content/en/functions/hmac.md
@@ -1,7 +1,7 @@
 ---
 title: hmac
 linktitle: hmac
-description: Compute the cryptographic checksum of a message.
+description: Returns a cryptographic hash that uses a key to sign a message.
 date: 2020-05-29
 publishdate: 2020-05-29
 lastmod: 2020-05-29
@@ -10,24 +10,25 @@ menu:
   docs:
     parent: "functions"
 keywords: [hmac,checksum]
-signature: ["hmac HASH_TYPE KEY MESSAGE"]
+signature: ["crypto.HMAC HASH_TYPE KEY MESSAGE [ENCODING]","hmac HASH_TYPE KEY MESSAGE [ENCODING]" ]
 workson: []
 hugoversion:
 relatedfuncs: [hmac]
 deprecated: false
-aliases: [hmac]
+aliases: []
 ---
 
-`hmac` returns a cryptographic hash that uses a key to sign a message.
+Set the `HASH_TYPE` argument to `md5`, `sha1`, `sha256`, or `sha512`.
 
-```
-{{ hmac "sha256" "Secret key" "Hello world, gophers!"}},
-<!-- returns the string "b6d11b6c53830b9d87036272ca9fe9d19306b8f9d8aa07b15da27d89e6e34f40"
-```
+Set the optional `ENCODING` argument to either `hex` (default) or `binary`.
 
-Supported hash functions:
+```go-html-template
+{{ hmac "sha256" "Secret key" "Secret message" }}
+5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84
 
-* md5
-* sha1
-* sha256
-* sha512
+{{ hmac "sha256" "Secret key" "Secret message" "hex" }}
+5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84
+
+{{ hmac "sha256" "Secret key" "Secret message" "binary" | base64Encode }}
+XM60kfRfixVOIPOwow7Tpv8wJ9Nz+Fx4/+iYMYCwPIQ=
+```
diff --git a/tpl/crypto/crypto.go b/tpl/crypto/crypto.go
index 785b135025e..e6e67e64b12 100644
--- a/tpl/crypto/crypto.go
+++ b/tpl/crypto/crypto.go
@@ -69,7 +69,7 @@ func (ns *Namespace) SHA256(in any) (string, error) {
 }
 
 // HMAC returns a cryptographic hash that uses a key to sign a message.
-func (ns *Namespace) HMAC(h any, k any, m any) (string, error) {
+func (ns *Namespace) HMAC(h any, k any, m any, e ...any) (string, error) {
 	ha, err := cast.ToStringE(h)
 	if err != nil {
 		return "", err
@@ -105,5 +105,21 @@ func (ns *Namespace) HMAC(h any, k any, m any) (string, error) {
 		return "", err
 	}
 
-	return hex.EncodeToString(mac.Sum(nil)[:]), nil
+	var encoding = "hex"
+	if len(e) > 0 && e[0] != nil {
+		encoding, err = cast.ToStringE(e[0])
+		if err != nil {
+			return "", err
+		}
+	}
+
+	switch encoding {
+	case "binary":
+		return string(mac.Sum(nil)[:]), nil
+	case "hex":
+		return hex.EncodeToString(mac.Sum(nil)[:]), nil
+	default:
+		return "", fmt.Errorf("%q is not a supported encoding method", encoding)
+	}
+
 }


Test patch:
diff --git a/tpl/crypto/crypto_test.go b/tpl/crypto/crypto_test.go
index 78e38731864..b6b2a69152f 100644
--- a/tpl/crypto/crypto_test.go
+++ b/tpl/crypto/crypto_test.go
@@ -107,20 +107,25 @@ func TestHMAC(t *testing.T) {
 	ns := New()
 
 	for i, test := range []struct {
-		hash   any
-		key    any
-		msg    any
-		expect any
+		hash     any
+		key      any
+		msg      any
+		encoding any
+		expect   any
 	}{
-		{"md5", "Secret key", "Hello world, gophers!", "36eb69b6bf2de96b6856fdee8bf89754"},
-		{"sha1", "Secret key", "Hello world, gophers!", "84a76647de6cd47ac6ae4258e3753f711172ce68"},
-		{"sha256", "Secret key", "Hello world, gophers!", "b6d11b6c53830b9d87036272ca9fe9d19306b8f9d8aa07b15da27d89e6e34f40"},
-		{"sha512", "Secret key", "Hello world, gophers!", "dc3e586cd936865e2abc4c12665e9cc568b2dad714df3c9037cbea159d036cfc4209da9e3fcd30887ff441056941966899f6fb7eec9646ff9ddb592595a8eb7f"},
-		{"", t, "", false},
+		{"md5", "Secret key", "Hello world, gophers!", nil, "36eb69b6bf2de96b6856fdee8bf89754"},
+		{"sha1", "Secret key", "Hello world, gophers!", nil, "84a76647de6cd47ac6ae4258e3753f711172ce68"},
+		{"sha256", "Secret key", "Hello world, gophers!", nil, "b6d11b6c53830b9d87036272ca9fe9d19306b8f9d8aa07b15da27d89e6e34f40"},
+		{"sha512", "Secret key", "Hello world, gophers!", nil, "dc3e586cd936865e2abc4c12665e9cc568b2dad714df3c9037cbea159d036cfc4209da9e3fcd30887ff441056941966899f6fb7eec9646ff9ddb592595a8eb7f"},
+		{"md5", "Secret key", "Hello world, gophers!", "hex", "36eb69b6bf2de96b6856fdee8bf89754"},
+		{"md5", "Secret key", "Hello world, gophers!", "binary", "6\xebi\xb6\xbf-\xe9khV\xfd\xee\x8b\xf8\x97T"},
+		{"md5", "Secret key", "Hello world, gophers!", "foo", false},
+		{"md5", "Secret key", "Hello world, gophers!", "", false},
+		{"", t, "", nil, false},
 	} {
-		errMsg := qt.Commentf("[%d] %v, %v, %v", i, test.hash, test.key, test.msg)
+		errMsg := qt.Commentf("[%d] %v, %v, %v, %v", i, test.hash, test.key, test.msg, test.encoding)
 
-		result, err := ns.HMAC(test.hash, test.key, test.msg)
+		result, err := ns.HMAC(test.hash, test.key, test.msg, test.encoding)
 
 		if b, ok := test.expect.(bool); ok && !b {
 			c.Assert(err, qt.Not(qt.IsNil), errMsg)