Skip to content

Commit 61ff658

Browse files
authored
Merge pull request #7 from slash3b/misc_bug_fixes
do not copy whole input and misc
2 parents b0d2bb4 + 91935ce commit 61ff658

2 files changed

Lines changed: 36 additions & 49 deletions

File tree

utfbom.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ var _ io.Reader = (*Reader)(nil)
1919
// ErrRead helps to trace error origin.
2020
var ErrRead = errors.New("utfbom: I/O error during BOM processing")
2121

22+
const maxBOMLen = 4
23+
2224
// Encoding is a character encoding standard.
2325
type Encoding int
2426

@@ -59,6 +61,10 @@ const (
5961
// - UTF-32 Big Endian (BOM: 0x00 0x00 0xfe 0xff)
6062
// - UTF-32 Little Endian (BOM: 0xff 0xfe 0x00 0x00)
6163
func DetectEncoding[T ~string | ~[]byte](input T) Encoding {
64+
if len(input) > maxBOMLen {
65+
input = input[:maxBOMLen]
66+
}
67+
6268
b := []byte(input)
6369

6470
if len(b) < 2 {
@@ -149,31 +155,28 @@ func (e Encoding) Bytes() []byte {
149155
// Trim removes the BOM prefix from the input.
150156
// Supports string or []byte inputs and returns the same type without the BOM.
151157
func Trim[T ~string | ~[]byte](input T) (T, Encoding) {
152-
b := []byte(input)
153-
enc := DetectEncoding(b)
158+
enc := DetectEncoding(input)
154159

155160
if enc == Unknown {
156161
return input, enc
157162
}
158163

159-
return T(b[enc.Len():]), enc
164+
return input[enc.Len():], enc
160165
}
161166

162167
// Prepend adds the corresponding Byte Order Mark (BOM) for a given encoding
163168
// to the beginning of a string or byte slice.
164-
// If the provided encoding is Unknown, the input is returned unmodified.
169+
// The input is returned unmodified if enc is Unknown or if the input already has any BOM.
165170
func Prepend[T ~string | ~[]byte](input T, enc Encoding) T {
166171
if enc == Unknown {
167172
return input
168173
}
169174

170-
b := []byte(input)
171-
172-
if DetectEncoding(b) != Unknown {
175+
if DetectEncoding(input) != Unknown {
173176
return input
174177
}
175178

176-
return T(append(enc.Bytes(), b...))
179+
return T(append(enc.Bytes(), []byte(input)...))
177180
}
178181

179182
// Reader implements automatic BOM (Unicode Byte Order Mark) checking and
@@ -201,8 +204,6 @@ func NewReader(rd io.Reader) *Reader {
201204
// On the first call, it detects and removes any Byte Order Mark (BOM).
202205
// Subsequent calls delegate directly to the underlying Reader.
203206
func (r *Reader) Read(buf []byte) (int, error) {
204-
const maxBOMLen = 4
205-
206207
if len(buf) == 0 {
207208
return 0, nil
208209
}

utfbom_test.go

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/csv"
66
"encoding/hex"
7+
"errors"
78
"fmt"
89
"io"
910
"strings"
@@ -218,41 +219,23 @@ func TestEncoding_Len(t *testing.T) {
218219
t.Parallel()
219220

220221
testCases := []struct {
222+
name string
221223
enc utfbom.Encoding
222224
expected int
223225
}{
224-
{
225-
enc: utfbom.Unknown,
226-
expected: 0,
227-
},
228-
{
229-
enc: utfbom.UTF8,
230-
expected: 3,
231-
},
232-
{
233-
enc: utfbom.UTF16BigEndian,
234-
expected: 2,
235-
},
236-
{
237-
enc: utfbom.UTF16LittleEndian,
238-
expected: 2,
239-
},
240-
{
241-
enc: utfbom.UTF32BigEndian,
242-
expected: 4,
243-
},
244-
{
245-
enc: utfbom.UTF32LittleEndian,
246-
expected: 4,
247-
},
248-
{
249-
enc: 999,
250-
expected: 0,
251-
},
226+
{"Unknown", utfbom.Unknown, 0},
227+
{"UTF8", utfbom.UTF8, 3},
228+
{"UTF16BigEndian", utfbom.UTF16BigEndian, 2},
229+
{"UTF16LittleEndian", utfbom.UTF16LittleEndian, 2},
230+
{"UTF32BigEndian", utfbom.UTF32BigEndian, 4},
231+
{"UTF32LittleEndian", utfbom.UTF32LittleEndian, 4},
232+
{"InvalidEncoding", 999, 0},
252233
}
253234

254235
for _, tc := range testCases {
255-
be.Equal(t, tc.enc.Len(), tc.expected)
236+
t.Run(tc.name, func(t *testing.T) {
237+
be.Equal(t, tc.enc.Len(), tc.expected)
238+
})
256239
}
257240
}
258241

@@ -313,16 +296,6 @@ func TestReader_StringWithoutBOM(t *testing.T) {
313296
be.Err(t, iotest.TestReader(rd, []byte(nobomstring)), nil)
314297
}
315298

316-
func TestReader_UsualReader(t *testing.T) {
317-
t.Parallel()
318-
319-
bomPrefixedStringReader := strings.NewReader(teststring)
320-
321-
rd := utfbom.NewReader(bomPrefixedStringReader)
322-
323-
be.Err(t, iotest.TestReader(rd, []byte(teststring[3:])), nil)
324-
}
325-
326299
func TestReader_OneByteReader(t *testing.T) {
327300
t.Parallel()
328301

@@ -570,6 +543,19 @@ func TestPrepend_TypeAliases(t *testing.T) {
570543
})
571544
}
572545

546+
// TestReader_UnderlyingReaderError verifies that when the underlying reader
547+
// returns a non-EOF error during BOM detection, it is wrapped with ErrRead.
548+
func TestReader_UnderlyingReaderError(t *testing.T) {
549+
t.Parallel()
550+
551+
rd := utfbom.NewReader(iotest.ErrReader(errors.New("disk failure")))
552+
553+
buf := make([]byte, 10)
554+
n, err := rd.Read(buf)
555+
be.Equal(t, 0, n)
556+
be.True(t, errors.Is(err, utfbom.ErrRead))
557+
}
558+
573559
func TestNewReader_NilPanics(t *testing.T) {
574560
t.Parallel()
575561

0 commit comments

Comments
 (0)