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  }