package iconv import ( "io" "runtime" "syscall" ) const ( defaultWriteBufferSize = 8 * 1024 minWriteBufferSize = 16 ) type Writer struct { destination io.Writer converter *Converter readBuffer, writeBuffer []byte readPos, writePos int } func NewWriter(destination io.Writer, fromEncoding string, toEncoding string) (*Writer, error) { return NewWriterSized(destination, fromEncoding, toEncoding, defaultWriteBufferSize) } func NewWriterFromConverter(destination io.Writer, converter *Converter) (writer *Writer) { return NewWriterFromConverterSized(destination, converter, defaultWriteBufferSize) } func NewWriterSized(destination io.Writer, fromEncoding, toEncoding string, size int) (*Writer, error) { converter, err := NewConverter(fromEncoding, toEncoding) if err != nil { return nil, err } // add a finalizer for the converter we created runtime.SetFinalizer(converter, finalizeConverter) return NewWriterFromConverterSized(destination, converter, size), nil } func NewWriterFromConverterSized(destination io.Writer, converter *Converter, size int) *Writer { if size < minWriteBufferSize { size = minWriteBufferSize } return &Writer{ destination: destination, converter: converter, readBuffer: make([]byte, size), writeBuffer: make([]byte, size), } } // Implements io.Writer // // Will attempt to convert all of p into buffer. If there's not enough room in // the buffer to hold all converted bytes, the buffer will be flushed and p will // continue to be processed. Close should be called on a writer when finished // with all writes, to ensure final shift sequences are written and buffer is // flushed to underlying io.Writer. // // Can return all errors that Convert can, as well as any errors from Flush. Note // that some errors from Convert are suppressed if we continue making progress // on p. func (w *Writer) Write(p []byte) (int, error) { var totalBytesRead, bytesRead, bytesWritten int var err error if w.readPos == 0 || len(p) == 0 { bytesRead, bytesWritten, err = w.converter.Convert(p, w.writeBuffer[w.writePos:]) totalBytesRead += bytesRead w.writePos += bytesWritten w.readPos = 0 } else { // we have left over bytes from previous write that weren't complete and there's at least // one byte being written, fill read buffer with p and try to convert, if we make progress // we can continue conversion from p itself bytesCopied := copy(w.readBuffer[w.readPos:], p) bytesRead, bytesWritten, err = w.converter.Convert(w.readBuffer[:w.readPos+bytesCopied], w.writeBuffer[w.writePos:]) // if we made no progress, give up if bytesRead <= w.readPos { return 0, err } bytesRead -= w.readPos totalBytesRead += bytesRead w.readPos = 0 w.writePos += bytesWritten } // try to process all of p - lots of io functions don't like short writes. // // There are a few error cases we need to treat specially, as long as we've // made progress on p, E2BIG and EILSEQ should not be fatal. EINVAL isn't // fatal as long as the rest of p fits in our buffers. for err != nil && bytesRead > 0 { switch err { case syscall.E2BIG: err = w.Flush() case syscall.EILSEQ: // IGNORE suffix still reports the error on convert err = nil // if no more bytes, don't do an empty convert (resets state) if totalBytesRead == len(p) { break } case syscall.EINVAL: // if the rest of p fits in read buffer copy it there if len(p[totalBytesRead:]) <= len(w.readBuffer) { w.readPos = copy(w.readBuffer, p[totalBytesRead:]) totalBytesRead += w.readPos break } } // if not an ignoreable err or Flush err if err != nil { break } bytesRead, bytesWritten, err = w.converter.Convert(p[totalBytesRead:], w.writeBuffer[w.writePos:]) totalBytesRead += bytesRead w.writePos += bytesWritten } return totalBytesRead, err } // Attempt to write any buffered data to destination writer. Returns error from // Write call or io.ErrShortWrite if Write didn't report an error but also didn't // accept all bytes given. func (w *Writer) Flush() error { if w.readPos < w.writePos { bytesWritten, err := w.destination.Write(w.writeBuffer[:w.writePos]) if bytesWritten < 0 { panic("iconv: writer returned negative count from Write") } if bytesWritten > 0 { w.writePos = copy(w.writeBuffer, w.writeBuffer[bytesWritten:w.writePos]) } if err == nil && w.writePos > 0 { err = io.ErrShortWrite } return err } return nil } // Perform a final write with empty buffer, which allows iconv to close any shift // sequences. A Flush is performed if needed. func (w *Writer) Close() error { _, err := w.Write(nil) if err != nil { return err } return w.Flush() } // Reset state and direct writes to a new destination writer func (w *Writer) Reset(destination io.Writer) { w.converter.Reset() *w = Writer{ destination: destination, converter: w.converter, readBuffer: w.readBuffer, writeBuffer: w.writeBuffer, } }