1package logfmt
2
3import (
4 "bytes"
5 "io"
6 "strconv"
7 "sync"
8 "unicode"
9 "unicode/utf16"
10 "unicode/utf8"
11)
12
13// Taken from Go's encoding/json and modified for use here.
14
15// Copyright 2010 The Go Authors. All rights reserved.
16// Use of this source code is governed by a BSD-style
17// license that can be found in the LICENSE file.
18
19var hex = "0123456789abcdef"
20
21var bufferPool = sync.Pool{
22 New: func() interface{} {
23 return &bytes.Buffer{}
24 },
25}
26
27func getBuffer() *bytes.Buffer {
28 return bufferPool.Get().(*bytes.Buffer)
29}
30
31func poolBuffer(buf *bytes.Buffer) {
32 buf.Reset()
33 bufferPool.Put(buf)
34}
35
36// NOTE: keep in sync with writeQuotedBytes below.
37func writeQuotedString(w io.Writer, s string) (int, error) {
38 buf := getBuffer()
39 buf.WriteByte('"')
40 start := 0
41 for i := 0; i < len(s); {
42 if b := s[i]; b < utf8.RuneSelf {
43 if 0x20 <= b && b != '\\' && b != '"' {
44 i++
45 continue
46 }
47 if start < i {
48 buf.WriteString(s[start:i])
49 }
50 switch b {
51 case '\\', '"':
52 buf.WriteByte('\\')
53 buf.WriteByte(b)
54 case '\n':
55 buf.WriteByte('\\')
56 buf.WriteByte('n')
57 case '\r':
58 buf.WriteByte('\\')
59 buf.WriteByte('r')
60 case '\t':
61 buf.WriteByte('\\')
62 buf.WriteByte('t')
63 default:
64 // This encodes bytes < 0x20 except for \n, \r, and \t.
65 buf.WriteString(`\u00`)
66 buf.WriteByte(hex[b>>4])
67 buf.WriteByte(hex[b&0xF])
68 }
69 i++
70 start = i
71 continue
72 }
73 c, size := utf8.DecodeRuneInString(s[i:])
74 if c == utf8.RuneError {
75 if start < i {
76 buf.WriteString(s[start:i])
77 }
78 buf.WriteString(`\ufffd`)
79 i += size
80 start = i
81 continue
82 }
83 i += size
84 }
85 if start < len(s) {
86 buf.WriteString(s[start:])
87 }
88 buf.WriteByte('"')
89 n, err := w.Write(buf.Bytes())
90 poolBuffer(buf)
91 return n, err
92}
93
94// NOTE: keep in sync with writeQuoteString above.
95func writeQuotedBytes(w io.Writer, s []byte) (int, error) {
96 buf := getBuffer()
97 buf.WriteByte('"')
98 start := 0
99 for i := 0; i < len(s); {
100 if b := s[i]; b < utf8.RuneSelf {
101 if 0x20 <= b && b != '\\' && b != '"' {
102 i++
103 continue
104 }
105 if start < i {
106 buf.Write(s[start:i])
107 }
108 switch b {
109 case '\\', '"':
110 buf.WriteByte('\\')
111 buf.WriteByte(b)
112 case '\n':
113 buf.WriteByte('\\')
114 buf.WriteByte('n')
115 case '\r':
116 buf.WriteByte('\\')
117 buf.WriteByte('r')
118 case '\t':
119 buf.WriteByte('\\')
120 buf.WriteByte('t')
121 default:
122 // This encodes bytes < 0x20 except for \n, \r, and \t.
123 buf.WriteString(`\u00`)
124 buf.WriteByte(hex[b>>4])
125 buf.WriteByte(hex[b&0xF])
126 }
127 i++
128 start = i
129 continue
130 }
131 c, size := utf8.DecodeRune(s[i:])
132 if c == utf8.RuneError {
133 if start < i {
134 buf.Write(s[start:i])
135 }
136 buf.WriteString(`\ufffd`)
137 i += size
138 start = i
139 continue
140 }
141 i += size
142 }
143 if start < len(s) {
144 buf.Write(s[start:])
145 }
146 buf.WriteByte('"')
147 n, err := w.Write(buf.Bytes())
148 poolBuffer(buf)
149 return n, err
150}
151
152// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
153// or it returns -1.
154func getu4(s []byte) rune {
155 if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
156 return -1
157 }
158 r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
159 if err != nil {
160 return -1
161 }
162 return rune(r)
163}
164
165func unquoteBytes(s []byte) (t []byte, ok bool) {
166 if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
167 return
168 }
169 s = s[1 : len(s)-1]
170
171 // Check for unusual characters. If there are none,
172 // then no unquoting is needed, so return a slice of the
173 // original bytes.
174 r := 0
175 for r < len(s) {
176 c := s[r]
177 if c == '\\' || c == '"' || c < ' ' {
178 break
179 }
180 if c < utf8.RuneSelf {
181 r++
182 continue
183 }
184 rr, size := utf8.DecodeRune(s[r:])
185 if rr == utf8.RuneError {
186 break
187 }
188 r += size
189 }
190 if r == len(s) {
191 return s, true
192 }
193
194 b := make([]byte, len(s)+2*utf8.UTFMax)
195 w := copy(b, s[0:r])
196 for r < len(s) {
197 // Out of room? Can only happen if s is full of
198 // malformed UTF-8 and we're replacing each
199 // byte with RuneError.
200 if w >= len(b)-2*utf8.UTFMax {
201 nb := make([]byte, (len(b)+utf8.UTFMax)*2)
202 copy(nb, b[0:w])
203 b = nb
204 }
205 switch c := s[r]; {
206 case c == '\\':
207 r++
208 if r >= len(s) {
209 return
210 }
211 switch s[r] {
212 default:
213 return
214 case '"', '\\', '/', '\'':
215 b[w] = s[r]
216 r++
217 w++
218 case 'b':
219 b[w] = '\b'
220 r++
221 w++
222 case 'f':
223 b[w] = '\f'
224 r++
225 w++
226 case 'n':
227 b[w] = '\n'
228 r++
229 w++
230 case 'r':
231 b[w] = '\r'
232 r++
233 w++
234 case 't':
235 b[w] = '\t'
236 r++
237 w++
238 case 'u':
239 r--
240 rr := getu4(s[r:])
241 if rr < 0 {
242 return
243 }
244 r += 6
245 if utf16.IsSurrogate(rr) {
246 rr1 := getu4(s[r:])
247 if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
248 // A valid pair; consume.
249 r += 6
250 w += utf8.EncodeRune(b[w:], dec)
251 break
252 }
253 // Invalid surrogate; fall back to replacement rune.
254 rr = unicode.ReplacementChar
255 }
256 w += utf8.EncodeRune(b[w:], rr)
257 }
258
259 // Quote, control characters are invalid.
260 case c == '"', c < ' ':
261 return
262
263 // ASCII
264 case c < utf8.RuneSelf:
265 b[w] = c
266 r++
267 w++
268
269 // Coerce to well-formed UTF-8.
270 default:
271 rr, size := utf8.DecodeRune(s[r:])
272 r += size
273 w += utf8.EncodeRune(b[w:], rr)
274 }
275 }
276 return b[0:w], true
277}