package convutil import ( "errors" "regexp" "tinyrdm/backend/types" strutil "tinyrdm/backend/utils/string" ) type DataConvert interface { Encode(string) (string, bool) Decode(string) (string, bool) } var ( jsonConv JsonConvert yamlConv YamlConvert xmlConv XmlConvert base64Conv Base64Convert binaryConv BinaryConvert hexConv HexConvert gzipConv GZipConvert deflateConv DeflateConvert zstdConv ZStdConvert brotliConv BrotliConvert msgpackConv MsgpackConvert ) // ConvertTo convert string to specified type // @param decodeType empty string indicates automatic detection // @param formatType empty string indicates automatic detection // @param custom decoder if any func ConvertTo(str, decodeType, formatType string, customDecoder []CmdConvert) (value, resultDecode, resultFormat string) { if len(str) <= 0 { // empty content if len(formatType) <= 0 { resultFormat = types.FORMAT_RAW } else { resultFormat = formatType } if len(decodeType) <= 0 { resultDecode = types.DECODE_NONE } else { resultDecode = decodeType } return } // decode first value, resultDecode = decodeWith(str, decodeType, customDecoder) // then format content if len(formatType) <= 0 { value, resultFormat = autoViewAs(value) } else { value, resultFormat = viewAs(value, formatType) } return } func decodeWith(str, decodeType string, customDecoder []CmdConvert) (value, resultDecode string) { if len(decodeType) > 0 { switch decodeType { case types.DECODE_NONE: value = str case types.DECODE_BASE64: if base64Str, ok := base64Conv.Decode(str); ok { value = base64Str } else { value = str } case types.DECODE_GZIP: if gzipStr, ok := gzipConv.Decode(str); ok { value = gzipStr } else { value = str } case types.DECODE_DEFLATE: if falteStr, ok := deflateConv.Decode(str); ok { value = falteStr } else { value = str } case types.DECODE_ZSTD: if zstdStr, ok := zstdConv.Decode(str); ok { value = zstdStr } else { value = str } case types.DECODE_BROTLI: if brotliStr, ok := brotliConv.Decode(str); ok { value = brotliStr } else { value = str } case types.DECODE_MSGPACK: if msgpackStr, ok := msgpackConv.Decode(str); ok { value = msgpackStr } else { value = str } default: for _, decoder := range customDecoder { if decoder.Name == decodeType { if decodedStr, ok := decoder.Decode(str); ok { value = decodedStr } else { value = str } break } } } resultDecode = decodeType return } value, resultDecode = autoDecode(str, customDecoder) return } // 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, customDecoder []CmdConvert) (value, resultDecode string) { if len(str) > 0 { // pure digit content may incorrect regard as some encoded type, skip decode if match, _ := regexp.MatchString(`^\d+$`, str); !match { var ok bool if len(str)%4 == 0 && len(str) >= 12 && !strutil.IsSameChar(str) { if value, ok = base64Conv.Decode(str); ok { resultDecode = types.DECODE_BASE64 return } } if value, ok = gzipConv.Decode(str); ok { resultDecode = types.DECODE_GZIP return } // FIXME: skip decompress with deflate due to incorrect format checking //if value, ok = decodeDeflate(str); ok { // resultDecode = types.DECODE_DEFLATE // return //} if value, ok = zstdConv.Decode(str); ok { resultDecode = types.DECODE_ZSTD return } // FIXME: skip decompress with brotli due to incorrect format checking //if value, ok = decodeBrotli(str); ok { // resultDecode = types.DECODE_BROTLI // return //} if value, ok = msgpackConv.Decode(str); ok { resultDecode = types.DECODE_MSGPACK return } // try decode with custom decoder for _, decoder := range customDecoder { if decoder.Auto { if value, ok = decoder.Decode(str); ok { resultDecode = decoder.Name return } } } } } value = str resultDecode = types.DECODE_NONE return } func viewAs(str, formatType string) (value, resultFormat string) { if len(formatType) > 0 { switch formatType { default: fallthrough case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML: value = str case types.FORMAT_JSON: if jsonStr, ok := jsonConv.Decode(str); ok { value = jsonStr } else { value = str } case types.FORMAT_HEX: if hexStr, ok := hexConv.Decode(str); ok { value = hexStr } else { value = str } case types.FORMAT_BINARY: if binStr, ok := binaryConv.Decode(str); ok { value = binStr } else { value = str } } resultFormat = formatType return } return } // 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 = jsonConv.Decode(str); ok { resultFormat = types.FORMAT_JSON return } if value, ok = yamlConv.Decode(str); ok { resultFormat = types.FORMAT_YAML return } if value, ok = xmlConv.Decode(str); ok { resultFormat = types.FORMAT_XML return } if strutil.ContainsBinary(str) { if value, ok = hexConv.Decode(str); ok { resultFormat = types.FORMAT_HEX return } } } value = str resultFormat = types.FORMAT_RAW return } func SaveAs(str, format, decode string, customDecoder []CmdConvert) (value string, err error) { value = str switch format { case types.FORMAT_JSON: if jsonStr, ok := jsonConv.Encode(str); ok { value = jsonStr } else { err = errors.New("invalid json data") return } case types.FORMAT_HEX: if hexStr, ok := hexConv.Encode(str); ok { value = hexStr } else { err = errors.New("invalid hex data") return } case types.FORMAT_BINARY: if binStr, ok := binaryConv.Encode(str); ok { value = binStr } else { err = errors.New("invalid binary data") return } } switch decode { case types.DECODE_NONE: return case types.DECODE_BASE64: value, _ = base64Conv.Encode(value) return case types.DECODE_GZIP: if gzipStr, ok := gzipConv.Encode(str); ok { value = gzipStr } else { err = errors.New("fail to build gzip") } return case types.DECODE_DEFLATE: if deflateStr, ok := deflateConv.Encode(str); ok { value = deflateStr } else { err = errors.New("fail to build deflate") } return case types.DECODE_ZSTD: if zstdStr, ok := zstdConv.Encode(str); ok { value = zstdStr } else { err = errors.New("fail to build zstd") } return case types.DECODE_BROTLI: if brotliStr, ok := brotliConv.Encode(str); ok { value = brotliStr } else { err = errors.New("fail to build brotli") } return case types.DECODE_MSGPACK: if msgpackStr, ok := msgpackConv.Encode(str); ok { value = msgpackStr } else { err = errors.New("fail to build msgpack") } return default: for _, decoder := range customDecoder { if decoder.Name == decode { if encodedStr, ok := decoder.Encode(str); ok { value = encodedStr } else { value = str } return } } } return str, nil }