2023-09-29 23:02:24 +08:00
|
|
|
package strutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"compress/flate"
|
|
|
|
"compress/gzip"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-10-08 23:29:43 +08:00
|
|
|
"github.com/andybalholm/brotli"
|
2023-09-29 23:02:24 +08:00
|
|
|
"io"
|
2023-10-05 23:09:35 +08:00
|
|
|
"strconv"
|
2023-09-29 23:02:24 +08:00
|
|
|
"strings"
|
|
|
|
"tinyrdm/backend/types"
|
2023-10-15 22:38:41 +08:00
|
|
|
"unicode/utf8"
|
2023-09-29 23:02:24 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// ConvertTo convert string to specified type
|
2023-10-29 23:05:34 +08:00
|
|
|
// @param decodeType empty string indicates automatic detection
|
|
|
|
// @param formatType empty string indicates automatic detection
|
|
|
|
func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultFormat string) {
|
2023-09-29 23:02:24 +08:00
|
|
|
if len(str) <= 0 {
|
|
|
|
// empty content
|
2023-10-29 23:05:34 +08:00
|
|
|
if len(formatType) <= 0 {
|
|
|
|
resultFormat = types.VIEWAS_PLAIN_TEXT
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultFormat = formatType
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
if len(decodeType) <= 0 {
|
|
|
|
resultDecode = types.DECODE_NONE
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = decodeType
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
return
|
2023-10-29 23:05:34 +08:00
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
// decode first
|
|
|
|
value, resultDecode = decodeWith(str, decodeType)
|
|
|
|
// then format content
|
|
|
|
value, resultFormat = viewAs(value, formatType)
|
|
|
|
return
|
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
func decodeWith(str, decodeType string) (value, resultDecode string) {
|
|
|
|
if len(decodeType) > 0 {
|
|
|
|
switch decodeType {
|
|
|
|
case types.DECODE_NONE:
|
2023-10-05 23:09:35 +08:00
|
|
|
value = str
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = decodeType
|
|
|
|
return
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.DECODE_BASE64:
|
|
|
|
if base64Str, ok := decodeBase64(str); ok {
|
|
|
|
value = base64Str
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = str
|
|
|
|
}
|
|
|
|
resultDecode = decodeType
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.DECODE_GZIP:
|
|
|
|
if gzipStr, ok := decodeGZip(str); ok {
|
2023-09-29 23:02:24 +08:00
|
|
|
value = gzipStr
|
2023-10-29 23:05:34 +08:00
|
|
|
} else {
|
|
|
|
value = str
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = decodeType
|
|
|
|
return
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.DECODE_DEFLATE:
|
|
|
|
if gzipStr, ok := decodeDeflate(str); ok {
|
|
|
|
value = gzipStr
|
2023-10-08 23:29:43 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = str
|
2023-10-08 23:29:43 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = decodeType
|
|
|
|
return
|
2023-10-08 23:29:43 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.DECODE_BROTLI:
|
|
|
|
if gzipStr, ok := decodeBrotli(str); ok {
|
|
|
|
value = gzipStr
|
2023-10-08 23:29:43 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = str
|
2023-10-08 23:29:43 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = decodeType
|
|
|
|
return
|
2023-10-08 23:29:43 +08:00
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
return autoDecode(str)
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
// attempt try possible decode method
|
|
|
|
// if no decode is possible, it will return the origin string value and "none" decode type
|
|
|
|
func autoDecode(str string) (value, resultDecode string) {
|
2023-09-29 23:02:24 +08:00
|
|
|
if len(str) > 0 {
|
|
|
|
var ok bool
|
|
|
|
if value, ok = decodeBase64(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = types.DECODE_BASE64
|
2023-09-29 23:02:24 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if value, ok = decodeGZip(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = types.DECODE_GZIP
|
2023-09-29 23:02:24 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if value, ok = decodeDeflate(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = types.DECODE_DEFLATE
|
2023-09-29 23:02:24 +08:00
|
|
|
return
|
|
|
|
}
|
2023-10-05 23:09:35 +08:00
|
|
|
|
2023-10-08 23:29:43 +08:00
|
|
|
if value, ok = decodeBrotli(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultDecode = types.DECODE_BROTLI
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
value = str
|
|
|
|
resultDecode = types.DECODE_NONE
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func viewAs(str, formatType string) (value, resultFormat string) {
|
|
|
|
if len(formatType) > 0 {
|
|
|
|
switch formatType {
|
|
|
|
case types.VIEWAS_PLAIN_TEXT:
|
|
|
|
value = str
|
|
|
|
resultFormat = formatType
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.VIEWAS_JSON:
|
|
|
|
if jsonStr, ok := decodeJson(str); ok {
|
|
|
|
value = jsonStr
|
|
|
|
} else {
|
|
|
|
value = str
|
2023-10-08 23:29:43 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
resultFormat = formatType
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.VIEWAS_HEX:
|
|
|
|
if hexStr, ok := decodeToHex(str); ok {
|
|
|
|
value = hexStr
|
|
|
|
} else {
|
|
|
|
value = str
|
|
|
|
}
|
|
|
|
resultFormat = formatType
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.VIEWAS_BINARY:
|
|
|
|
if binStr, ok := decodeBinary(str); ok {
|
|
|
|
value = binStr
|
|
|
|
} else {
|
|
|
|
value = str
|
|
|
|
}
|
|
|
|
resultFormat = formatType
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return autoViewAs(str)
|
|
|
|
}
|
|
|
|
|
|
|
|
// attempt automatic convert to possible types
|
|
|
|
// if no conversion is possible, it will return the origin string value and "plain text" type
|
|
|
|
func autoViewAs(str string) (value, resultFormat string) {
|
|
|
|
if len(str) > 0 {
|
|
|
|
var ok bool
|
|
|
|
if value, ok = decodeJson(str); ok {
|
|
|
|
resultFormat = types.VIEWAS_JSON
|
2023-10-08 23:29:43 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-11 01:15:23 +08:00
|
|
|
if containsBinary(str) {
|
|
|
|
if value, ok = decodeToHex(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
resultFormat = types.VIEWAS_HEX
|
2023-10-05 23:09:35 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
value = str
|
2023-10-29 23:05:34 +08:00
|
|
|
resultFormat = types.VIEWAS_PLAIN_TEXT
|
2023-09-29 23:02:24 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeJson(str string) (string, bool) {
|
|
|
|
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
|
|
|
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
2023-10-11 15:48:29 +08:00
|
|
|
var out bytes.Buffer
|
|
|
|
if err := json.Indent(&out, []byte(str), "", " "); err == nil {
|
|
|
|
return out.String(), true
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeBase64(str string) (string, bool) {
|
|
|
|
if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil {
|
2023-10-15 22:38:41 +08:00
|
|
|
if s := string(decodedStr); utf8.ValidString(s) {
|
|
|
|
return s, true
|
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
2023-10-05 23:09:35 +08:00
|
|
|
func decodeBinary(str string) (string, bool) {
|
|
|
|
var binary strings.Builder
|
|
|
|
for _, char := range str {
|
|
|
|
binary.WriteString(fmt.Sprintf("%08b", int(char)))
|
|
|
|
}
|
|
|
|
return binary.String(), true
|
|
|
|
}
|
|
|
|
|
2023-10-11 01:15:23 +08:00
|
|
|
func decodeToHex(str string) (string, bool) {
|
2023-10-08 18:25:52 +08:00
|
|
|
decodeStr := hex.EncodeToString([]byte(str))
|
2023-09-29 23:02:24 +08:00
|
|
|
var resultStr strings.Builder
|
2023-10-08 18:25:52 +08:00
|
|
|
for i := 0; i < len(decodeStr); i += 2 {
|
2023-09-29 23:02:24 +08:00
|
|
|
resultStr.WriteString("\\x")
|
2023-10-08 18:25:52 +08:00
|
|
|
resultStr.WriteString(decodeStr[i : i+2])
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
return resultStr.String(), true
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeGZip(str string) (string, bool) {
|
|
|
|
if reader, err := gzip.NewReader(strings.NewReader(str)); err == nil {
|
|
|
|
defer reader.Close()
|
|
|
|
var decompressed []byte
|
|
|
|
if decompressed, err = io.ReadAll(reader); err == nil {
|
|
|
|
return string(decompressed), true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeDeflate(str string) (string, bool) {
|
|
|
|
reader := flate.NewReader(strings.NewReader(str))
|
|
|
|
defer reader.Close()
|
|
|
|
if decompressed, err := io.ReadAll(reader); err == nil {
|
|
|
|
return string(decompressed), true
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
2023-10-08 23:29:43 +08:00
|
|
|
func decodeBrotli(str string) (string, bool) {
|
|
|
|
reader := brotli.NewReader(strings.NewReader(str))
|
|
|
|
if decompressed, err := io.ReadAll(reader); err == nil {
|
|
|
|
return string(decompressed), true
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
func SaveAs(str, viewType, decodeType string) (value string, err error) {
|
|
|
|
value = str
|
|
|
|
switch viewType {
|
|
|
|
case types.VIEWAS_JSON:
|
|
|
|
if jsonStr, ok := encodeJson(str); ok {
|
|
|
|
value = jsonStr
|
|
|
|
} else {
|
|
|
|
err = errors.New("invalid json data")
|
|
|
|
return
|
|
|
|
}
|
2023-10-05 23:09:35 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.VIEWAS_HEX:
|
|
|
|
if hexStr, ok := encodeHex(str); ok {
|
|
|
|
value = hexStr
|
|
|
|
} else {
|
|
|
|
err = errors.New("invalid hex data")
|
|
|
|
return
|
|
|
|
}
|
2023-10-05 23:09:35 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.VIEWAS_BINARY:
|
|
|
|
if binStr, ok := encodeBinary(str); ok {
|
|
|
|
value = binStr
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
err = errors.New("invalid binary data")
|
|
|
|
return
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
}
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
switch decodeType {
|
|
|
|
case types.DECODE_NONE:
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.DECODE_BASE64:
|
|
|
|
value, _ = encodeBase64(value)
|
|
|
|
return
|
|
|
|
|
|
|
|
case types.DECODE_GZIP:
|
2023-09-29 23:02:24 +08:00
|
|
|
if gzipStr, ok := encodeGZip(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = gzipStr
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
err = errors.New("fail to build gzip")
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
return
|
2023-09-29 23:02:24 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.DECODE_DEFLATE:
|
2023-09-29 23:02:24 +08:00
|
|
|
if deflateStr, ok := encodeDeflate(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = deflateStr
|
2023-09-29 23:02:24 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
err = errors.New("fail to build deflate")
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
return
|
2023-10-08 23:29:43 +08:00
|
|
|
|
2023-10-29 23:05:34 +08:00
|
|
|
case types.DECODE_BROTLI:
|
2023-10-08 23:29:43 +08:00
|
|
|
if brotliStr, ok := encodeBrotli(str); ok {
|
2023-10-29 23:05:34 +08:00
|
|
|
value = brotliStr
|
2023-10-08 23:29:43 +08:00
|
|
|
} else {
|
2023-10-29 23:05:34 +08:00
|
|
|
err = errors.New("fail to build brotli")
|
2023-10-08 23:29:43 +08:00
|
|
|
}
|
2023-10-29 23:05:34 +08:00
|
|
|
return
|
2023-09-29 23:02:24 +08:00
|
|
|
}
|
|
|
|
return str, errors.New("fail to save with unknown error")
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeJson(str string) (string, bool) {
|
|
|
|
var data any
|
|
|
|
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
|
|
|
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
|
|
|
if err := json.Unmarshal([]byte(str), &data); err == nil {
|
|
|
|
var jsonByte []byte
|
|
|
|
if jsonByte, err = json.Marshal(data); err == nil {
|
|
|
|
return string(jsonByte), true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeBase64(str string) (string, bool) {
|
|
|
|
return base64.StdEncoding.EncodeToString([]byte(str)), true
|
|
|
|
}
|
|
|
|
|
2023-10-05 23:09:35 +08:00
|
|
|
func encodeBinary(str string) (string, bool) {
|
|
|
|
var result strings.Builder
|
|
|
|
total := len(str)
|
|
|
|
for i := 0; i < total; i += 8 {
|
|
|
|
b, _ := strconv.ParseInt(str[i:i+8], 2, 8)
|
|
|
|
result.WriteByte(byte(b))
|
|
|
|
}
|
|
|
|
return result.String(), true
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeHex(str string) (string, bool) {
|
|
|
|
hexStrArr := strings.Split(str, "\\x")
|
|
|
|
hexStr := strings.Join(hexStrArr, "")
|
|
|
|
if decodeStr, err := hex.DecodeString(hexStr); err == nil {
|
|
|
|
return string(decodeStr), true
|
|
|
|
}
|
|
|
|
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
2023-09-29 23:02:24 +08:00
|
|
|
func encodeGZip(str string) (string, bool) {
|
|
|
|
var compress = func(b []byte) (string, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writer := gzip.NewWriter(&buf)
|
|
|
|
if _, err := writer.Write([]byte(str)); err != nil {
|
|
|
|
writer.Close()
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
writer.Close()
|
|
|
|
return string(buf.Bytes()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if gzipStr, err := compress([]byte(str)); err == nil {
|
|
|
|
return gzipStr, true
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeDeflate(str string) (string, bool) {
|
|
|
|
var compress = func(b []byte) (string, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writer, err := flate.NewWriter(&buf, flate.DefaultCompression)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if _, err = writer.Write([]byte(str)); err != nil {
|
|
|
|
writer.Close()
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
writer.Close()
|
|
|
|
return string(buf.Bytes()), nil
|
|
|
|
}
|
|
|
|
if deflateStr, err := compress([]byte(str)); err == nil {
|
|
|
|
return deflateStr, true
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|
2023-10-08 23:29:43 +08:00
|
|
|
|
|
|
|
func encodeBrotli(str string) (string, bool) {
|
|
|
|
var compress = func(b []byte) (string, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writer := brotli.NewWriter(&buf)
|
|
|
|
if _, err := writer.Write([]byte(str)); err != nil {
|
|
|
|
writer.Close()
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
writer.Close()
|
|
|
|
return string(buf.Bytes()), nil
|
|
|
|
}
|
|
|
|
if brotliStr, err := compress([]byte(str)); err == nil {
|
|
|
|
return brotliStr, true
|
|
|
|
}
|
|
|
|
return str, false
|
|
|
|
}
|