github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/jsoni/extra/binary_as_string_codec.go (about)

     1  package extra
     2  
     3  import (
     4  	"context"
     5  	"unicode/utf8"
     6  	"unsafe"
     7  
     8  	"github.com/bingoohuang/gg/pkg/jsoni"
     9  	"github.com/modern-go/reflect2"
    10  )
    11  
    12  // safeSet holds the value true if the ASCII character with the given array
    13  // position can be represented inside a JSON string without any further
    14  // escaping.
    15  //
    16  // All values are true except for the ASCII control characters (0-31), the
    17  // double quote ("), and the backslash character ("\").
    18  var safeSet = [utf8.RuneSelf]bool{
    19  	' ':      true,
    20  	'!':      true,
    21  	'"':      false,
    22  	'#':      true,
    23  	'$':      true,
    24  	'%':      true,
    25  	'&':      true,
    26  	'\'':     true,
    27  	'(':      true,
    28  	')':      true,
    29  	'*':      true,
    30  	'+':      true,
    31  	',':      true,
    32  	'-':      true,
    33  	'.':      true,
    34  	'/':      true,
    35  	'0':      true,
    36  	'1':      true,
    37  	'2':      true,
    38  	'3':      true,
    39  	'4':      true,
    40  	'5':      true,
    41  	'6':      true,
    42  	'7':      true,
    43  	'8':      true,
    44  	'9':      true,
    45  	':':      true,
    46  	';':      true,
    47  	'<':      true,
    48  	'=':      true,
    49  	'>':      true,
    50  	'?':      true,
    51  	'@':      true,
    52  	'A':      true,
    53  	'B':      true,
    54  	'C':      true,
    55  	'D':      true,
    56  	'E':      true,
    57  	'F':      true,
    58  	'G':      true,
    59  	'H':      true,
    60  	'I':      true,
    61  	'J':      true,
    62  	'K':      true,
    63  	'L':      true,
    64  	'M':      true,
    65  	'N':      true,
    66  	'O':      true,
    67  	'P':      true,
    68  	'Q':      true,
    69  	'R':      true,
    70  	'S':      true,
    71  	'T':      true,
    72  	'U':      true,
    73  	'V':      true,
    74  	'W':      true,
    75  	'X':      true,
    76  	'Y':      true,
    77  	'Z':      true,
    78  	'[':      true,
    79  	'\\':     false,
    80  	']':      true,
    81  	'^':      true,
    82  	'_':      true,
    83  	'`':      true,
    84  	'a':      true,
    85  	'b':      true,
    86  	'c':      true,
    87  	'd':      true,
    88  	'e':      true,
    89  	'f':      true,
    90  	'g':      true,
    91  	'h':      true,
    92  	'i':      true,
    93  	'j':      true,
    94  	'k':      true,
    95  	'l':      true,
    96  	'm':      true,
    97  	'n':      true,
    98  	'o':      true,
    99  	'p':      true,
   100  	'q':      true,
   101  	'r':      true,
   102  	's':      true,
   103  	't':      true,
   104  	'u':      true,
   105  	'v':      true,
   106  	'w':      true,
   107  	'x':      true,
   108  	'y':      true,
   109  	'z':      true,
   110  	'{':      true,
   111  	'|':      true,
   112  	'}':      true,
   113  	'~':      true,
   114  	'\u007f': true,
   115  }
   116  
   117  var binaryType = jsoni.PtrElem((*[]byte)(nil))
   118  
   119  type BinaryAsStringExtension struct {
   120  	jsoni.DummyExtension
   121  }
   122  
   123  func (extension *BinaryAsStringExtension) CreateEncoder(typ reflect2.Type) jsoni.ValEncoder {
   124  	if typ == binaryType {
   125  		return &binaryAsStringCodec{}
   126  	}
   127  	return nil
   128  }
   129  
   130  func (extension *BinaryAsStringExtension) CreateDecoder(typ reflect2.Type) jsoni.ValDecoder {
   131  	if typ == binaryType {
   132  		return &binaryAsStringCodec{}
   133  	}
   134  	return nil
   135  }
   136  
   137  type binaryAsStringCodec struct{}
   138  
   139  func (codec *binaryAsStringCodec) Decode(_ context.Context, ptr unsafe.Pointer, iter *jsoni.Iterator) {
   140  	rawBytes := iter.ReadStringAsSlice()
   141  	bytes := make([]byte, 0, len(rawBytes))
   142  	for i := 0; i < len(rawBytes); i++ {
   143  		b := rawBytes[i]
   144  		if b == '\\' {
   145  			b2 := rawBytes[i+1]
   146  			if b2 != '\\' {
   147  				iter.ReportError("decode binary as string", `\\x is only supported escape`)
   148  				return
   149  			}
   150  			b3 := rawBytes[i+2]
   151  			if b3 != 'x' {
   152  				iter.ReportError("decode binary as string", `\\x is only supported escape`)
   153  				return
   154  			}
   155  			b4 := rawBytes[i+3]
   156  			b5 := rawBytes[i+4]
   157  			i += 4
   158  			b = readHex(iter, b4, b5)
   159  		}
   160  		bytes = append(bytes, b)
   161  	}
   162  	*(*[]byte)(ptr) = bytes
   163  }
   164  
   165  func (codec *binaryAsStringCodec) IsEmpty(_ context.Context, ptr unsafe.Pointer, _ bool) bool {
   166  	return len(*((*[]byte)(ptr))) == 0
   167  }
   168  
   169  func (codec *binaryAsStringCodec) Encode(_ context.Context, ptr unsafe.Pointer, stream *jsoni.Stream) {
   170  	newBuffer := writeBytes(stream.Buffer(), *(*[]byte)(ptr))
   171  	stream.SetBuffer(newBuffer)
   172  }
   173  
   174  func readHex(iter *jsoni.Iterator, b1, b2 byte) byte {
   175  	var ret byte
   176  	if b1 >= '0' && b1 <= '9' {
   177  		ret = b1 - '0'
   178  	} else if b1 >= 'a' && b1 <= 'f' {
   179  		ret = b1 - 'a' + 10
   180  	} else {
   181  		iter.ReportError("read hex", "expects 0~9 or a~f, but found "+string([]byte{b1}))
   182  		return 0
   183  	}
   184  	ret *= 16
   185  	if b2 >= '0' && b2 <= '9' {
   186  		ret += b2 - '0'
   187  	} else if b2 >= 'a' && b2 <= 'f' {
   188  		ret += b2 - 'a' + 10
   189  	} else {
   190  		iter.ReportError("read hex", "expects 0~9 or a~f, but found "+string([]byte{b2}))
   191  		return 0
   192  	}
   193  	return ret
   194  }
   195  
   196  var hex = "0123456789abcdef"
   197  
   198  func writeBytes(space []byte, s []byte) []byte {
   199  	space = append(space, '"')
   200  	// write string, the fast path, without utf8 and escape support
   201  	var i int
   202  	var c byte
   203  	for i, c = range s {
   204  		if c < utf8.RuneSelf && safeSet[c] {
   205  			space = append(space, c)
   206  		} else {
   207  			break
   208  		}
   209  	}
   210  	if i == len(s)-1 {
   211  		space = append(space, '"')
   212  		return space
   213  	}
   214  	return writeBytesSlowPath(space, s[i:])
   215  }
   216  
   217  func writeBytesSlowPath(space []byte, s []byte) []byte {
   218  	start := 0
   219  	// for the remaining parts, we process them char by char
   220  	var i int
   221  	var b byte
   222  	for i, b = range s {
   223  		if b >= utf8.RuneSelf {
   224  			space = append(space, '\\', '\\', 'x', hex[b>>4], hex[b&0xF])
   225  			start = i + 1
   226  			continue
   227  		}
   228  		if safeSet[b] {
   229  			continue
   230  		}
   231  		if start < i {
   232  			space = append(space, s[start:i]...)
   233  		}
   234  		space = append(space, '\\', '\\', 'x', hex[b>>4], hex[b&0xF])
   235  		start = i + 1
   236  	}
   237  	if start < len(s) {
   238  		space = append(space, s[start:]...)
   239  	}
   240  	return append(space, '"')
   241  }