cli/pkg/io/sigil/sigils.go
Vi c122e89f40 feat(io): add Sigil composable transform framework (port from Enchantrix) (#345) (#353)
Co-authored-by: Claude <developers@lethean.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 20:30:26 +00:00

273 lines
6.8 KiB
Go

package sigil
import (
"bytes"
"compress/gzip"
"crypto"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
"golang.org/x/crypto/sha3"
)
// ReverseSigil is a symmetric Sigil that reverses the bytes of the payload.
// Both In and Out perform the same reversal operation.
type ReverseSigil struct{}
// In reverses the bytes of the data.
func (s *ReverseSigil) In(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
reversed := make([]byte, len(data))
for i, j := 0, len(data)-1; i < len(data); i, j = i+1, j-1 {
reversed[i] = data[j]
}
return reversed, nil
}
// Out reverses the bytes of the data (symmetric with In).
func (s *ReverseSigil) Out(data []byte) ([]byte, error) {
return s.In(data)
}
// HexSigil is a Sigil that encodes/decodes data to/from hexadecimal.
// In encodes the data, Out decodes it.
type HexSigil struct{}
// In encodes the data to hexadecimal.
func (s *HexSigil) In(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
dst := make([]byte, hex.EncodedLen(len(data)))
hex.Encode(dst, data)
return dst, nil
}
// Out decodes the data from hexadecimal.
func (s *HexSigil) Out(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
dst := make([]byte, hex.DecodedLen(len(data)))
_, err := hex.Decode(dst, data)
return dst, err
}
// Base64Sigil is a Sigil that encodes/decodes data to/from standard base64.
// In encodes the data, Out decodes it.
type Base64Sigil struct{}
// In encodes the data to base64.
func (s *Base64Sigil) In(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
dst := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(dst, data)
return dst, nil
}
// Out decodes the data from base64.
func (s *Base64Sigil) Out(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
dst := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(dst, data)
return dst[:n], err
}
// GzipSigil is a Sigil that compresses/decompresses data using gzip.
// In compresses the data, Out decompresses it.
type GzipSigil struct{}
// In compresses the data using gzip.
func (s *GzipSigil) In(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write(data); err != nil {
return nil, err
}
if err := gz.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// Out decompresses the data using gzip.
func (s *GzipSigil) Out(data []byte) ([]byte, error) {
if data == nil {
return nil, nil
}
r, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer r.Close()
return io.ReadAll(r)
}
// JSONSigil is a Sigil that compacts or indents JSON data.
// Out is a passthrough (no-op).
type JSONSigil struct {
Indent bool
}
// In compacts or indents the JSON data depending on the Indent field.
func (s *JSONSigil) In(data []byte) ([]byte, error) {
if s.Indent {
var out bytes.Buffer
err := json.Indent(&out, data, "", " ")
return out.Bytes(), err
}
var out bytes.Buffer
err := json.Compact(&out, data)
return out.Bytes(), err
}
// Out is a passthrough for JSONSigil. The primary use is formatting.
func (s *JSONSigil) Out(data []byte) ([]byte, error) {
return data, nil
}
// HashSigil is a Sigil that hashes data using a specified algorithm.
// In computes the hash digest, Out is a passthrough.
type HashSigil struct {
Hash crypto.Hash
}
// NewHashSigil creates a new HashSigil for the given hash algorithm.
func NewHashSigil(h crypto.Hash) *HashSigil {
return &HashSigil{Hash: h}
}
// In hashes the data using the configured algorithm.
func (s *HashSigil) In(data []byte) ([]byte, error) {
var h io.Writer
switch s.Hash {
case crypto.MD4:
h = md4.New()
case crypto.MD5:
h = md5.New()
case crypto.SHA1:
h = sha1.New()
case crypto.SHA224:
h = sha256.New224()
case crypto.SHA256:
h = sha256.New()
case crypto.SHA384:
h = sha512.New384()
case crypto.SHA512:
h = sha512.New()
case crypto.RIPEMD160:
h = ripemd160.New()
case crypto.SHA3_224:
h = sha3.New224()
case crypto.SHA3_256:
h = sha3.New256()
case crypto.SHA3_384:
h = sha3.New384()
case crypto.SHA3_512:
h = sha3.New512()
case crypto.SHA512_224:
h = sha512.New512_224()
case crypto.SHA512_256:
h = sha512.New512_256()
case crypto.BLAKE2s_256:
h, _ = blake2s.New256(nil)
case crypto.BLAKE2b_256:
h, _ = blake2b.New256(nil)
case crypto.BLAKE2b_384:
h, _ = blake2b.New384(nil)
case crypto.BLAKE2b_512:
h, _ = blake2b.New512(nil)
default:
return nil, errors.New("sigil: hash algorithm not available")
}
h.Write(data)
return h.(interface{ Sum([]byte) []byte }).Sum(nil), nil
}
// Out is a passthrough for HashSigil. Hashing is irreversible.
func (s *HashSigil) Out(data []byte) ([]byte, error) {
return data, nil
}
// NewSigil is a factory function that returns a Sigil based on a string name.
// It is the primary way to create Sigil instances.
//
// Supported names: reverse, hex, base64, gzip, json, json-indent,
// md4, md5, sha1, sha224, sha256, sha384, sha512, ripemd160,
// sha3-224, sha3-256, sha3-384, sha3-512, sha512-224, sha512-256,
// blake2s-256, blake2b-256, blake2b-384, blake2b-512.
func NewSigil(name string) (Sigil, error) {
switch name {
case "reverse":
return &ReverseSigil{}, nil
case "hex":
return &HexSigil{}, nil
case "base64":
return &Base64Sigil{}, nil
case "gzip":
return &GzipSigil{}, nil
case "json":
return &JSONSigil{Indent: false}, nil
case "json-indent":
return &JSONSigil{Indent: true}, nil
case "md4":
return NewHashSigil(crypto.MD4), nil
case "md5":
return NewHashSigil(crypto.MD5), nil
case "sha1":
return NewHashSigil(crypto.SHA1), nil
case "sha224":
return NewHashSigil(crypto.SHA224), nil
case "sha256":
return NewHashSigil(crypto.SHA256), nil
case "sha384":
return NewHashSigil(crypto.SHA384), nil
case "sha512":
return NewHashSigil(crypto.SHA512), nil
case "ripemd160":
return NewHashSigil(crypto.RIPEMD160), nil
case "sha3-224":
return NewHashSigil(crypto.SHA3_224), nil
case "sha3-256":
return NewHashSigil(crypto.SHA3_256), nil
case "sha3-384":
return NewHashSigil(crypto.SHA3_384), nil
case "sha3-512":
return NewHashSigil(crypto.SHA3_512), nil
case "sha512-224":
return NewHashSigil(crypto.SHA512_224), nil
case "sha512-256":
return NewHashSigil(crypto.SHA512_256), nil
case "blake2s-256":
return NewHashSigil(crypto.BLAKE2s_256), nil
case "blake2b-256":
return NewHashSigil(crypto.BLAKE2b_256), nil
case "blake2b-384":
return NewHashSigil(crypto.BLAKE2b_384), nil
case "blake2b-512":
return NewHashSigil(crypto.BLAKE2b_512), nil
default:
return nil, errors.New("sigil: unknown sigil name: " + name)
}
}