From 47a62a3c1636eec7824f92d2a92a704797fd817a Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Wed, 6 May 2026 01:11:39 +0800 Subject: [PATCH] feat: add Base64 Raw support and smart decoding upgrade --- CHANGELOG.md | 8 +++++++ README.md | 6 +++-- encoding.go | 60 ++++++++++++++++++++++++++++++++++++++++-------- encoding_test.go | 27 +++++++++++++++++++--- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ca72f..8350343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog: @go/encoding +## [v1.1.0] - 2026-05-06 + +### Added +- **Base64 无填充支持**:新增 `Base64Raw`、`Base64RawToString`、`UrlBase64Raw`、`UrlBase64RawToString` 接口,支持生成不带填充符(`=`)的编码。 + +### Changed +- **智能解码升级**:升级了 `UnBase64` 与 `UnUrlBase64` 系列函数,通过 O(1) 零分配检测自动兼容“带填充”与“无填充”的输入数据,无需额外调用 Raw 解码接口。 + ## [v1.0.6] - 2026-05-06 ### Changed diff --git a/README.md b/README.md index b00cb14..317f504 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ - `func Hex(data []byte) []byte` / `func HexToString(data []byte) string` - `func UnHex(data []byte) ([]byte, error)` / `func UnHexFromString(data string) ([]byte, error)` - `func Base64(data []byte) []byte` / `func Base64ToString(data []byte) string` -- `func UnBase64(data []byte) ([]byte, error)` / `func UnBase64FromString(data string) ([]byte, error)` +- `func Base64Raw(data []byte) []byte` / `func Base64RawToString(data []byte) string` (无填充版本) +- `func UnBase64(data []byte) ([]byte, error)` / `func UnBase64FromString(data string) ([]byte, error)` (智能兼容填充与无填充) - `func UrlBase64(data []byte) []byte` / `func UrlBase64ToString(data []byte) string` -- `func UnUrlBase64(data []byte) ([]byte, error)` / `func UnUrlBase64FromString(data string) ([]byte, error)` +- `func UrlBase64Raw(data []byte) []byte` / `func UrlBase64RawToString(data []byte) string` (无填充版本) +- `func UnUrlBase64(data []byte) ([]byte, error)` / `func UnUrlBase64FromString(data string) ([]byte, error)` (智能兼容填充与无填充) ### Web 编码 (URL/HTML) - `func UrlEncode(data []byte) string` diff --git a/encoding.go b/encoding.go index 2335445..dacae84 100644 --- a/encoding.go +++ b/encoding.go @@ -44,6 +44,18 @@ func Base64ToString(data []byte) string { return base64.StdEncoding.EncodeToString(data) } +// Base64Raw 将数据转换为无填充的 Base64 编码的字节切片 +func Base64Raw(data []byte) []byte { + buf := make([]byte, base64.RawStdEncoding.EncodedLen(len(data))) + base64.RawStdEncoding.Encode(buf, data) + return buf +} + +// Base64RawToString 将数据转换为无填充的 Base64 编码的字符串 +func Base64RawToString(data []byte) string { + return base64.RawStdEncoding.EncodeToString(data) +} + // UrlBase64 将数据转换为 URL 安全的 Base64 编码的字节切片 func UrlBase64(data []byte) []byte { buf := make([]byte, base64.URLEncoding.EncodedLen(len(data))) @@ -56,28 +68,56 @@ func UrlBase64ToString(data []byte) string { return base64.URLEncoding.EncodeToString(data) } -// UnBase64 将 Base64 编码的字节切片解码 +// UrlBase64Raw 将数据转换为 URL 安全且无填充的 Base64 编码的字节切片 +func UrlBase64Raw(data []byte) []byte { + buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(data))) + base64.RawURLEncoding.Encode(buf, data) + return buf +} + +// UrlBase64RawToString 将数据转换为 URL 安全且无填充的 Base64 编码的字符串 +func UrlBase64RawToString(data []byte) string { + return base64.RawURLEncoding.EncodeToString(data) +} + +// UnBase64 将 Base64 编码的字节切片解码(自动兼容有无填充) func UnBase64(data []byte) ([]byte, error) { - dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) - n, err := base64.StdEncoding.Decode(dbuf, data) + if len(data) > 0 && data[len(data)-1] == '=' { + dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(data))) + n, err := base64.StdEncoding.Decode(dbuf, data) + return dbuf[:n], err + } + dbuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(data))) + n, err := base64.RawStdEncoding.Decode(dbuf, data) return dbuf[:n], err } -// UnBase64FromString 将 Base64 编码的字符串解码 +// UnBase64FromString 将 Base64 编码的字符串解码(自动兼容有无填充) func UnBase64FromString(data string) ([]byte, error) { - return base64.StdEncoding.DecodeString(data) + if len(data) > 0 && data[len(data)-1] == '=' { + return base64.StdEncoding.DecodeString(data) + } + return base64.RawStdEncoding.DecodeString(data) } -// UnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码 +// UnUrlBase64 将 URL 安全的 Base64 编码的字节切片解码(自动兼容有无填充) func UnUrlBase64(data []byte) ([]byte, error) { - dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data))) - n, err := base64.URLEncoding.Decode(dbuf, data) + if len(data) > 0 && data[len(data)-1] == '=' { + dbuf := make([]byte, base64.URLEncoding.DecodedLen(len(data))) + n, err := base64.URLEncoding.Decode(dbuf, data) + return dbuf[:n], err + } + dbuf := make([]byte, base64.RawURLEncoding.DecodedLen(len(data))) + n, err := base64.RawURLEncoding.Decode(dbuf, data) return dbuf[:n], err } -// UnUrlBase64FromString 将 URL 安全的 Base64 编码的字符串解码 +// UnUrlBase64FromString 将 URL 安全的 Base64 编码的字符串解码(自动兼容有无填充) func UnUrlBase64FromString(data string) ([]byte, error) { - return base64.URLEncoding.DecodeString(data) + if len(data) > 0 && data[len(data)-1] == '=' { + return base64.URLEncoding.DecodeString(data) + } + return base64.RawURLEncoding.DecodeString(data) } // UrlEncode 对数据进行 URL 编码 diff --git a/encoding_test.go b/encoding_test.go index 34e12ab..9353882 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -40,17 +40,38 @@ func TestBase64(t *testing.T) { if !bytes.Equal(cast.As(encoding.UnBase64FromString(enc)), data) { t.Error("UnBase64FromString with cast.As failed") } + + // Raw (Unpadded) + rawEnc := encoding.Base64RawToString(data) + if bytes.HasSuffix([]byte(rawEnc), []byte("=")) { + t.Error("Base64Raw should not have padding") + } + rawDec, err := encoding.UnBase64FromString(rawEnc) + if err != nil || !bytes.Equal(data, rawDec) { + t.Error("Base64Raw smart decoding failed") + } // URL - uEnc := encoding.UrlBase64ToString([]byte("hello/world+")) + uData := []byte("hello/world+") + uEnc := encoding.UrlBase64ToString(uData) uDec, _ := encoding.UnUrlBase64([]byte(uEnc)) - if !bytes.Equal([]byte("hello/world+"), uDec) { + if !bytes.Equal(uData, uDec) { t.Error("UrlBase64 roundtrip failed") } - if !bytes.Equal(cast.As(encoding.UnUrlBase64FromString(uEnc)), []byte("hello/world+")) { + if !bytes.Equal(cast.As(encoding.UnUrlBase64FromString(uEnc)), uData) { t.Error("UnUrlBase64FromString with cast.As failed") } + + // URL Raw (Unpadded) + uRawEnc := encoding.UrlBase64RawToString(uData) + if bytes.HasSuffix([]byte(uRawEnc), []byte("=")) { + t.Error("UrlBase64Raw should not have padding") + } + uRawDec, err := encoding.UnUrlBase64FromString(uRawEnc) + if err != nil || !bytes.Equal(uData, uRawDec) { + t.Error("UrlBase64Raw smart decoding failed") + } } // --- Web ---