Skip to content

Commit 8389d2e

Browse files
committed
Add Go license to logger_strings.go
1 parent 04e46e6 commit 8389d2e

File tree

3 files changed

+367
-47
lines changed

3 files changed

+367
-47
lines changed

middleware/logger_strings.go

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// SPDX-FileCopyrightText: Copyright 2010 The Go Authors
3+
//
14
// Copyright 2010 The Go Authors. All rights reserved.
25
// Use of this source code is governed by a BSD-style
36
// license that can be found in the LICENSE file.
7+
//
8+
//
9+
// Go LICENSE https://raw.githubusercontent.com/golang/go/36bca3166e18db52687a4d91ead3f98ffe6d00b8/LICENSE
10+
/**
11+
Copyright 2009 The Go Authors.
12+
13+
Redistribution and use in source and binary forms, with or without
14+
modification, are permitted provided that the following conditions are
15+
met:
16+
17+
* Redistributions of source code must retain the above copyright
18+
notice, this list of conditions and the following disclaimer.
19+
* Redistributions in binary form must reproduce the above
20+
copyright notice, this list of conditions and the following disclaimer
21+
in the documentation and/or other materials provided with the
22+
distribution.
23+
* Neither the name of Google LLC nor the names of its
24+
contributors may be used to endorse or promote products derived from
25+
this software without specific prior written permission.
26+
27+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38+
*/
439

540
package middleware
641

@@ -37,6 +72,7 @@ func writeJSONSafeString(buf *bytes.Buffer, src string) (int, error) {
3772
}
3873
case '\b':
3974
n, err := buf.Write([]byte{'\\', 'b'})
75+
written += n
4076
if err != nil {
4177
return n, err
4278
}
@@ -47,7 +83,7 @@ func writeJSONSafeString(buf *bytes.Buffer, src string) (int, error) {
4783
return written, err
4884
}
4985
case '\n':
50-
n, err := buf.Write([]byte{'\\', 'f'})
86+
n, err := buf.Write([]byte{'\\', 'n'})
5187
written += n
5288
if err != nil {
5389
return written, err
@@ -66,10 +102,6 @@ func writeJSONSafeString(buf *bytes.Buffer, src string) (int, error) {
66102
}
67103
default:
68104
// This encodes bytes < 0x20 except for \b, \f, \n, \r and \t.
69-
// If escapeHTML is set, it also escapes <, >, and &
70-
// because they can lead to security holes when
71-
// user-controlled strings are rendered into JSON
72-
// and served to some browsers.
73105
n, err := buf.Write([]byte{'\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]})
74106
written += n
75107
if err != nil {
@@ -80,10 +112,6 @@ func writeJSONSafeString(buf *bytes.Buffer, src string) (int, error) {
80112
start = i
81113
continue
82114
}
83-
// TODO(https://go.dev/issue/56948): Use generic utf8 functionality.
84-
// For now, cast only a small portion of byte slices to a string
85-
// so that it can be stack allocated. This slows down []byte slightly
86-
// due to the extra copy, but keeps string performance roughly the same.
87115
srcN := min(len(src)-i, utf8.UTFMax)
88116
c, size := utf8.DecodeRuneInString(src[i : i+srcN])
89117
if c == utf8.RuneError && size == 1 {
@@ -101,29 +129,6 @@ func writeJSONSafeString(buf *bytes.Buffer, src string) (int, error) {
101129
start = i
102130
continue
103131
}
104-
// U+2028 is LINE SEPARATOR.
105-
// U+2029 is PARAGRAPH SEPARATOR.
106-
// They are both technically valid characters in JSON strings,
107-
// but don't work in JSONP, which has to be evaluated as JavaScript,
108-
// and can lead to security holes there. It is valid JSON to
109-
// escape them, so we do so unconditionally.
110-
// See https://en.wikipedia.org/wiki/JSON#Safety.
111-
if c == '\u2028' || c == '\u2029' {
112-
n, err := buf.Write([]byte(src[start:i]))
113-
written += n
114-
if err != nil {
115-
return written, err
116-
}
117-
n, err = buf.Write([]byte{'\\', 'u', '2', '0', '2', hex[c&0xF]})
118-
written += n
119-
if err != nil {
120-
return written, err
121-
}
122-
123-
i += size
124-
start = i
125-
continue
126-
}
127132
i += size
128133
}
129134
n, err := buf.Write([]byte(src[start:]))

middleware/logger_strings_test.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package middleware
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestWriteJSONSafeString(t *testing.T) {
11+
testCases := []struct {
12+
name string
13+
whenInput string
14+
expect string
15+
expectN int
16+
}{
17+
// Basic cases
18+
{
19+
name: "empty string",
20+
whenInput: "",
21+
expect: "",
22+
expectN: 0,
23+
},
24+
{
25+
name: "simple ASCII without special chars",
26+
whenInput: "hello",
27+
expect: "hello",
28+
expectN: 5,
29+
},
30+
{
31+
name: "single character",
32+
whenInput: "a",
33+
expect: "a",
34+
expectN: 1,
35+
},
36+
{
37+
name: "alphanumeric",
38+
whenInput: "Hello123World",
39+
expect: "Hello123World",
40+
expectN: 13,
41+
},
42+
43+
// Special character escaping
44+
{
45+
name: "backslash",
46+
whenInput: `path\to\file`,
47+
expect: `path\\to\\file`,
48+
expectN: 14,
49+
},
50+
{
51+
name: "double quote",
52+
whenInput: `say "hello"`,
53+
expect: `say \"hello\"`,
54+
expectN: 13,
55+
},
56+
{
57+
name: "backslash and quote combined",
58+
whenInput: `a\b"c`,
59+
expect: `a\\b\"c`,
60+
expectN: 7,
61+
},
62+
{
63+
name: "single backslash",
64+
whenInput: `\`,
65+
expect: `\\`,
66+
expectN: 2,
67+
},
68+
{
69+
name: "single quote",
70+
whenInput: `"`,
71+
expect: `\"`,
72+
expectN: 2,
73+
},
74+
75+
// Control character escaping
76+
{
77+
name: "backspace",
78+
whenInput: "hello\bworld",
79+
expect: `hello\bworld`,
80+
expectN: 12,
81+
},
82+
{
83+
name: "form feed",
84+
whenInput: "hello\fworld",
85+
expect: `hello\fworld`,
86+
expectN: 12,
87+
},
88+
{
89+
name: "newline",
90+
whenInput: "hello\nworld",
91+
expect: `hello\nworld`,
92+
expectN: 12,
93+
},
94+
{
95+
name: "carriage return",
96+
whenInput: "hello\rworld",
97+
expect: `hello\rworld`,
98+
expectN: 12,
99+
},
100+
{
101+
name: "tab",
102+
whenInput: "hello\tworld",
103+
expect: `hello\tworld`,
104+
expectN: 12,
105+
},
106+
{
107+
name: "multiple newlines",
108+
whenInput: "line1\nline2\nline3",
109+
expect: `line1\nline2\nline3`,
110+
expectN: 19,
111+
},
112+
113+
// Low control characters (< 0x20)
114+
{
115+
name: "null byte",
116+
whenInput: "hello\x00world",
117+
expect: `hello\u0000world`,
118+
expectN: 16,
119+
},
120+
{
121+
name: "control character 0x01",
122+
whenInput: "test\x01value",
123+
expect: `test\u0001value`,
124+
expectN: 15,
125+
},
126+
{
127+
name: "control character 0x0e",
128+
whenInput: "test\x0evalue",
129+
expect: `test\u000evalue`,
130+
expectN: 15,
131+
},
132+
{
133+
name: "control character 0x1f",
134+
whenInput: "test\x1fvalue",
135+
expect: `test\u001fvalue`,
136+
expectN: 15,
137+
},
138+
{
139+
name: "multiple control characters",
140+
whenInput: "\x00\x01\x02",
141+
expect: `\u0000\u0001\u0002`,
142+
expectN: 18,
143+
},
144+
145+
// UTF-8 handling
146+
{
147+
name: "valid UTF-8 Chinese",
148+
whenInput: "hello 世界",
149+
expect: "hello 世界",
150+
expectN: 12,
151+
},
152+
{
153+
name: "valid UTF-8 emoji",
154+
whenInput: "party 🎉 time",
155+
expect: "party 🎉 time",
156+
expectN: 15,
157+
},
158+
{
159+
name: "mixed ASCII and UTF-8",
160+
whenInput: "Hello世界123",
161+
expect: "Hello世界123",
162+
expectN: 14,
163+
},
164+
{
165+
name: "UTF-8 with special chars",
166+
whenInput: "世界\n\"test\"",
167+
expect: `世界\n\"test\"`,
168+
expectN: 16,
169+
},
170+
171+
// Invalid UTF-8
172+
{
173+
name: "invalid UTF-8 sequence",
174+
whenInput: "hello\xff\xfeworld",
175+
expect: `hello\ufffd\ufffdworld`,
176+
expectN: 22,
177+
},
178+
{
179+
name: "incomplete UTF-8 sequence",
180+
whenInput: "test\xc3value",
181+
expect: `test\ufffdvalue`,
182+
expectN: 15,
183+
},
184+
185+
// Complex mixed cases
186+
{
187+
name: "all common escapes",
188+
whenInput: "tab\there\nquote\"backslash\\",
189+
expect: `tab\there\nquote\"backslash\\`,
190+
expectN: 29,
191+
},
192+
{
193+
name: "mixed controls and UTF-8",
194+
whenInput: "hello\t世界\ntest\"",
195+
expect: `hello\t世界\ntest\"`,
196+
expectN: 21,
197+
},
198+
{
199+
name: "all control characters",
200+
whenInput: "\b\f\n\r\t",
201+
expect: `\b\f\n\r\t`,
202+
expectN: 10,
203+
},
204+
{
205+
name: "control and low ASCII",
206+
whenInput: "a\nb\x00c",
207+
expect: `a\nb\u0000c`,
208+
expectN: 11,
209+
},
210+
211+
// Edge cases
212+
{
213+
name: "starts with special char",
214+
whenInput: "\\start",
215+
expect: `\\start`,
216+
expectN: 7,
217+
},
218+
{
219+
name: "ends with special char",
220+
whenInput: "end\"",
221+
expect: `end\"`,
222+
expectN: 5,
223+
},
224+
{
225+
name: "consecutive special chars",
226+
whenInput: "\\\\\"\"",
227+
expect: `\\\\\"\"`,
228+
expectN: 8,
229+
},
230+
{
231+
name: "only special characters",
232+
whenInput: "\"\\\n\t",
233+
expect: `\"\\\n\t`,
234+
expectN: 8,
235+
},
236+
{
237+
name: "spaces and punctuation",
238+
whenInput: "Hello, World! How are you?",
239+
expect: "Hello, World! How are you?",
240+
expectN: 26,
241+
},
242+
{
243+
name: "JSON-like string",
244+
whenInput: "{\"key\":\"value\"}",
245+
expect: `{\"key\":\"value\"}`,
246+
expectN: 19,
247+
},
248+
}
249+
250+
for _, tt := range testCases {
251+
t.Run(tt.name, func(t *testing.T) {
252+
buf := &bytes.Buffer{}
253+
n, err := writeJSONSafeString(buf, tt.whenInput)
254+
255+
assert.NoError(t, err)
256+
assert.Equal(t, tt.expect, buf.String())
257+
assert.Equal(t, tt.expectN, n)
258+
})
259+
}
260+
}
261+
262+
func BenchmarkWriteJSONSafeString(b *testing.B) {
263+
testCases := []struct {
264+
name string
265+
input string
266+
}{
267+
{"simple", "hello world"},
268+
{"with escapes", "tab\there\nquote\"backslash\\"},
269+
{"utf8", "hello 世界 🎉"},
270+
{"mixed", "Hello\t世界\ntest\"value\\path"},
271+
{"long simple", "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"},
272+
{"long complex", "line1\nline2\tline3\"quote\\slash\x00null世界🎉"},
273+
}
274+
275+
for _, tc := range testCases {
276+
b.Run(tc.name, func(b *testing.B) {
277+
buf := &bytes.Buffer{}
278+
b.ResetTimer()
279+
for i := 0; i < b.N; i++ {
280+
buf.Reset()
281+
writeJSONSafeString(buf, tc.input)
282+
}
283+
})
284+
}
285+
}

0 commit comments

Comments
 (0)