You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
5.3 KiB

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