Skip to content

Commit c83669d

Browse files
authored
Merge pull request #189 from redis-rb/fix-utf8-response
Fix BufferedIO to search with `byteindex`
2 parents 02e44b1 + 2886b46 commit c83669d

File tree

3 files changed

+84
-35
lines changed

3 files changed

+84
-35
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,6 @@ Style/CaseLikeIf:
208208

209209
Style/EmptyMethod:
210210
Enabled: false
211+
212+
Style/AccessModifierDeclarations:
213+
Enabled: false

lib/redis_client/ruby_connection/buffered_io.rb

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,80 @@ class BufferedIO
1010

1111
attr_accessor :read_timeout, :write_timeout
1212

13-
def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
14-
@io = io
15-
@buffer = +""
16-
@offset = 0
17-
@chunk_size = chunk_size
18-
@read_timeout = read_timeout
19-
@write_timeout = write_timeout
20-
@blocking_reads = false
13+
if String.method_defined?(:byteindex) # Ruby 3.2+
14+
ENCODING = Encoding::UTF_8
15+
16+
def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
17+
@io = io
18+
@buffer = +""
19+
@offset = 0
20+
@chunk_size = chunk_size
21+
@read_timeout = read_timeout
22+
@write_timeout = write_timeout
23+
@blocking_reads = false
24+
end
25+
26+
def gets_chomp
27+
fill_buffer(false) if @offset >= @buffer.bytesize
28+
until eol_index = @buffer.byteindex(EOL, @offset)
29+
fill_buffer(false)
30+
end
31+
32+
line = @buffer.byteslice(@offset, eol_index - @offset)
33+
@offset = eol_index + EOL_SIZE
34+
line
35+
end
36+
37+
def read_chomp(bytes)
38+
ensure_remaining(bytes + EOL_SIZE)
39+
str = @buffer.byteslice(@offset, bytes)
40+
@offset += bytes + EOL_SIZE
41+
str
42+
end
43+
44+
private def ensure_line
45+
fill_buffer(false) if @offset >= @buffer.bytesize
46+
until @buffer.byteindex(EOL, @offset)
47+
fill_buffer(false)
48+
end
49+
end
50+
else
51+
ENCODING = Encoding::BINARY
52+
53+
def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
54+
@io = io
55+
@buffer = "".b
56+
@offset = 0
57+
@chunk_size = chunk_size
58+
@read_timeout = read_timeout
59+
@write_timeout = write_timeout
60+
@blocking_reads = false
61+
end
62+
63+
def gets_chomp
64+
fill_buffer(false) if @offset >= @buffer.bytesize
65+
until eol_index = @buffer.index(EOL, @offset)
66+
fill_buffer(false)
67+
end
68+
69+
line = @buffer.byteslice(@offset, eol_index - @offset)
70+
@offset = eol_index + EOL_SIZE
71+
line
72+
end
73+
74+
def read_chomp(bytes)
75+
ensure_remaining(bytes + EOL_SIZE)
76+
str = @buffer.byteslice(@offset, bytes)
77+
@offset += bytes + EOL_SIZE
78+
str.force_encoding(Encoding::UTF_8)
79+
end
80+
81+
private def ensure_line
82+
fill_buffer(false) if @offset >= @buffer.bytesize
83+
until @buffer.index(EOL, @offset)
84+
fill_buffer(false)
85+
end
86+
end
2187
end
2288

2389
def close
@@ -90,17 +156,6 @@ def getbyte
90156
byte
91157
end
92158

93-
def gets_chomp
94-
fill_buffer(false) if @offset >= @buffer.bytesize
95-
until eol_index = @buffer.index(EOL, @offset)
96-
fill_buffer(false)
97-
end
98-
99-
line = @buffer.byteslice(@offset, eol_index - @offset)
100-
@offset = eol_index + EOL_SIZE
101-
line
102-
end
103-
104159
def gets_integer
105160
int = 0
106161
offset = @offset
@@ -124,22 +179,8 @@ def gets_integer
124179
int
125180
end
126181

127-
def read_chomp(bytes)
128-
ensure_remaining(bytes + EOL_SIZE)
129-
str = @buffer.byteslice(@offset, bytes)
130-
@offset += bytes + EOL_SIZE
131-
str
132-
end
133-
134182
private
135183

136-
def ensure_line
137-
fill_buffer(false) if @offset >= @buffer.bytesize
138-
until @buffer.index(EOL, @offset)
139-
fill_buffer(false)
140-
end
141-
end
142-
143184
def ensure_remaining(bytes)
144185
needed = bytes - (@buffer.bytesize - @offset)
145186
if needed > 0
@@ -171,9 +212,9 @@ def fill_buffer(strict, size = @chunk_size)
171212
if empty_buffer
172213
@offset = start
173214
empty_buffer = false
174-
@buffer.force_encoding(Encoding::UTF_8) unless @buffer.encoding == Encoding::UTF_8
215+
@buffer.force_encoding(ENCODING) unless @buffer.encoding == ENCODING
175216
else
176-
@buffer << bytes.force_encoding(Encoding::UTF_8)
217+
@buffer << bytes.force_encoding(ENCODING)
177218
end
178219
remaining -= bytes.bytesize
179220
return if !strict || remaining <= 0

test/redis_client/resp3_test.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def test_load_array
108108
assert_parses [1, 2, 3], "*3\r\n:1\r\n:2\r\n:3\r\n"
109109
end
110110

111+
def test_load_multibyte_chars
112+
# Check that the buffer is properly operating over bytes and not characters
113+
assert_parses ["۪۪", 2], "*2\r\n$12\r\n۪۪\r\n:2\r\n"
114+
end
115+
111116
def test_load_set
112117
assert_parses ['orange', 'apple', true, 100, 999], "~5\r\n+orange\r\n+apple\r\n#t\r\n:100\r\n:999\r\n"
113118
end

0 commit comments

Comments
 (0)