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.

182 line
4.9 KiB

  1. package iconv
  2. import (
  3. "io"
  4. "runtime"
  5. "syscall"
  6. )
  7. const (
  8. defaultWriteBufferSize = 8 * 1024
  9. minWriteBufferSize = 16
  10. )
  11. type Writer struct {
  12. destination io.Writer
  13. converter *Converter
  14. readBuffer, writeBuffer []byte
  15. readPos, writePos int
  16. }
  17. func NewWriter(destination io.Writer, fromEncoding string, toEncoding string) (*Writer, error) {
  18. return NewWriterSized(destination, fromEncoding, toEncoding, defaultWriteBufferSize)
  19. }
  20. func NewWriterFromConverter(destination io.Writer, converter *Converter) (writer *Writer) {
  21. return NewWriterFromConverterSized(destination, converter, defaultWriteBufferSize)
  22. }
  23. func NewWriterSized(destination io.Writer, fromEncoding, toEncoding string, size int) (*Writer, error) {
  24. converter, err := NewConverter(fromEncoding, toEncoding)
  25. if err != nil {
  26. return nil, err
  27. }
  28. // add a finalizer for the converter we created
  29. runtime.SetFinalizer(converter, finalizeConverter)
  30. return NewWriterFromConverterSized(destination, converter, size), nil
  31. }
  32. func NewWriterFromConverterSized(destination io.Writer, converter *Converter, size int) *Writer {
  33. if size < minWriteBufferSize {
  34. size = minWriteBufferSize
  35. }
  36. return &Writer{
  37. destination: destination,
  38. converter: converter,
  39. readBuffer: make([]byte, size),
  40. writeBuffer: make([]byte, size),
  41. }
  42. }
  43. // Implements io.Writer
  44. //
  45. // Will attempt to convert all of p into buffer. If there's not enough room in
  46. // the buffer to hold all converted bytes, the buffer will be flushed and p will
  47. // continue to be processed. Close should be called on a writer when finished
  48. // with all writes, to ensure final shift sequences are written and buffer is
  49. // flushed to underlying io.Writer.
  50. //
  51. // Can return all errors that Convert can, as well as any errors from Flush. Note
  52. // that some errors from Convert are suppressed if we continue making progress
  53. // on p.
  54. func (w *Writer) Write(p []byte) (int, error) {
  55. var totalBytesRead, bytesRead, bytesWritten int
  56. var err error
  57. if w.readPos == 0 || len(p) == 0 {
  58. bytesRead, bytesWritten, err = w.converter.Convert(p, w.writeBuffer[w.writePos:])
  59. totalBytesRead += bytesRead
  60. w.writePos += bytesWritten
  61. w.readPos = 0
  62. } else {
  63. // we have left over bytes from previous write that weren't complete and there's at least
  64. // one byte being written, fill read buffer with p and try to convert, if we make progress
  65. // we can continue conversion from p itself
  66. bytesCopied := copy(w.readBuffer[w.readPos:], p)
  67. bytesRead, bytesWritten, err = w.converter.Convert(w.readBuffer[:w.readPos+bytesCopied], w.writeBuffer[w.writePos:])
  68. // if we made no progress, give up
  69. if bytesRead <= w.readPos {
  70. return 0, err
  71. }
  72. bytesRead -= w.readPos
  73. totalBytesRead += bytesRead
  74. w.readPos = 0
  75. w.writePos += bytesWritten
  76. }
  77. // try to process all of p - lots of io functions don't like short writes.
  78. //
  79. // There are a few error cases we need to treat specially, as long as we've
  80. // made progress on p, E2BIG and EILSEQ should not be fatal. EINVAL isn't
  81. // fatal as long as the rest of p fits in our buffers.
  82. for err != nil && bytesRead > 0 {
  83. switch err {
  84. case syscall.E2BIG:
  85. err = w.Flush()
  86. case syscall.EILSEQ:
  87. // IGNORE suffix still reports the error on convert
  88. err = nil
  89. // if no more bytes, don't do an empty convert (resets state)
  90. if totalBytesRead == len(p) {
  91. break
  92. }
  93. case syscall.EINVAL:
  94. // if the rest of p fits in read buffer copy it there
  95. if len(p[totalBytesRead:]) <= len(w.readBuffer) {
  96. w.readPos = copy(w.readBuffer, p[totalBytesRead:])
  97. totalBytesRead += w.readPos
  98. break
  99. }
  100. }
  101. // if not an ignoreable err or Flush err
  102. if err != nil {
  103. break
  104. }
  105. bytesRead, bytesWritten, err = w.converter.Convert(p[totalBytesRead:], w.writeBuffer[w.writePos:])
  106. totalBytesRead += bytesRead
  107. w.writePos += bytesWritten
  108. }
  109. return totalBytesRead, err
  110. }
  111. // Attempt to write any buffered data to destination writer. Returns error from
  112. // Write call or io.ErrShortWrite if Write didn't report an error but also didn't
  113. // accept all bytes given.
  114. func (w *Writer) Flush() error {
  115. if w.readPos < w.writePos {
  116. bytesWritten, err := w.destination.Write(w.writeBuffer[:w.writePos])
  117. if bytesWritten < 0 {
  118. panic("iconv: writer returned negative count from Write")
  119. }
  120. if bytesWritten > 0 {
  121. w.writePos = copy(w.writeBuffer, w.writeBuffer[bytesWritten:w.writePos])
  122. }
  123. if err == nil && w.writePos > 0 {
  124. err = io.ErrShortWrite
  125. }
  126. return err
  127. }
  128. return nil
  129. }
  130. // Perform a final write with empty buffer, which allows iconv to close any shift
  131. // sequences. A Flush is performed if needed.
  132. func (w *Writer) Close() error {
  133. _, err := w.Write(nil)
  134. if err != nil {
  135. return err
  136. }
  137. return w.Flush()
  138. }
  139. // Reset state and direct writes to a new destination writer
  140. func (w *Writer) Reset(destination io.Writer) {
  141. w.converter.Reset()
  142. *w = Writer{
  143. destination: destination,
  144. converter: w.converter,
  145. readBuffer: w.readBuffer,
  146. writeBuffer: w.writeBuffer,
  147. }
  148. }