github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/iconv/converter.go (about) 1 package iconv 2 3 /* 4 #cgo darwin LDFLAGS: -liconv 5 #cgo freebsd LDFLAGS: -liconv 6 #cgo windows LDFLAGS: -liconv 7 #include <stdlib.h> 8 #include <iconv.h> 9 10 // As of GO 1.6 passing a pointer to Go pointer, will lead to panic 11 // Therofore we use this wrapper function, to avoid passing **char directly from go 12 size_t call_iconv(iconv_t ctx, char *in, size_t *size_in, char *out, size_t *size_out){ 13 return iconv(ctx, &in, size_in, &out, size_out); 14 } 15 16 */ 17 import "C" 18 import "syscall" 19 import "unsafe" 20 21 type Converter struct { 22 context C.iconv_t 23 open bool 24 } 25 26 // NewConverter Initialize a new Converter. If fromEncoding or toEncoding are not supported by 27 // iconv then an EINVAL error will be returned. An ENOMEM error maybe returned if 28 // there is not enough memory to initialize an iconv descriptor 29 func NewConverter(fromEncoding string, toEncoding string) (converter *Converter, err error) { 30 converter = new(Converter) 31 32 // convert to C strings 33 toEncodingC := C.CString(toEncoding) 34 fromEncodingC := C.CString(fromEncoding) 35 36 // open an iconv descriptor 37 converter.context, err = C.iconv_open(toEncodingC, fromEncodingC) 38 39 // free the C Strings 40 C.free(unsafe.Pointer(toEncodingC)) 41 C.free(unsafe.Pointer(fromEncodingC)) 42 43 // check err 44 if err == nil { 45 // no error, mark the context as open 46 converter.open = true 47 } 48 49 return 50 } 51 52 // destroy is called during garbage collection 53 func (c *Converter) destroy() { 54 _ = c.Close() 55 } 56 57 // Close a Converter's iconv description explicitly 58 func (c *Converter) Close() (err error) { 59 if c.open { 60 _, err = C.iconv_close(c.context) 61 } 62 63 return 64 } 65 66 // Convert bytes from an input byte slice into a give output byte slice 67 // 68 // As many bytes that can converted and fit into the size of output will be 69 // processed and the number of bytes read for input as well as the number of 70 // bytes written to output will be returned. If not all converted bytes can fit 71 // into output and E2BIG error will also be returned. If input contains an invalid 72 // sequence of bytes for the Converter's fromEncoding an EILSEQ error will be returned 73 // 74 // For shift based output encodings, any end shift byte sequences can be generated by 75 // passing a 0 length byte slice as input. Also passing a 0 length byte slice for output 76 // will simply reset the iconv descriptor shift state without writing any bytes. 77 func (c *Converter) Convert(input []byte, output []byte) (bytesRead int, bytesWritten int, err error) { 78 // make sure we are still open 79 if c.open { 80 inputLeft := C.size_t(len(input)) 81 outputLeft := C.size_t(len(output)) 82 83 if inputLeft > 0 && outputLeft > 0 { 84 // we have to give iconv a pointer to a pointer of the underlying 85 // storage of each byte slice - so far this is the simplest 86 // way i've found to do that in Go, but it seems ugly 87 inputPointer := (*C.char)(unsafe.Pointer(&input[0])) 88 outputPointer := (*C.char)(unsafe.Pointer(&output[0])) 89 90 _, err = C.call_iconv(c.context, inputPointer, &inputLeft, outputPointer, &outputLeft) 91 92 // update byte counters 93 bytesRead = len(input) - int(inputLeft) 94 bytesWritten = len(output) - int(outputLeft) 95 } else if inputLeft == 0 && outputLeft > 0 { 96 // inputPointer will be nil, outputPointer is generated as above 97 outputPointer := (*C.char)(unsafe.Pointer(&output[0])) 98 99 _, err = C.call_iconv(c.context, nil, &inputLeft, outputPointer, &outputLeft) 100 101 // update write byte counter 102 bytesWritten = len(output) - int(outputLeft) 103 } else { 104 // both input and output are zero length, do a shift state reset 105 _, err = C.call_iconv(c.context, nil, &inputLeft, nil, &outputLeft) 106 } 107 } else { 108 err = syscall.EBADF 109 } 110 111 return bytesRead, bytesWritten, err 112 } 113 114 // ConvertString Convert an input string 115 // EILSEQ error may be returned if input contains invalid bytes for the 116 // Converter's fromEncoding. 117 func (c *Converter) ConvertString(input string) (output string, err error) { 118 // make sure we are still open 119 if c.open { 120 // construct the buffers 121 inputBuffer := []byte(input) 122 outputBuffer := make([]byte, len(inputBuffer)*2) // we use a larger buffer to help avoid resizing later 123 124 // call Convert until all input bytes are read or an error occurs 125 var bytesRead, totalBytesRead, bytesWritten, totalBytesWritten int 126 127 for totalBytesRead < len(inputBuffer) && err == nil { 128 // use the totals to create buffer slices 129 bytesRead, bytesWritten, err = c.Convert(inputBuffer[totalBytesRead:], outputBuffer[totalBytesWritten:]) 130 131 totalBytesRead += bytesRead 132 totalBytesWritten += bytesWritten 133 134 // check for the E2BIG error specifically, we can add to the output 135 // buffer to correct for it and then continue 136 if err == syscall.E2BIG { 137 // increase the size of the output buffer by another input length 138 // first, create a new buffer 139 tempBuffer := make([]byte, len(outputBuffer)+len(inputBuffer)) 140 141 // copy the existing data 142 copy(tempBuffer, outputBuffer) 143 144 // switch the buffers 145 outputBuffer = tempBuffer 146 147 // forget the error 148 err = nil 149 } 150 } 151 152 if err == nil { 153 // perform a final shift state reset 154 _, bytesWritten, err = c.Convert([]byte{}, outputBuffer[totalBytesWritten:]) 155 156 // update total count 157 totalBytesWritten += bytesWritten 158 } 159 160 // construct the final output string 161 output = string(outputBuffer[:totalBytesWritten]) 162 } else { 163 err = syscall.EBADF 164 } 165 166 return output, err 167 }