From 1ad5547335fd2caf31dfa6b5846d01699e693b5f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sun, 11 Aug 2013 01:30:52 +0800 Subject: [PATCH] io.c: IO.copy_stream should write in binary mode. This patch makes `IO.copy_stream' always copy in binary mode, fixing the following scenario: require 'tempfile' require 'stringio' Encoding.default_internal = 'UTF-8' dst = Tempfile.new('out') dst.binmode # before this patch it raises: # in `write': "\xDE" from ASCII-8BIT to UTF-8 # (Encoding::UndefinedConversionError) IO.copy_stream(StringIO.new("\xDE\xAD\xBE\xEF"), dst) The other way to fix this would be trying to preserve the file mode from Tempfile instead of always writing in binary mode. However, this won't be the case for other objects responding to `to_path'. I think as we're treating destination as streams, we would always want writing in binary. So I guess this is ok. --- io.c | 1 + test/ruby/test_io.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/io.c b/io.c index 8678af7..97da92f 100644 --- a/io.c +++ b/io.c @@ -10172,6 +10172,7 @@ copy_stream_body(VALUE arg) args[1] = INT2NUM(oflags); args[2] = INT2FIX(0666); dst_io = rb_class_new_instance(3, args, rb_cFile); + rb_io_binmode_m(dst_io); stp->dst = dst_io; stp->close_dst = 1; } diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 5cda6c9..ebf2d52 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -857,6 +857,23 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_to_obj_with_to_path_in_binmode + mkcdtmpdir { + EnvUtil.with_default_internal(Encoding::UTF_8) do + # StringIO to object with to_path + bytes = "\xDE\xAD\xBE\xEF".force_encoding(Encoding::ASCII_8BIT) + src = StringIO.new(bytes) + dst = Object.new + def dst.to_path + "qux" + end + IO.copy_stream(src, dst) + assert_equal(bytes, File.binread("qux")) + assert_equal(4, src.pos) + end + } + end + class Rot13IO def initialize(io) @io = io -- 1.8.3.4