130 lines
4.2 KiB
Go
130 lines
4.2 KiB
Go
// Copyright 2020 The Cockroach Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package escape
|
|
|
|
import (
|
|
"bytes"
|
|
"unicode/utf8"
|
|
|
|
m "github.com/cockroachdb/redact/internal/markers"
|
|
)
|
|
|
|
// InternalEscapeBytes escapes redaction markers in the provided buf
|
|
// starting at the location startLoc.
|
|
// The bytes before startLoc are considered safe (already escaped).
|
|
//
|
|
// If breakNewLines is set, a closing redaction marker
|
|
// is placed before sequences of one or more newline characters,
|
|
// and an open redaction marker is placed afterwards.
|
|
func InternalEscapeBytes(b []byte, startLoc int, breakNewLines, strip bool) (res []byte) {
|
|
// Note: we use len(...RedactableS) and not len(...RedactableBytes)
|
|
// because the ...S variant is a compile-time constant so this
|
|
// accelerates the loops below.
|
|
start, ls := m.StartBytes, len(m.StartS)
|
|
end, le := m.EndBytes, len(m.EndS)
|
|
escape := m.EscapeMarkBytes
|
|
|
|
// Trim final newlines/spaces, for convenience.
|
|
if strip {
|
|
end := len(b)
|
|
for i := end - 1; i >= startLoc; i-- {
|
|
if b[i] == '\n' || b[i] == ' ' {
|
|
end = i
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
b = b[:end]
|
|
}
|
|
|
|
// res is the output slice. In the common case where there is
|
|
// nothing to escape, the input slice is returned directly
|
|
// and no allocation takes place.
|
|
res = b
|
|
// copied is true if and only if `res` is a copy of `b`. It only
|
|
// turns to true if the loop below finds something to escape.
|
|
copied := false
|
|
// k is the index in b up to (and excluding) the byte which we've
|
|
// already copied into res (if copied=true).
|
|
k := 0
|
|
|
|
for i := startLoc; i < len(b); i++ {
|
|
if breakNewLines && b[i] == '\n' {
|
|
if !copied {
|
|
// We only allocate an output slice when we know we definitely
|
|
// need it.
|
|
res = make([]byte, 0, len(b))
|
|
copied = true
|
|
}
|
|
res = append(res, b[k:i]...)
|
|
res = append(res, end...)
|
|
// Advance to the last newline character. We want to forward
|
|
// them all in a single call to doWrite, for performance.
|
|
lastNewLine := i
|
|
for lastNewLine < len(b) && b[lastNewLine] == '\n' {
|
|
lastNewLine++
|
|
}
|
|
res = append(res, b[i:lastNewLine]...)
|
|
res = append(res, start...)
|
|
k = lastNewLine
|
|
i = lastNewLine - 1
|
|
} else
|
|
// Ensure that occurrences of the delimiter inside the string get
|
|
// escaped.
|
|
// Reminder: ls and le are likely greater than 1, as we are scanning
|
|
// utf-8 encoded delimiters (the utf-8 encoding is multibyte).
|
|
if i+ls <= len(b) && bytes.Equal(b[i:i+ls], start) {
|
|
if !copied {
|
|
// We only allocate an output slice when we know we definitely
|
|
// need it.
|
|
res = make([]byte, 0, len(b)+len(escape))
|
|
copied = true
|
|
}
|
|
res = append(res, b[k:i]...)
|
|
res = append(res, escape...)
|
|
// Advance the counters by the length (in bytes) of the delimiter.
|
|
k = i + ls
|
|
i += ls - 1 /* -1 because we have i++ at the end of every iteration */
|
|
} else if i+le <= len(b) && bytes.Equal(b[i:i+le], end) {
|
|
if !copied {
|
|
// See the comment above about res allocation.
|
|
res = make([]byte, 0, len(b)+len(escape))
|
|
copied = true
|
|
}
|
|
res = append(res, b[k:i]...)
|
|
res = append(res, escape...)
|
|
// Advance the counters by the length (in bytes) of the delimiter.
|
|
k = i + le
|
|
i += le - 1 /* -1 because we have i++ at the end of every iteration */
|
|
}
|
|
}
|
|
// If the string terminates with an invalid utf-8 sequence, we
|
|
// want to avoid a run-in with a subsequent redaction marker.
|
|
if r, s := utf8.DecodeLastRune(b); s == 1 && r == utf8.RuneError {
|
|
if !copied {
|
|
// See the comment above about res allocation.
|
|
res = make([]byte, 0, len(b)+len(escape))
|
|
copied = true
|
|
}
|
|
res = append(res, b[k:]...)
|
|
res = append(res, escape...)
|
|
k = len(b)
|
|
}
|
|
if copied {
|
|
res = append(res, b[k:]...)
|
|
}
|
|
return
|
|
}
|