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.

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