├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── api.go ├── bench_test.go ├── builder ├── builder.go └── builder_test.go ├── doc.go ├── go.mod ├── interfaces └── interfaces.go ├── internal ├── buffer │ ├── buffer.go │ └── buffer_test.go ├── escape │ ├── escape.go │ └── escape_test.go ├── fmtforward │ ├── make_format.go │ └── make_format_test.go ├── markers │ ├── constants.go │ └── markers.go ├── redact │ ├── doc.go │ └── wrappers.go └── rfmt │ ├── README.md │ ├── fmtsort │ └── sort.go │ ├── format.go │ ├── format.go.diff │ ├── helpers.go │ ├── print.go │ ├── print.go.diff │ ├── printer_adapter.go │ ├── refresh.sh │ ├── registry.go │ └── registry_test.go ├── markers_print.go ├── markers_test.go ├── util.go └── util_test.go /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build-and-test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go: 16 | - "1.13" 17 | - "1.14" 18 | - "1.15" 19 | - "1.16" 20 | - "1.17" 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Set up Go (${{ matrix.go }} 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go }} 28 | 29 | - name: Build (${{ matrix.go }}) 30 | run: go build ./... 31 | 32 | - name: Test (${{ matrix.go }}) 33 | run: go test ./... 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12.x 5 | - 1.13.x 6 | - 1.14.x 7 | - 1.15.x 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redact 2 | Utilities to redact Go strings for confidentiality 3 | 4 | ![Build Status](https://github.com/cockroachdb/redact/actions/workflows/ci.yaml/badge.svg?branch=master) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/cockroachdb/redact.svg)](https://pkg.go.dev/github.com/cockroachdb/redact) 6 | 7 | Some explanations about how this is used in e.g. CockroachDB: 8 | 9 | https://wiki.crdb.io/wiki/spaces/CRDB/pages/1824817806/Log+and+error+redactability 10 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | 21 | "github.com/cockroachdb/redact/builder" 22 | i "github.com/cockroachdb/redact/interfaces" 23 | b "github.com/cockroachdb/redact/internal/buffer" 24 | fw "github.com/cockroachdb/redact/internal/fmtforward" 25 | m "github.com/cockroachdb/redact/internal/markers" 26 | w "github.com/cockroachdb/redact/internal/redact" 27 | ifmt "github.com/cockroachdb/redact/internal/rfmt" 28 | ) 29 | 30 | // SafeFormatter is implemented by object types that want to separate 31 | // safe and non-safe information when printed out by a Printf-like 32 | // formatter. 33 | type SafeFormatter = i.SafeFormatter 34 | 35 | // SafeValue is a marker interface to be implemented by types that 36 | // alias base Go types and whose natural representation via Printf is 37 | // always safe for reporting. 38 | // 39 | // This is recognized by the SafePrinter interface as an alternative 40 | // to SafeFormatter. 41 | // 42 | // It is provided to decorate "leaf" Go types, such as aliases to int. 43 | // 44 | // Typically, a linter enforces that a type can only implement this 45 | // interface if it aliases a base go type. More complex types should 46 | // implement SafeFormatter instead. 47 | // 48 | // It is advised to build an automatic process during builds to 49 | // collect all the types that implement this interface, as well as all 50 | // uses of this type, and produce a report. Changes to this report 51 | // should receive maximal amount of scrutiny during code reviews. 52 | type SafeValue = i.SafeValue 53 | 54 | // SafeMessager is an alternative to SafeFormatter used in previous 55 | // versions of CockroachDB. 56 | // NB: this interface is obsolete. Use SafeFormatter instead. 57 | // TODO(knz): Remove this. 58 | type SafeMessager = i.SafeMessager 59 | 60 | // SafePrinter is a stateful helper that abstracts an output stream in 61 | // the context of printf-like formatting, but with the ability to 62 | // separate safe and unsafe bits of data. 63 | // 64 | // This package provides one implementation of this using marker 65 | // delimiters for unsafe data, see markers.go. We would like to aim 66 | // for alternate implementations to generate more structured formats. 67 | type SafePrinter = i.SafePrinter 68 | 69 | // SafeWriter provides helper functions for use in implementations of 70 | // SafeFormatter, to format mixes of safe and unsafe strings. 71 | type SafeWriter = i.SafeWriter 72 | 73 | // SafeString represents a string that is not a sensitive value. 74 | type SafeString = i.SafeString 75 | 76 | // SafeInt represents an integer that is not a sensitive value. 77 | type SafeInt = i.SafeInt 78 | 79 | // SafeUint represents an integer that is not a sensitive value. 80 | type SafeUint = i.SafeUint 81 | 82 | // SafeFloat represents a floating-point value that is not a sensitive value. 83 | type SafeFloat = i.SafeFloat 84 | 85 | // SafeRune aliases rune. See the explanation for SafeString. 86 | type SafeRune = i.SafeRune 87 | 88 | // RedactableString is a string that contains a mix of safe and unsafe 89 | // bits of data, but where it is known that unsafe bits are enclosed 90 | // by redaction markers ‹ and ›, and occurrences of the markers 91 | // inside the original data items have been escaped. 92 | // 93 | // Instances of RedactableString should not be constructed directly; 94 | // instead use the facilities from print.go (Sprint, Sprintf) 95 | // or the methods below. 96 | type RedactableString = m.RedactableString 97 | 98 | // RedactableBytes is like RedactableString but is a byte slice. 99 | // 100 | // Instances of RedactableBytes should not be constructed directly; 101 | // instead use the facilities from print.go (Sprint, Sprintf) 102 | // or the methods below. 103 | type RedactableBytes = m.RedactableBytes 104 | 105 | // StartMarker returns the start delimiter for an unsafe string. 106 | func StartMarker() []byte { return m.StartMarker() } 107 | 108 | // EndMarker returns the end delimiter for an unsafe string. 109 | func EndMarker() []byte { return m.EndMarker() } 110 | 111 | // RedactedMarker returns the special string used by Redact. 112 | func RedactedMarker() []byte { return m.RedactedMarker() } 113 | 114 | // EscapeMarkers escapes the special delimiters from the provided 115 | // byte slice. 116 | func EscapeMarkers(s []byte) []byte { return m.EscapeMarkers(s) } 117 | 118 | // EscapeBytes escapes markers inside the given byte slice and encloses 119 | // the entire byte slice between redaction markers. 120 | // EscapeBytes escapes markers inside the given byte slice and encloses 121 | // the entire byte slice between redaction markers. 122 | func EscapeBytes(s []byte) RedactableBytes { return ifmt.EscapeBytes(s) } 123 | 124 | // ManualBuffer is a variable-sized buffer of bytes with 125 | // Write methods. Writes are escaped in different ways 126 | // depending on the output mode. 127 | // Note: This type is only meant for "advanced" usage. 128 | // For most common uses, consider using StringBuilder instead. 129 | type ManualBuffer = b.Buffer 130 | 131 | // Unsafe turns any value that would otherwise be considered safe, 132 | // into an unsafe value. 133 | func Unsafe(a interface{}) interface{} { return w.Unsafe(a) } 134 | 135 | // Safe turns any value into an object that is considered as safe by 136 | // the formatter. 137 | // 138 | // This is provided as an “escape hatch” for cases where the other 139 | // interfaces and conventions fail. Increased usage of this mechanism 140 | // should be taken as a signal that a new abstraction is missing. 141 | // The implementation is also slow. 142 | func Safe(a interface{}) SafeValue { return w.Safe(a) } 143 | 144 | // RegisterRedactErrorFn registers an error redaction function for use 145 | // during automatic redaction by this package. 146 | // Provided e.g. by cockroachdb/errors. 147 | func RegisterRedactErrorFn(fn func(err error, p i.SafePrinter, verb rune)) { 148 | ifmt.RegisterRedactErrorFn(fn) 149 | } 150 | 151 | // MakeFormat is a helper for use by implementations of the 152 | // SafeFormatter interface. It reproduces the format currently active 153 | // in fmt.State and verb. This is provided because Go's standard 154 | // fmt.State does not make the original format string available to us. 155 | // 156 | // If the return value justV is true, then the current state 157 | // was found to be %v exactly; in that case the caller 158 | // can avoid a full-blown Printf call and use just Print instead 159 | // to take a shortcut. 160 | func MakeFormat(s fmt.State, verb rune) (justV bool, format string) { 161 | return fw.MakeFormat(s, verb) 162 | } 163 | 164 | // StringBuilder accumulates strings with optional redaction markers. 165 | // 166 | // It implements io.Writer but marks direct writes as redactable. 167 | // To distinguish safe and unsafe bits, it also implements the SafeWriter 168 | // interface. 169 | type StringBuilder = builder.StringBuilder 170 | 171 | // RegisterSafeType registers a data type to always be considered safe 172 | // during the production of redactable strings. 173 | func RegisterSafeType(t reflect.Type) { 174 | ifmt.RegisterSafeType(t) 175 | } 176 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import "testing" 18 | 19 | func BenchmarkRedact(b *testing.B) { 20 | x := struct { 21 | a int 22 | }{456} 23 | for i := 0; i < b.N; i++ { 24 | _ = Sprintf("hello %v %v %v", 123, x, Safe("world"), Unsafe("universe")) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /builder/builder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package builder 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | i "github.com/cockroachdb/redact/interfaces" 22 | ib "github.com/cockroachdb/redact/internal/buffer" 23 | ifmt "github.com/cockroachdb/redact/internal/rfmt" 24 | ) 25 | 26 | // StringBuilder accumulates strings with optional redaction markers. 27 | // 28 | // It implements io.Writer but marks direct writes as redactable. 29 | // To distinguish safe and unsafe bits, it also implements the SafeWriter 30 | // interface. 31 | type StringBuilder struct { 32 | ib.Buffer 33 | } 34 | 35 | var _ fmt.Stringer = StringBuilder{} 36 | var _ fmt.Stringer = (*StringBuilder)(nil) 37 | 38 | // SafeFormat implements SafeFormatter. 39 | func (b StringBuilder) SafeFormat(p i.SafePrinter, _ rune) { 40 | // We only support the %v / %s natural print here. 41 | // Go supports other formatting verbs for strings: %x/%X/%q. 42 | // 43 | // We don't do this here, keeping in mind that the output 44 | // of a SafeFormat must remain a redactable string. 45 | // 46 | // %x/%X cannot be implemented because they would turn redaction 47 | // markers into hex codes, and the entire result string would 48 | // appear safe for reporting, which would break the semantics 49 | // of this package. 50 | // 51 | // %q cannot be implemented because it replaces non-ASCII characters 52 | // with numeric unicode escapes, which breaks redaction 53 | // markers too. 54 | p.Print(b.RedactableString()) 55 | } 56 | 57 | var _ i.SafeFormatter = StringBuilder{} 58 | var _ i.SafeFormatter = (*StringBuilder)(nil) 59 | 60 | // StringBuilder implements io.Writer. 61 | // Direct Write() calls are considered unsafe. 62 | var _ io.Writer = (*StringBuilder)(nil) 63 | 64 | // Write implements the io.Writer interface. 65 | func (b *StringBuilder) Write(s []byte) (int, error) { 66 | b.SetMode(ib.UnsafeEscaped) 67 | return b.Buffer.Write(s) 68 | } 69 | 70 | // WriteString shadows the ib.Buffer.WriteString method. 71 | func (b *StringBuilder) WriteString(s string) (int, error) { 72 | b.SetMode(ib.UnsafeEscaped) 73 | return b.Buffer.WriteString(s) 74 | } 75 | 76 | // WriteByte shadows the ib.Buffer.WriteByte method. 77 | func (b *StringBuilder) WriteByte(c byte) error { 78 | b.SetMode(ib.UnsafeEscaped) 79 | return b.Buffer.WriteByte(c) 80 | } 81 | 82 | // WriteRune shadows the ib.Buffer.WriteRune method. 83 | func (b *StringBuilder) WriteRune(r rune) error { 84 | b.SetMode(ib.UnsafeEscaped) 85 | return b.Buffer.WriteRune(r) 86 | } 87 | 88 | // StringBuilder implements SafeWriter. 89 | var _ i.SafeWriter = (*StringBuilder)(nil) 90 | 91 | // Print is part of the SafeWriter interface. 92 | func (b *StringBuilder) Print(args ...interface{}) { 93 | b.SetMode(ib.PreRedactable) 94 | _, _ = ifmt.Fprint(&b.Buffer, args...) 95 | } 96 | 97 | // Printf is part of the SafeWriter interface. 98 | func (b *StringBuilder) Printf(format string, args ...interface{}) { 99 | b.SetMode(ib.PreRedactable) 100 | _, _ = ifmt.Fprintf(&b.Buffer, format, args...) 101 | } 102 | 103 | // SafeString is part of the SafeWriter interface. 104 | func (b *StringBuilder) SafeString(s i.SafeString) { 105 | b.SetMode(ib.SafeEscaped) 106 | _, _ = b.Buffer.WriteString(string(s)) 107 | } 108 | 109 | // SafeInt is part of the SafeWriter interface. 110 | func (b *StringBuilder) SafeInt(s i.SafeInt) { 111 | b.SetMode(ib.SafeEscaped) 112 | _, _ = ifmt.Fprintf(&b.Buffer, "%d", s) 113 | } 114 | 115 | // SafeUint is part of the SafeWriter interface. 116 | func (b *StringBuilder) SafeUint(s i.SafeUint) { 117 | b.SetMode(ib.SafeEscaped) 118 | _, _ = ifmt.Fprintf(&b.Buffer, "%d", s) 119 | } 120 | 121 | // SafeFloat is part of the SafeWriter interface. 122 | func (b *StringBuilder) SafeFloat(s i.SafeFloat) { 123 | b.SetMode(ib.SafeEscaped) 124 | _, _ = ifmt.Fprintf(&b.Buffer, "%v", s) 125 | } 126 | 127 | // SafeRune is part of the SafeWriter interface. 128 | func (b *StringBuilder) SafeRune(s i.SafeRune) { 129 | b.SetMode(ib.SafeEscaped) 130 | _ = b.Buffer.WriteRune(rune(s)) 131 | } 132 | 133 | func (b *StringBuilder) SafeByte(s i.SafeByte) { 134 | b.SetMode(ib.SafeEscaped) 135 | _ = b.Buffer.WriteByte(byte(s)) 136 | } 137 | 138 | func (b *StringBuilder) SafeBytes(s i.SafeBytes) { 139 | b.SetMode(ib.SafeEscaped) 140 | _, _ = b.Buffer.Write([]byte(s)) 141 | } 142 | 143 | // UnsafeString is part of the SafeWriter interface. 144 | func (b *StringBuilder) UnsafeString(s string) { 145 | b.SetMode(ib.UnsafeEscaped) 146 | _, _ = b.Buffer.WriteString(s) 147 | } 148 | 149 | // UnsafeRune is part of the SafeWriter interface. 150 | func (b *StringBuilder) UnsafeRune(s rune) { 151 | b.SetMode(ib.UnsafeEscaped) 152 | _ = b.Buffer.WriteRune(s) 153 | } 154 | 155 | // UnsafeByte is part of the SafeWriter interface. 156 | func (b *StringBuilder) UnsafeByte(s byte) { 157 | b.SetMode(ib.UnsafeEscaped) 158 | _ = b.Buffer.WriteByte(s) 159 | } 160 | 161 | // UnsafeBytes is part of the SafeWriter interface. 162 | func (b *StringBuilder) UnsafeBytes(s []byte) { 163 | b.SetMode(ib.UnsafeEscaped) 164 | _, _ = b.Buffer.Write(s) 165 | } 166 | -------------------------------------------------------------------------------- /builder/builder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package builder 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | m "github.com/cockroachdb/redact/internal/markers" 22 | w "github.com/cockroachdb/redact/internal/redact" 23 | ifmt "github.com/cockroachdb/redact/internal/rfmt" 24 | ) 25 | 26 | func TestBuilder(t *testing.T) { 27 | var b StringBuilder 28 | 29 | fmt.Fprint(&b, "unsafe") 30 | b.SafeRune('\n') 31 | 32 | b.Print("unsafe") 33 | b.SafeRune('\n') 34 | 35 | b.Print(w.Safe("safe")) 36 | b.SafeRune('\n') 37 | 38 | b.Printf("safe") 39 | b.SafeRune('\n') 40 | 41 | b.Printf("hello %v %v", w.Safe("safe"), "unsafe") 42 | b.SafeRune('\n') 43 | 44 | b.SafeString("safe\n") 45 | 46 | b.SafeInt(123) 47 | b.SafeUint(456) 48 | b.SafeFloat(3.14) 49 | 50 | b.SafeRune('S') 51 | b.SafeRune('\n') 52 | 53 | b.UnsafeString("unsafe") 54 | b.SafeRune('\n') 55 | b.UnsafeString("unsafe" + m.StartS[:1]) 56 | b.SafeRune('\n') 57 | 58 | b.UnsafeRune('U') 59 | b.SafeRune('\n') 60 | 61 | b.UnsafeByte('U') 62 | b.SafeByte('\n') 63 | 64 | b.UnsafeByte(m.StartS[0]) 65 | b.SafeByte('\n') 66 | 67 | b.UnsafeBytes([]byte("UUU")) 68 | b.SafeBytes([]byte("\n")) 69 | 70 | actualR := b.RedactableString() 71 | const expectedR = `‹unsafe› 72 | ‹unsafe› 73 | safe 74 | safe 75 | hello safe ‹unsafe› 76 | safe 77 | 1234563.14S 78 | ‹unsafe› 79 | ‹unsafe` + "\342" + `?› 80 | ‹U› 81 | ‹U› 82 | ‹?› 83 | ‹UUU› 84 | ` 85 | if actualR != expectedR { 86 | t.Errorf("expected:\n%s\n\ngot:\n%s", expectedR, actualR) 87 | } 88 | if actualB := b.RedactableBytes(); string(actualB) != expectedR { 89 | t.Errorf("expected:\n%s\n\ngot:\n%s", expectedR, actualB) 90 | } 91 | 92 | if actualR2 := ifmt.Sprint(&b); actualR2 != expectedR { 93 | t.Errorf("expected:\n%s\n\ngot:\n%s", expectedR, actualR2) 94 | } 95 | 96 | actual := b.String() 97 | const expected = `unsafe 98 | unsafe 99 | safe 100 | safe 101 | hello safe unsafe 102 | safe 103 | 1234563.14S 104 | unsafe 105 | unsafe` + "\342" + `? 106 | U 107 | U 108 | ? 109 | UUU 110 | ` 111 | if actual != expected { 112 | t.Errorf("expected:\n%s\n\ngot:\n%s", expected, actual) 113 | } 114 | 115 | if actual := fmt.Sprintf("%v", b); actual != expected { 116 | t.Errorf("expected:\n%s\n\ngot:\n%s", expected, actual) 117 | } 118 | } 119 | 120 | func TestMixedWrites(t *testing.T) { 121 | t.Run("oneline", func(t *testing.T) { 122 | var b StringBuilder 123 | b.SafeString("safe") 124 | b.WriteString("unsafe") 125 | b.SafeString("") 126 | b.WriteByte('U') 127 | b.SafeString("") 128 | b.WriteRune('U') 129 | actual := b.RedactableString() 130 | const expected = "safe‹unsafeUU›" 131 | if actual != expected { 132 | t.Errorf("expected:\n%s\n\ngot:\n%s", expected, actual) 133 | } 134 | const expectedWithoutMarkers = "safeunsafeUU" 135 | if ractual := actual.StripMarkers(); ractual != expectedWithoutMarkers { 136 | t.Errorf("expected:\n%s\n\ngot:\n%s", expectedWithoutMarkers, ractual) 137 | } 138 | }) 139 | 140 | t.Run("multiline", func(t *testing.T) { 141 | var b StringBuilder 142 | b.SafeString("\nsafe\n") 143 | b.WriteString("\nunsafe\n") 144 | b.SafeString("\n") 145 | b.WriteByte('\n') 146 | b.SafeString("\n") 147 | b.WriteRune('\n') 148 | actual := b.RedactableString() 149 | const expected = "\nsafe\n\n‹unsafe›\n\n\n\n\n" 150 | if actual != expected { 151 | t.Errorf("expected:\n%q\n\ngot:\n%q", expected, actual) 152 | } 153 | const expectedWithoutMarkers = "\nsafe\n\nunsafe\n\n\n\n\n" 154 | if ractual := actual.StripMarkers(); ractual != expectedWithoutMarkers { 155 | t.Errorf("expected:\n%q\n\ngot:\n%q", expectedWithoutMarkers, ractual) 156 | } 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | // Package redact provides facilities for separating “safe” and 16 | // “unsafe” pieces of data when logging and constructing error object. 17 | // 18 | // An item is said to be “safe” if it is proven to not contain 19 | // PII or otherwise confidential information that should not escape 20 | // the boundaries of the current system, for example via telemetry 21 | // or crash reporting. Conversely, data is considered “unsafe” 22 | // until/unless it is known to be “safe”. 23 | // 24 | // Example use: 25 | // 26 | // redactable := redact.Sprintf("hello %s", "universe") 27 | // 28 | // // At this point, 'redactable' contains "hello ‹universe›". 29 | // 30 | // // This prints "hello universe": 31 | // fmt.Println(redactable.StripMarkers()) 32 | // 33 | // // This reports "hello ‹×›": 34 | // fmt.Println(redactable.Redact()) 35 | // 36 | // 37 | // When defining your own custom types, you can define 38 | // a SafeFormat method, implementing the redact.SafeFormatter 39 | // interface in a way you'd otherwise implement fmt.Formatter. 40 | // This is then recognized by this package's API automatically. 41 | // 42 | // Alternatively: 43 | // 44 | // - you can implement the SafeValue interface, which tells the 45 | // redact package to always the default formatting of a type 46 | // as safe and thus not included inside redaction markers. 47 | // 48 | // - you can include a value within redact.Safe() and redact.Unsafe() 49 | // in redact.Sprintf / redact.Fprintf calls, to force 50 | // the omission or inclusion of redaction markers. 51 | package redact 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cockroachdb/redact 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /interfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package interfaces 16 | 17 | import "fmt" 18 | 19 | // SafeFormatter is implemented by object types that want to separate 20 | // safe and non-safe information when printed out by a Printf-like 21 | // formatter. 22 | type SafeFormatter interface { 23 | // SafeFormat is like the Format method of fmt.Formatter, except 24 | // that it operates using a SafePrinter instead of a fmt.State for 25 | // output. 26 | // 27 | // The verb argument is the control character that defines 28 | // the formatting mode in the surrounding Printf call. 29 | // For example, if this method is called to format %03d, 30 | // the verb is 'd'. 31 | SafeFormat(s SafePrinter, verb rune) 32 | } 33 | 34 | // SafePrinter is a stateful helper that abstracts an output stream in 35 | // the context of printf-like formatting, but with the ability to 36 | // separate safe and unsafe bits of data. 37 | // 38 | // This package provides one implementation of this using marker 39 | // delimiters for unsafe data, see markers.go. We would like to aim 40 | // for alternate implementations to generate more structured formats. 41 | type SafePrinter interface { 42 | // SafePrinter inherits fmt.State to access format flags, however 43 | // calls to fmt.State's underlying Write() as unsafe. 44 | fmt.State 45 | 46 | // SafePrinter provides the SafeWriter interface. 47 | SafeWriter 48 | } 49 | 50 | // SafeWriter provides helper functions for use in implementations of 51 | // SafeFormatter, to format mixes of safe and unsafe strings. 52 | type SafeWriter interface { 53 | // SafeString emits a safe string. 54 | SafeString(SafeString) 55 | 56 | // SafeInt emits a safe integer. 57 | SafeInt(SafeInt) 58 | 59 | // SafeUint emits a safe unsigned integer. 60 | SafeUint(SafeUint) 61 | 62 | // SafeFloat emits a safe floating-point value. 63 | SafeFloat(SafeFloat) 64 | 65 | // SafeRune emits a safe rune. 66 | SafeRune(SafeRune) 67 | 68 | // SafeByte emits a safe byte. 69 | SafeByte(SafeByte) 70 | 71 | // SafeBytes emits a safe byte slice. 72 | SafeBytes(SafeBytes) 73 | 74 | // Print emits its arguments separated by spaces. 75 | // For each argument it dynamically checks for the SafeFormatter or 76 | // SafeValue interface and either use that, or mark the argument 77 | // payload as unsafe. 78 | Print(args ...interface{}) 79 | 80 | // For printf, a linter checks that the format string is 81 | // a constant literal, so the implementation can assume it's always 82 | // safe. 83 | Printf(format string, arg ...interface{}) 84 | 85 | // UnsafeString writes an unsafe string. 86 | UnsafeString(string) 87 | 88 | // UnsafeByte writes an unsafe byte. 89 | UnsafeByte(byte) 90 | 91 | // UnsafeBytes writes an unsafe byte slice. 92 | UnsafeBytes([]byte) 93 | 94 | // UnsafeRune writes an unsafe rune. 95 | UnsafeRune(rune) 96 | } 97 | 98 | // SafeString represents a string that is not a sensitive value. 99 | type SafeString string 100 | 101 | // SafeValue makes SafeString a SafeValue. 102 | func (SafeString) SafeValue() {} 103 | 104 | // SafeInt represents an integer that is not a sensitive value. 105 | type SafeInt int64 106 | 107 | // SafeValue makes SafeInt a SafeValue. 108 | func (SafeInt) SafeValue() {} 109 | 110 | // SafeUint represents an integer that is not a sensitive value. 111 | type SafeUint uint64 112 | 113 | // SafeValue makes SafeUint a SafeValue. 114 | func (SafeUint) SafeValue() {} 115 | 116 | // SafeFloat represents a floating-point value that is not a sensitive value. 117 | type SafeFloat float64 118 | 119 | // SafeValue makes SafeFloat a SafeValue. 120 | func (SafeFloat) SafeValue() {} 121 | 122 | // SafeRune aliases rune. See the explanation for SafeString. 123 | type SafeRune rune 124 | 125 | // SafeValue makes SafeRune a SafeValue. 126 | func (SafeRune) SafeValue() {} 127 | 128 | // SafeByte represents a byte that is not a sensitive value. 129 | type SafeByte byte 130 | 131 | // SafeValue makes SafeByte a SafeValue. 132 | func (SafeByte) SafeValue() {} 133 | 134 | // SafeBytes represents a byte slice that is not a sensitive value. 135 | type SafeBytes []byte 136 | 137 | // SafeValue makes SafeBytes a SafeValue. 138 | func (SafeBytes) SafeValue() {} 139 | 140 | // SafeValue is a marker interface to be implemented by types that 141 | // alias base Go types and whose natural representation via Printf is 142 | // always safe for reporting. 143 | // 144 | // This is recognized by the SafePrinter interface as an alternative 145 | // to SafeFormatter. 146 | // 147 | // It is provided to decorate "leaf" Go types, such as aliases to int. 148 | // 149 | // Typically, a linter enforces that a type can only implement this 150 | // interface if it aliases a base go type. More complex types should 151 | // implement SafeFormatter instead. 152 | // 153 | // It is advised to build an automatic process during builds to 154 | // collect all the types that implement this interface, as well as all 155 | // uses of this type, and produce a report. Changes to this report 156 | // should receive maximal amount of scrutiny during code reviews. 157 | type SafeValue interface { 158 | SafeValue() 159 | } 160 | 161 | // SafeMessager is an alternative to SafeFormatter used in previous 162 | // versions of CockroachDB. 163 | // NB: this interface is obsolete. Use SafeFormatter instead. 164 | // TODO(knz): Remove this. 165 | type SafeMessager = interface { 166 | SafeMessage() string 167 | } 168 | -------------------------------------------------------------------------------- /internal/buffer/buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package buffer 16 | 17 | import ( 18 | "bytes" 19 | origFmt "fmt" 20 | "unicode/utf8" 21 | "unsafe" 22 | 23 | "github.com/cockroachdb/redact/internal/escape" 24 | m "github.com/cockroachdb/redact/internal/markers" 25 | ) 26 | 27 | // Buffer is a variable-sized buffer of bytes with a 28 | // Write method. Writes are escaped in different ways 29 | // depending on the output mode. 30 | type Buffer struct { 31 | buf []byte 32 | validUntil int // exclusive upper bound of data that's already validated 33 | mode OutputMode 34 | markerOpen bool 35 | } 36 | 37 | // OutputMode determines how writes are processed in the Buffer. 38 | type OutputMode int 39 | 40 | const ( 41 | // UnsafeEscaped says that written data is unsafe for reporting, 42 | // so it should be enclosed in redaction markers, 43 | // and may contain redaction markers itself, and so it should be escaped. 44 | UnsafeEscaped OutputMode = iota 45 | // SafeEscaped says that written data is safe for reporting 46 | // (should not be enclosed in redaction markers) but may itself 47 | // contain redaction markers, and so it should be escaped. 48 | SafeEscaped 49 | // SafeRaw says that written data is safe for reporting 50 | // (should not be enclosed in redaction markers) and is 51 | // guaranteed not to contain redaction markers, so it 52 | // needs not be escaped. 53 | SafeRaw 54 | // PreRedactable says that written data already contains a mixed 55 | // of safe and unsafe information with suitably placed redaction markers, 56 | // and so should be inlined. 57 | PreRedactable = SafeRaw 58 | ) 59 | 60 | // RedactableBytes returns the bytes in the buffer. 61 | func (b Buffer) RedactableBytes() m.RedactableBytes { 62 | // NB: we're dependent on the fact this is a copy of the original 63 | // buffer. The finalize() method should not be called 64 | // in a conceputally read-only accessor like RedactableBytes(). 65 | b.finalize() 66 | return m.RedactableBytes(b.buf) 67 | } 68 | 69 | // RedactableString returns the bytes in the buffer. 70 | func (b Buffer) RedactableString() m.RedactableString { 71 | // NB: we're dependent on the fact this is a copy of the original 72 | // buffer. The finalize() method should not be called 73 | // in a conceputally read-only accessor like RedactableString(). 74 | b.finalize() 75 | return m.RedactableString(b.buf) 76 | } 77 | 78 | // String implemens fmt.Stringer. 79 | func (b Buffer) String() string { 80 | // NB: we're dependent on the fact this is a copy of the original 81 | // buffer. The finalize() method should not be called 82 | // in a conceputally read-only accessor like String(). 83 | b.finalize() 84 | return m.RedactableString(b.buf).StripMarkers() 85 | } 86 | 87 | // TakeRedactableBytes returns the buffer 88 | // contents and reinitializes the buffer. This saves 89 | // a memory allocation compared to RedactableBytes(). 90 | func (b *Buffer) TakeRedactableBytes() m.RedactableBytes { 91 | b.finalize() 92 | r := b.buf 93 | b.buf = nil 94 | b.validUntil = 0 95 | b.mode = UnsafeEscaped 96 | return m.RedactableBytes(r) 97 | } 98 | 99 | // TakeRedactableString returns the buffer 100 | // contents and reinitializes the buffer. This saves 101 | // a memory allocation compared to RedactableString(). 102 | func (b *Buffer) TakeRedactableString() m.RedactableString { 103 | if b == nil { 104 | // Special case, useful in debugging. 105 | return "" 106 | } 107 | b.finalize() 108 | r := *(*m.RedactableString)(unsafe.Pointer(&b.buf)) 109 | b.buf = nil 110 | b.validUntil = 0 111 | b.mode = UnsafeEscaped 112 | return r 113 | } 114 | 115 | // Len returns the number of bytes in the buffer. 116 | func (b *Buffer) Len() int { 117 | copy := *b 118 | copy.finalize() 119 | return len(copy.buf) 120 | } 121 | 122 | // Cap returns the capacity of the buffer's underlying byte slice, 123 | // that is, the total space allocated for the buffer's data. 124 | func (b *Buffer) Cap() int { 125 | return cap(b.buf) 126 | } 127 | 128 | // Write appends the contents of p to the buffer, growing the buffer as 129 | // needed. The return value n is the length of p; err is always nil. If the 130 | // buffer becomes too large, Write will panic with ErrTooLarge. 131 | func (b *Buffer) Write(p []byte) (n int, err error) { 132 | b.startWrite() 133 | m, ok := b.tryGrowByReslice(len(p)) 134 | if !ok { 135 | m = b.grow(len(p)) 136 | } 137 | return copy(b.buf[m:], p), nil 138 | } 139 | 140 | // WriteString appends the contents of s to the buffer, growing the buffer as 141 | // needed. The return value n is the length of s; err is always nil. If the 142 | // buffer becomes too large, WriteString will panic with ErrTooLarge. 143 | func (b *Buffer) WriteString(s string) (n int, err error) { 144 | b.startWrite() 145 | m, ok := b.tryGrowByReslice(len(s)) 146 | if !ok { 147 | m = b.grow(len(s)) 148 | } 149 | return copy(b.buf[m:], s), nil 150 | } 151 | 152 | // WriteByte emits a single byte. 153 | func (b *Buffer) WriteByte(s byte) error { 154 | b.startWrite() 155 | if b.mode == UnsafeEscaped && 156 | (s >= utf8.RuneSelf || 157 | s == m.StartS[0] || s == m.EndS[0]) { 158 | // Unsafe byte. Escape it. 159 | _, err := b.WriteString(m.EscapeMarkS) 160 | return err 161 | } 162 | m, ok := b.tryGrowByReslice(1) 163 | if !ok { 164 | m = b.grow(1) 165 | } 166 | b.buf[m] = s 167 | return nil 168 | } 169 | 170 | // WriteRune emits a single rune. 171 | func (b *Buffer) WriteRune(s rune) error { 172 | b.startWrite() 173 | l := utf8.RuneLen(s) 174 | m, ok := b.tryGrowByReslice(l) 175 | if !ok { 176 | m = b.grow(l) 177 | } 178 | _ = utf8.EncodeRune(b.buf[m:], s) 179 | return nil 180 | } 181 | 182 | // finalize ensures that all the buffer is properly 183 | // marked. 184 | func (b *Buffer) finalize() { 185 | if b.mode == SafeRaw { 186 | b.validUntil = len(b.buf) 187 | } else { 188 | b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */) 189 | } 190 | if b.markerOpen { 191 | b.endRedactable() 192 | b.validUntil = len(b.buf) 193 | } 194 | } 195 | 196 | // endRedactable adds the closing redaction marker. 197 | func (b *Buffer) endRedactable() { 198 | if len(b.buf) == 0 { 199 | return 200 | } 201 | if bytes.HasSuffix(b.buf, m.StartBytes) { 202 | // Special case: remove a trailing open marker and call it a day. 203 | b.buf = b.buf[:len(b.buf)-m.StartLen] 204 | } else { 205 | p, ok := b.tryGrowByReslice(m.EndLen) 206 | if !ok { 207 | p = b.grow(m.EndLen) 208 | } 209 | copy(b.buf[p:], m.EndS) 210 | } 211 | b.markerOpen = false 212 | } 213 | 214 | func (b *Buffer) startWrite() { 215 | if b.mode == UnsafeEscaped && !b.markerOpen { 216 | b.startRedactable() 217 | b.validUntil = len(b.buf) 218 | } 219 | } 220 | 221 | // endRedactable adds the closing redaction marker. 222 | func (b *Buffer) startRedactable() { 223 | if bytes.HasSuffix(b.buf, m.EndBytes) { 224 | // Special case: remove a trailing closing marker and call it a day. 225 | b.buf = b.buf[:len(b.buf)-m.EndLen] 226 | } else { 227 | p, ok := b.tryGrowByReslice(len(m.StartS)) 228 | if !ok { 229 | p = b.grow(len(m.StartS)) 230 | } 231 | copy(b.buf[p:], m.StartS) 232 | } 233 | b.markerOpen = true 234 | } 235 | 236 | // escapeToEnd escapes occurrences of redaction markers in 237 | // b.buf[b.validUntil:] and advances b.validUntil until the end. 238 | func (b *Buffer) escapeToEnd(breakNewLines bool) { 239 | b.buf = escape.InternalEscapeBytes(b.buf, b.validUntil, breakNewLines, false /* trim */) 240 | b.validUntil = len(b.buf) 241 | } 242 | 243 | // GetMode retrieves the output mode. 244 | func (b *Buffer) GetMode() OutputMode { 245 | return b.mode 246 | } 247 | 248 | // SetMode changes the output mode. 249 | func (b *Buffer) SetMode(newMode OutputMode) { 250 | if b.mode == newMode { 251 | // noop 252 | return 253 | } 254 | if b.mode == UnsafeEscaped || b.mode == SafeEscaped { 255 | b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */) 256 | } 257 | if b.markerOpen { 258 | b.endRedactable() 259 | } 260 | b.validUntil = len(b.buf) 261 | b.mode = newMode 262 | } 263 | 264 | // Reset resets the buffer to be empty, 265 | // but it retains the underlying storage for use by future writes. 266 | // It also resets the output mode to UnsafeEscaped. 267 | func (b *Buffer) Reset() { 268 | b.buf = b.buf[:0] 269 | b.validUntil = 0 270 | b.mode = UnsafeEscaped 271 | b.markerOpen = false 272 | } 273 | 274 | // tryGrowByReslice is a inlineable version of grow for the fast-case where the 275 | // internal buffer only needs to be resliced. 276 | // It returns the index where bytes should be written and whether it succeeded. 277 | func (b *Buffer) tryGrowByReslice(n int) (int, bool) { 278 | if l := len(b.buf); n <= cap(b.buf)-l { 279 | b.buf = b.buf[:l+n] 280 | return l, true 281 | } 282 | return 0, false 283 | } 284 | 285 | // grow grows the buffer to guarantee space for n more bytes. 286 | // It returns the index where bytes should be written. 287 | // If the buffer can't grow it will panic with ErrTooLarge. 288 | func (b *Buffer) grow(n int) int { 289 | m := len(b.buf) 290 | // Try to grow by means of a reslice. 291 | if i, ok := b.tryGrowByReslice(n); ok { 292 | return i 293 | } 294 | if b.buf == nil && n <= smallBufferSize { 295 | b.buf = make([]byte, n, smallBufferSize) 296 | return 0 297 | } 298 | c := cap(b.buf) 299 | const maxInt = int(^uint(0) >> 1) 300 | if n <= c/2-m { 301 | // let capacity get twice as large. 302 | } else if c > maxInt-c-n { 303 | panic(ErrTooLarge) 304 | } else { 305 | // Not enough space anywhere, we need to allocate. 306 | buf := makeSlice(2*c + n) 307 | copy(buf, b.buf) 308 | b.buf = buf 309 | } 310 | b.buf = b.buf[:m+n] 311 | return m 312 | } 313 | 314 | // Grow grows the buffer's capacity, if necessary, to guarantee space for 315 | // another n bytes. After Grow(n), at least n bytes can be written to the 316 | // buffer without another allocation. 317 | // If n is negative, Grow will panic. 318 | // If the buffer can't grow it will panic with ErrTooLarge. 319 | func (b *Buffer) Grow(n int) { 320 | if n < 0 { 321 | panic(origFmt.Errorf("redact.Buffer.Grow: negative count")) 322 | } 323 | m := b.grow(n) 324 | b.buf = b.buf[:m] 325 | } 326 | 327 | // clone is used in tests. 328 | func (b *Buffer) clone() *Buffer { 329 | c := *b 330 | c.buf = append([]byte(nil), b.buf...) 331 | return &c 332 | } 333 | 334 | // makeSlice allocates a slice of size n. If the allocation fails, it panics 335 | // with ErrTooLarge. 336 | func makeSlice(n int) []byte { 337 | // If the make fails, give a known error. 338 | defer func() { 339 | if recover() != nil { 340 | panic(ErrTooLarge) 341 | } 342 | }() 343 | return make([]byte, n) 344 | } 345 | 346 | // ErrTooLarge is passed to panic if memory cannot be allocated to 347 | // store data in a buffer. 348 | var ErrTooLarge = origFmt.Errorf("redact.Buffer: too large") 349 | 350 | // smallBufferSize is an initial allocation minimal capacity. 351 | const smallBufferSize = 64 352 | -------------------------------------------------------------------------------- /internal/escape/escape.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package escape 16 | 17 | import ( 18 | "bytes" 19 | "unicode/utf8" 20 | 21 | m "github.com/cockroachdb/redact/internal/markers" 22 | ) 23 | 24 | // InternalEscapeBytes escapes redaction markers in the provided buf 25 | // starting at the location startLoc. 26 | // The bytes before startLoc are considered safe (already escaped). 27 | // 28 | // If breakNewLines is set, a closing redaction marker 29 | // is placed before sequences of one or more newline characters, 30 | // and an open redaction marker is placed afterwards. 31 | // 32 | // If strip is set, final newlines and spaces are trimmed from the 33 | // output. 34 | func InternalEscapeBytes(b []byte, startLoc int, breakNewLines, strip bool) (res []byte) { 35 | // Note: we use len(...RedactableS) and not len(...RedactableBytes) 36 | // because the ...S variant is a compile-time constant so this 37 | // accelerates the loops below. 38 | start, ls := m.StartBytes, len(m.StartS) 39 | end, le := m.EndBytes, len(m.EndS) 40 | escape := m.EscapeMarkBytes 41 | 42 | // Trim final newlines/spaces, for convenience. 43 | if strip { 44 | end := len(b) 45 | for i := end - 1; i >= startLoc; i-- { 46 | if b[i] == '\n' || b[i] == ' ' { 47 | end = i 48 | } else { 49 | break 50 | } 51 | } 52 | b = b[:end] 53 | } 54 | 55 | // res is the output slice. In the common case where there is 56 | // nothing to escape, the input slice is returned directly 57 | // and no allocation takes place. 58 | res = b 59 | // copied is true if and only if `res` is a copy of `b`. It only 60 | // turns to true if the loop below finds something to escape. 61 | copied := false 62 | // k is the index in b up to (and excluding) the byte which we've 63 | // already copied into res (if copied=true). 64 | k := 0 65 | 66 | for i := startLoc; i < len(b); i++ { 67 | if breakNewLines && b[i] == '\n' { 68 | if !copied { 69 | // We only allocate an output slice when we know we definitely 70 | // need it. 71 | res = make([]byte, 0, len(b)) 72 | copied = true 73 | } 74 | res = append(res, b[k:i]...) 75 | // Either add an end marker, or elide a start marker immediately prior. 76 | if bytes.HasSuffix(res, start) { 77 | res = res[:len(res)-ls] 78 | } else { 79 | res = append(res, end...) 80 | } 81 | // Advance to the last newline character. We want to forward 82 | // them all in a single call to doWrite, for performance. 83 | lastNewLine := i 84 | for lastNewLine < len(b) && b[lastNewLine] == '\n' { 85 | lastNewLine++ 86 | } 87 | res = append(res, b[i:lastNewLine]...) 88 | res = append(res, start...) 89 | k = lastNewLine 90 | i = lastNewLine - 1 91 | } else 92 | // Ensure that occurrences of the delimiter inside the string get 93 | // escaped. 94 | // Reminder: ls and le are likely greater than 1, as we are scanning 95 | // utf-8 encoded delimiters (the utf-8 encoding is multibyte). 96 | if i+ls <= len(b) && bytes.Equal(b[i:i+ls], start) { 97 | if !copied { 98 | // We only allocate an output slice when we know we definitely 99 | // need it. 100 | res = make([]byte, 0, len(b)+len(escape)) 101 | copied = true 102 | } 103 | res = append(res, b[k:i]...) 104 | res = append(res, escape...) 105 | // Advance the counters by the length (in bytes) of the delimiter. 106 | k = i + ls 107 | i += ls - 1 /* -1 because we have i++ at the end of every iteration */ 108 | } else if i+le <= len(b) && bytes.Equal(b[i:i+le], end) { 109 | if !copied { 110 | // See the comment above about res allocation. 111 | res = make([]byte, 0, len(b)+len(escape)) 112 | copied = true 113 | } 114 | res = append(res, b[k:i]...) 115 | res = append(res, escape...) 116 | // Advance the counters by the length (in bytes) of the delimiter. 117 | k = i + le 118 | i += le - 1 /* -1 because we have i++ at the end of every iteration */ 119 | } 120 | } 121 | // If the string terminates with an invalid utf-8 sequence, we 122 | // want to avoid a run-in with a subsequent redaction marker. 123 | if r, s := utf8.DecodeLastRune(b); s == 1 && r == utf8.RuneError { 124 | if !copied { 125 | // See the comment above about res allocation. 126 | res = make([]byte, 0, len(b)+len(escape)) 127 | copied = true 128 | } 129 | res = append(res, b[k:]...) 130 | res = append(res, escape...) 131 | k = len(b) 132 | } 133 | if copied { 134 | res = append(res, b[k:]...) 135 | } 136 | return 137 | } 138 | -------------------------------------------------------------------------------- /internal/escape/escape_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package escape 16 | 17 | import "testing" 18 | 19 | func TestInternalEscape(t *testing.T) { 20 | testCases := []struct { 21 | input []byte 22 | start int 23 | bnl bool 24 | strip bool 25 | expected string 26 | }{ 27 | {nil, 0, false, false, ""}, 28 | {[]byte(""), 0, false, false, ""}, 29 | {[]byte("abc"), 0, false, false, "abc"}, 30 | {[]byte("‹abc›"), 0, false, false, "?abc?"}, 31 | {[]byte("‹abc›"), 3, false, false, "‹abc?"}, 32 | {[]byte("‹abc›def›ghi"), 3, false, false, "‹abc?def?ghi"}, 33 | {[]byte("‹abc›"), len([]byte("‹abc›")), false, false, "‹abc›"}, 34 | {[]byte("‹abc›‹def›"), len([]byte("‹abc›")), false, false, "‹abc›?def?"}, 35 | {[]byte("‹abc›\n‹d\nef›"), len([]byte("‹abc›")), false, false, "‹abc›\n?d\nef?"}, 36 | {[]byte("abc\n‹d\nef›\n \n\n "), len([]byte("abc")), true, false, "abc›\n‹?d›\n‹ef?›\n‹ ›\n\n‹ "}, 37 | {[]byte("abc\n‹d\nef›\n \n\n "), len([]byte("abc")), true, true, "abc›\n‹?d›\n‹ef?"}, 38 | {[]byte("‹abc› ‹def›"), len([]byte("‹abc› ")), true, true, "‹abc› ?def?"}, 39 | {[]byte("abc‹\ndef"), len([]byte("abc‹")), true, true, "abc\n‹def"}, 40 | } 41 | 42 | for _, tc := range testCases { 43 | actual := string(InternalEscapeBytes(tc.input, tc.start, tc.bnl, tc.strip)) 44 | if actual != tc.expected { 45 | t.Errorf("%q/%d: expected %q, got %q", string(tc.input), tc.start, tc.expected, actual) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/fmtforward/make_format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package fmtforward 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "strconv" 21 | "strings" 22 | ) 23 | 24 | // ReproducePrintf calls fmt.Print/fmt.Printf on the argument 25 | // based on the details present in the fmt.State and 26 | // writes to the io.Writer. 27 | func ReproducePrintf(w io.Writer, s fmt.State, verb rune, arg interface{}) { 28 | justV, revFmt := MakeFormat(s, verb) 29 | if justV { 30 | // Common case, avoids generating then parsing the format again. 31 | fmt.Fprint(w, arg) 32 | } else { 33 | fmt.Fprintf(w, revFmt, arg) 34 | } 35 | } 36 | 37 | // MakeFormat reproduces the format currently active 38 | // in fmt.State and verb. This is provided because Go's standard 39 | // fmt.State does not make the original format string available to us. 40 | // 41 | // If the return value justV is true, then the current state 42 | // was found to be %v exactly; in that case the caller 43 | // can avoid a full-blown Printf call and use just Print instead 44 | // to take a shortcut. 45 | func MakeFormat(s fmt.State, verb rune) (justV bool, format string) { 46 | plus, minus, hash, sp, z := s.Flag('+'), s.Flag('-'), s.Flag('#'), s.Flag(' '), s.Flag('0') 47 | w, wp := s.Width() 48 | p, pp := s.Precision() 49 | 50 | if !plus && !minus && !hash && !sp && !z && !wp && !pp { 51 | switch verb { 52 | case 'v': 53 | return true, "%v" 54 | case 's': 55 | return false, "%s" 56 | case 'd': 57 | return false, "%d" 58 | } 59 | // Other cases handled in the slow path below. 60 | } 61 | 62 | var f strings.Builder 63 | f.WriteByte('%') 64 | if plus { 65 | f.WriteByte('+') 66 | } 67 | if minus { 68 | f.WriteByte('-') 69 | } 70 | if hash { 71 | f.WriteByte('#') 72 | } 73 | if sp { 74 | f.WriteByte(' ') 75 | } 76 | if z { 77 | f.WriteByte('0') 78 | } 79 | if wp { 80 | f.WriteString(strconv.Itoa(w)) 81 | } 82 | if pp { 83 | f.WriteByte('.') 84 | f.WriteString(strconv.Itoa(p)) 85 | } 86 | f.WriteRune(verb) 87 | return false, f.String() 88 | } 89 | -------------------------------------------------------------------------------- /internal/fmtforward/make_format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package fmtforward 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | ) 21 | 22 | // TestMakeFormat verifies that the makeFormat() helper is able to 23 | // reproduce the format given as input to fmt function. 24 | func TestMakeFormat(t *testing.T) { 25 | testData := []string{ 26 | "%c", "%v", "%q", 27 | "%3f", "%.3f", "%2.3f", 28 | "%# v", "%012s", 29 | "%+v", "%-12s", 30 | } 31 | 32 | for _, test := range testData { 33 | justV, revFmt := getFormat(test) 34 | if (test == "%v") != justV { 35 | t.Errorf("%q: expected justV %v, got %v", test, test == "%v", justV) 36 | } 37 | if revFmt != test { 38 | t.Errorf("%q: got %q instead", test, revFmt) 39 | } 40 | } 41 | } 42 | 43 | type formatTester struct { 44 | fn func(fmt.State, rune) 45 | } 46 | 47 | func (f formatTester) Format(s fmt.State, verb rune) { 48 | f.fn(s, verb) 49 | } 50 | 51 | func getFormat(testFmt string) (justV bool, revFmt string) { 52 | f := formatTester{func(s fmt.State, verb rune) { 53 | justV, revFmt = MakeFormat(s, verb) 54 | }} 55 | _ = fmt.Sprintf(testFmt, f) 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /internal/markers/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package markers 16 | 17 | import "regexp" 18 | 19 | // Internal constants. 20 | const ( 21 | Start = '‹' 22 | StartS = string(Start) 23 | StartLen = len(StartS) 24 | End = '›' 25 | EndS = string(End) 26 | EndLen = len(EndS) 27 | EscapeMark = '?' 28 | EscapeMarkS = string(EscapeMark) 29 | RedactedS = StartS + "×" + EndS 30 | ) 31 | 32 | // Internal variables. 33 | var ( 34 | StartBytes = []byte(StartS) 35 | EndBytes = []byte(EndS) 36 | EscapeMarkBytes = []byte(EscapeMarkS) 37 | RedactedBytes = []byte(RedactedS) 38 | ReStripSensitive = regexp.MustCompile(StartS + "[^" + StartS + EndS + "]*" + EndS) 39 | ReStripMarkers = regexp.MustCompile("[" + StartS + EndS + "]") 40 | ) 41 | -------------------------------------------------------------------------------- /internal/markers/markers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package markers 16 | 17 | import i "github.com/cockroachdb/redact/interfaces" 18 | 19 | // RedactableString is a string that contains a mix of safe and unsafe 20 | // bits of data, but where it is known that unsafe bits are enclosed 21 | // by redaction markers ‹ and ›, and occurrences of the markers 22 | // inside the original data items have been escaped. 23 | // 24 | // Instances of RedactableString should not be constructed directly; 25 | // instead use the facilities from print.go (Sprint, Sprintf) 26 | // or the methods below. 27 | type RedactableString string 28 | 29 | // StripMarkers removes the redaction markers from the 30 | // RedactableString. This returns an unsafe string where all safe and 31 | // unsafe bits are mixed together. 32 | func (s RedactableString) StripMarkers() string { 33 | return ReStripMarkers.ReplaceAllString(string(s), "") 34 | } 35 | 36 | // Redact replaces all occurrences of unsafe substrings by the 37 | // “Redacted” marker, ‹×›. The result string is still safe. 38 | func (s RedactableString) Redact() RedactableString { 39 | return RedactableString(ReStripSensitive.ReplaceAllString(string(s), RedactedS)) 40 | } 41 | 42 | // ToBytes converts the string to a byte slice. 43 | func (s RedactableString) ToBytes() RedactableBytes { 44 | return RedactableBytes([]byte(string(s))) 45 | } 46 | 47 | // SafeFormat formats the redactable safely. 48 | func (s RedactableString) SafeFormat(sp i.SafePrinter, _ rune) { 49 | // As per annotateArgs() in markers_internal_print.go, 50 | // we consider the redactable string not further formattable. 51 | sp.Print(s) 52 | } 53 | 54 | // RedactableBytes is like RedactableString but is a byte slice. 55 | // 56 | // Instances of RedactableBytes should not be constructed directly; 57 | // instead use the facilities from print.go (Sprint, Sprintf) 58 | // or the methods below. 59 | type RedactableBytes []byte 60 | 61 | // StripMarkers removes the redaction markers from the 62 | // RedactableBytes. This returns an unsafe string where all safe and 63 | // unsafe bits are mixed together. 64 | func (s RedactableBytes) StripMarkers() []byte { 65 | return ReStripMarkers.ReplaceAll([]byte(s), nil) 66 | } 67 | 68 | // Redact replaces all occurrences of unsafe substrings by the 69 | // “Redacted” marker, ‹×›. 70 | func (s RedactableBytes) Redact() RedactableBytes { 71 | return RedactableBytes(ReStripSensitive.ReplaceAll(s, RedactedBytes)) 72 | } 73 | 74 | // ToString converts the byte slice to a string. 75 | func (s RedactableBytes) ToString() RedactableString { 76 | return RedactableString(string([]byte(s))) 77 | } 78 | 79 | // SafeFormat formats the redactable safely. 80 | func (s RedactableBytes) SafeFormat(sp i.SafePrinter, _ rune) { 81 | // As per annotateArgs() in markers_internal_print.go, 82 | // we consider the redactable bytes not further formattable. 83 | sp.Print(s) 84 | } 85 | 86 | // StartMarker returns the start delimiter for an unsafe string. 87 | func StartMarker() []byte { return []byte(StartS) } 88 | 89 | // EndMarker returns the end delimiter for an unsafe string. 90 | func EndMarker() []byte { return []byte(EndS) } 91 | 92 | // RedactedMarker returns the special string used by Redact. 93 | func RedactedMarker() []byte { return []byte(RedactedS) } 94 | 95 | // EscapeMarkers escapes the special delimiters from the provided 96 | // byte slice. 97 | func EscapeMarkers(s []byte) []byte { 98 | return ReStripMarkers.ReplaceAll(s, EscapeMarkBytes) 99 | } 100 | -------------------------------------------------------------------------------- /internal/redact/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | // Package redact is a helper internal package that provides the 16 | // Unsafe and Safe value wrappers. Its name is 'redact' even though a 17 | // better name would be 'wrappers' so as to ensure that the type name 18 | // of generated values contains the 'redact.' prefix. 19 | package redact 20 | -------------------------------------------------------------------------------- /internal/redact/wrappers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | origFmt "fmt" 19 | 20 | i "github.com/cockroachdb/redact/interfaces" 21 | "github.com/cockroachdb/redact/internal/fmtforward" 22 | ) 23 | 24 | // Unsafe turns any value that would otherwise be considered safe, 25 | // into an unsafe value. 26 | func Unsafe(a interface{}) interface{} { 27 | return unsafeWrap{a} 28 | } 29 | 30 | // UnsafeWrap is the type of wrapper produced by Unsafe. 31 | // This is exported only for use by the rfmt package. 32 | // Client packages should not make assumptions about 33 | // the concrete return type of Unsafe(). 34 | type UnsafeWrap = unsafeWrap 35 | 36 | type unsafeWrap struct { 37 | a interface{} 38 | } 39 | 40 | func (w unsafeWrap) GetValue() interface{} { return w.a } 41 | 42 | func (w unsafeWrap) Format(s origFmt.State, verb rune) { 43 | fmtforward.ReproducePrintf(s, s, verb, w.a) 44 | } 45 | 46 | // Safe turns any value into an object that is considered as safe by 47 | // the formatter. 48 | // 49 | // This is provided as an “escape hatch” for cases where the other 50 | // interfaces and conventions fail. Increased usage of this mechanism 51 | // should be taken as a signal that a new abstraction is missing. 52 | // The implementation is also slow. 53 | func Safe(a interface{}) i.SafeValue { 54 | return safeWrapper{a} 55 | } 56 | 57 | // SafeWrapper is the type of wrapper produced by Safe. 58 | // This is exported only for use by the rfmt package. 59 | // Client packages should not make assumptions about 60 | // the concrete return type of Safe(). 61 | type SafeWrapper = safeWrapper 62 | 63 | type safeWrapper struct { 64 | a interface{} 65 | } 66 | 67 | var _ i.SafeValue = safeWrapper{} 68 | var _ origFmt.Formatter = safeWrapper{} 69 | var _ i.SafeMessager = safeWrapper{} 70 | 71 | func (w safeWrapper) GetValue() interface{} { return w.a } 72 | 73 | // SafeValue implements the SafeValue interface. 74 | func (w safeWrapper) SafeValue() {} 75 | 76 | // Format implements the fmt.Formatter interface. 77 | func (w safeWrapper) Format(s origFmt.State, verb rune) { 78 | fmtforward.ReproducePrintf(s, s, verb, w.a) 79 | } 80 | 81 | // SafeMessage implements SafeMessager. 82 | func (w safeWrapper) SafeMessage() string { 83 | return origFmt.Sprintf("%v", w.a) 84 | } 85 | -------------------------------------------------------------------------------- /internal/rfmt/README.md: -------------------------------------------------------------------------------- 1 | Overall code structure 2 | ====================== 3 | 4 | This directory imports the "printf" logic as-is from the Go standard 5 | library and instruments it for redaction of sensitive data. 6 | 7 | Overall, the original Go code is structured as follows: 8 | 9 | - the top-level API functions (`Printf` `Fprintf` etc) instantiate a 10 | "printer" struct called `pp`, then call the `doPrint*()` methods 11 | on it. 12 | 13 | - the `pp` contains a byte slice (`pp.buf`, type `buffer`) that 14 | accumulates the result of formatting *regardless of the final output 15 | of the API* - i.e. a buffer is used even when using `Fprint` to an 16 | `io.Writer`. Only after the `doPrint()` method finishes, is the 17 | `buffer` copied to the final `io.Writer` in the `Fprint*` variants. 18 | 19 | - each of the `doPrint` methods does some analysis on the argument 20 | list - quite simple for e.g. `doPrintln`, more intricate for 21 | `doPrintf`. As part of the analysis it emits "spacing" or no-op 22 | bytes directly on the `pp.buf`. For example `doPrint` emits spaces 23 | between arguments directly, `doPrintf` emits the non-formatting 24 | characters from the format string, as well as certain *constant* 25 | error strings (e.g. `%(BADPREC)`). 26 | 27 | Refreshing the sources 28 | ====================== 29 | 30 | The files in this directory have been imported from 31 | 32 | `$GOROOT/src/fmt/{format,print}.go` 33 | 34 | and 35 | 36 | `$GOROOT/src/internal/fmtsort` 37 | 38 | And patched using the included `.diff` files. 39 | 40 | To upgrade to a newer Go implementation, import the files anew and 41 | re-apply the patches. 42 | 43 | See the script `refresh.sh` for details. 44 | -------------------------------------------------------------------------------- /internal/rfmt/fmtsort/sort.go: -------------------------------------------------------------------------------- 1 | // Code generated from the Go standard library. DO NOT EDIT 2 | // GENERATED FILE DO NOT EDIT 3 | // Copyright 2018 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | // Package fmtsort provides a general stable ordering mechanism 8 | // for maps, on behalf of the fmt and text/template packages. 9 | // It is not guaranteed to be efficient and works only for types 10 | // that are valid map keys. 11 | package fmtsort 12 | 13 | import ( 14 | "reflect" 15 | "sort" 16 | ) 17 | 18 | // Note: Throughout this package we avoid calling reflect.Value.Interface as 19 | // it is not always legal to do so and it's easier to avoid the issue than to face it. 20 | 21 | // SortedMap represents a map's keys and values. The keys and values are 22 | // aligned in index order: Value[i] is the value in the map corresponding to Key[i]. 23 | type SortedMap struct { 24 | Key []reflect.Value 25 | Value []reflect.Value 26 | } 27 | 28 | func (o *SortedMap) Len() int { return len(o.Key) } 29 | func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } 30 | func (o *SortedMap) Swap(i, j int) { 31 | o.Key[i], o.Key[j] = o.Key[j], o.Key[i] 32 | o.Value[i], o.Value[j] = o.Value[j], o.Value[i] 33 | } 34 | 35 | // Sort accepts a map and returns a SortedMap that has the same keys and 36 | // values but in a stable sorted order according to the keys, modulo issues 37 | // raised by unorderable key values such as NaNs. 38 | // 39 | // The ordering rules are more general than with Go's < operator: 40 | // 41 | // - when applicable, nil compares low 42 | // - ints, floats, and strings order by < 43 | // - NaN compares less than non-NaN floats 44 | // - bool compares false before true 45 | // - complex compares real, then imag 46 | // - pointers compare by machine address 47 | // - channel values compare by machine address 48 | // - structs compare each field in turn 49 | // - arrays compare each element in turn. 50 | // Otherwise identical arrays compare by length. 51 | // - interface values compare first by reflect.Type describing the concrete type 52 | // and then by concrete value as described in the previous rules. 53 | // 54 | func Sort(mapValue reflect.Value) *SortedMap { 55 | if mapValue.Type().Kind() != reflect.Map { 56 | return nil 57 | } 58 | // Note: this code is arranged to not panic even in the presence 59 | // of a concurrent map update. The runtime is responsible for 60 | // yelling loudly if that happens. See issue 33275. 61 | n := mapValue.Len() 62 | key := make([]reflect.Value, 0, n) 63 | value := make([]reflect.Value, 0, n) 64 | iter := mapValue.MapRange() 65 | for iter.Next() { 66 | key = append(key, iter.Key()) 67 | value = append(value, iter.Value()) 68 | } 69 | sorted := &SortedMap{ 70 | Key: key, 71 | Value: value, 72 | } 73 | sort.Stable(sorted) 74 | return sorted 75 | } 76 | 77 | // compare compares two values of the same type. It returns -1, 0, 1 78 | // according to whether a > b (1), a == b (0), or a < b (-1). 79 | // If the types differ, it returns -1. 80 | // See the comment on Sort for the comparison rules. 81 | func compare(aVal, bVal reflect.Value) int { 82 | aType, bType := aVal.Type(), bVal.Type() 83 | if aType != bType { 84 | return -1 // No good answer possible, but don't return 0: they're not equal. 85 | } 86 | switch aVal.Kind() { 87 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 88 | a, b := aVal.Int(), bVal.Int() 89 | switch { 90 | case a < b: 91 | return -1 92 | case a > b: 93 | return 1 94 | default: 95 | return 0 96 | } 97 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 98 | a, b := aVal.Uint(), bVal.Uint() 99 | switch { 100 | case a < b: 101 | return -1 102 | case a > b: 103 | return 1 104 | default: 105 | return 0 106 | } 107 | case reflect.String: 108 | a, b := aVal.String(), bVal.String() 109 | switch { 110 | case a < b: 111 | return -1 112 | case a > b: 113 | return 1 114 | default: 115 | return 0 116 | } 117 | case reflect.Float32, reflect.Float64: 118 | return floatCompare(aVal.Float(), bVal.Float()) 119 | case reflect.Complex64, reflect.Complex128: 120 | a, b := aVal.Complex(), bVal.Complex() 121 | if c := floatCompare(real(a), real(b)); c != 0 { 122 | return c 123 | } 124 | return floatCompare(imag(a), imag(b)) 125 | case reflect.Bool: 126 | a, b := aVal.Bool(), bVal.Bool() 127 | switch { 128 | case a == b: 129 | return 0 130 | case a: 131 | return 1 132 | default: 133 | return -1 134 | } 135 | case reflect.Ptr, reflect.UnsafePointer: 136 | a, b := aVal.Pointer(), bVal.Pointer() 137 | switch { 138 | case a < b: 139 | return -1 140 | case a > b: 141 | return 1 142 | default: 143 | return 0 144 | } 145 | case reflect.Chan: 146 | if c, ok := nilCompare(aVal, bVal); ok { 147 | return c 148 | } 149 | ap, bp := aVal.Pointer(), bVal.Pointer() 150 | switch { 151 | case ap < bp: 152 | return -1 153 | case ap > bp: 154 | return 1 155 | default: 156 | return 0 157 | } 158 | case reflect.Struct: 159 | for i := 0; i < aVal.NumField(); i++ { 160 | if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { 161 | return c 162 | } 163 | } 164 | return 0 165 | case reflect.Array: 166 | for i := 0; i < aVal.Len(); i++ { 167 | if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 { 168 | return c 169 | } 170 | } 171 | return 0 172 | case reflect.Interface: 173 | if c, ok := nilCompare(aVal, bVal); ok { 174 | return c 175 | } 176 | c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type())) 177 | if c != 0 { 178 | return c 179 | } 180 | return compare(aVal.Elem(), bVal.Elem()) 181 | default: 182 | // Certain types cannot appear as keys (maps, funcs, slices), but be explicit. 183 | panic("bad type in compare: " + aType.String()) 184 | } 185 | } 186 | 187 | // nilCompare checks whether either value is nil. If not, the boolean is false. 188 | // If either value is nil, the boolean is true and the integer is the comparison 189 | // value. The comparison is defined to be 0 if both are nil, otherwise the one 190 | // nil value compares low. Both arguments must represent a chan, func, 191 | // interface, map, pointer, or slice. 192 | func nilCompare(aVal, bVal reflect.Value) (int, bool) { 193 | if aVal.IsNil() { 194 | if bVal.IsNil() { 195 | return 0, true 196 | } 197 | return -1, true 198 | } 199 | if bVal.IsNil() { 200 | return 1, true 201 | } 202 | return 0, false 203 | } 204 | 205 | // floatCompare compares two floating-point values. NaNs compare low. 206 | func floatCompare(a, b float64) int { 207 | switch { 208 | case isNaN(a): 209 | return -1 // No good answer if b is a NaN so don't bother checking. 210 | case isNaN(b): 211 | return 1 212 | case a < b: 213 | return -1 214 | case a > b: 215 | return 1 216 | } 217 | return 0 218 | } 219 | 220 | func isNaN(a float64) bool { 221 | return a != a 222 | } 223 | -------------------------------------------------------------------------------- /internal/rfmt/format.go: -------------------------------------------------------------------------------- 1 | // Code generated from the Go standard library. DO NOT EDIT 2 | // GENERATED FILE DO NOT EDIT 3 | // 4 | // Copyright 2009 The Go Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | package rfmt 9 | 10 | import ( 11 | "strconv" 12 | "unicode/utf8" 13 | ) 14 | 15 | const ( 16 | ldigits = "0123456789abcdefx" 17 | udigits = "0123456789ABCDEFX" 18 | ) 19 | 20 | const ( 21 | signed = true 22 | unsigned = false 23 | ) 24 | 25 | // flags placed in a separate struct for easy clearing. 26 | type fmtFlags struct { 27 | widPresent bool 28 | precPresent bool 29 | minus bool 30 | plus bool 31 | sharp bool 32 | space bool 33 | zero bool 34 | 35 | // For the formats %+v %#v, we set the plusV/sharpV flags 36 | // and clear the plus/sharp flags since %+v and %#v are in effect 37 | // different, flagless formats set at the top level. 38 | plusV bool 39 | sharpV bool 40 | } 41 | 42 | // A fmt is the raw formatter used by Printf etc. 43 | // It prints into a buffer that must be set up separately. 44 | type fmt struct { 45 | buf *buffer 46 | 47 | fmtFlags 48 | 49 | wid int // width 50 | prec int // precision 51 | 52 | // intbuf is large enough to store %b of an int64 with a sign and 53 | // avoids padding at the end of the struct on 32 bit architectures. 54 | intbuf [68]byte 55 | } 56 | 57 | func (f *fmt) clearflags() { 58 | f.fmtFlags = fmtFlags{} 59 | } 60 | 61 | func (f *fmt) init(buf *buffer) { 62 | f.buf = buf 63 | f.clearflags() 64 | } 65 | 66 | // writePadding generates n bytes of padding. 67 | func (f *fmt) writePadding(n int) { 68 | if n <= 0 { // No padding bytes needed. 69 | return 70 | } 71 | // Make enough room for padding. 72 | f.buf.Grow(n) 73 | // Decide which byte the padding should be filled with. 74 | padByte := byte(' ') 75 | if f.zero { 76 | padByte = byte('0') 77 | } 78 | // Fill padding with padByte. 79 | for i := 0; i < n; i++ { 80 | f.buf.writeByte(padByte) 81 | } 82 | } 83 | 84 | // pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). 85 | func (f *fmt) pad(b []byte) { 86 | if !f.widPresent || f.wid == 0 { 87 | f.buf.write(b) 88 | return 89 | } 90 | width := f.wid - utf8.RuneCount(b) 91 | if !f.minus { 92 | // left padding 93 | f.writePadding(width) 94 | f.buf.write(b) 95 | } else { 96 | // right padding 97 | f.buf.write(b) 98 | f.writePadding(width) 99 | } 100 | } 101 | 102 | // padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). 103 | func (f *fmt) padString(s string) { 104 | if !f.widPresent || f.wid == 0 { 105 | f.buf.writeString(s) 106 | return 107 | } 108 | width := f.wid - utf8.RuneCountInString(s) 109 | if !f.minus { 110 | // left padding 111 | f.writePadding(width) 112 | f.buf.writeString(s) 113 | } else { 114 | // right padding 115 | f.buf.writeString(s) 116 | f.writePadding(width) 117 | } 118 | } 119 | 120 | // fmtBoolean formats a boolean. 121 | func (f *fmt) fmtBoolean(v bool) { 122 | if v { 123 | f.padString("true") 124 | } else { 125 | f.padString("false") 126 | } 127 | } 128 | 129 | // fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'". 130 | func (f *fmt) fmtUnicode(u uint64) { 131 | buf := f.intbuf[0:] 132 | 133 | // With default precision set the maximum needed buf length is 18 134 | // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits 135 | // into the already allocated intbuf with a capacity of 68 bytes. 136 | prec := 4 137 | if f.precPresent && f.prec > 4 { 138 | prec = f.prec 139 | // Compute space needed for "U+" , number, " '", character, "'". 140 | width := 2 + prec + 2 + utf8.UTFMax + 1 141 | if width > len(buf) { 142 | buf = make([]byte, width) 143 | } 144 | } 145 | 146 | // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left. 147 | i := len(buf) 148 | 149 | // For %#U we want to add a space and a quoted character at the end of the buffer. 150 | if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { 151 | i-- 152 | buf[i] = '\'' 153 | i -= utf8.RuneLen(rune(u)) 154 | utf8.EncodeRune(buf[i:], rune(u)) 155 | i-- 156 | buf[i] = '\'' 157 | i-- 158 | buf[i] = ' ' 159 | } 160 | // Format the Unicode code point u as a hexadecimal number. 161 | for u >= 16 { 162 | i-- 163 | buf[i] = udigits[u&0xF] 164 | prec-- 165 | u >>= 4 166 | } 167 | i-- 168 | buf[i] = udigits[u] 169 | prec-- 170 | // Add zeros in front of the number until requested precision is reached. 171 | for prec > 0 { 172 | i-- 173 | buf[i] = '0' 174 | prec-- 175 | } 176 | // Add a leading "U+". 177 | i-- 178 | buf[i] = '+' 179 | i-- 180 | buf[i] = 'U' 181 | 182 | oldZero := f.zero 183 | f.zero = false 184 | f.pad(buf[i:]) 185 | f.zero = oldZero 186 | } 187 | 188 | // fmtInteger formats signed and unsigned integers. 189 | func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) { 190 | negative := isSigned && int64(u) < 0 191 | if negative { 192 | u = -u 193 | } 194 | 195 | buf := f.intbuf[0:] 196 | // The already allocated f.intbuf with a capacity of 68 bytes 197 | // is large enough for integer formatting when no precision or width is set. 198 | if f.widPresent || f.precPresent { 199 | // Account 3 extra bytes for possible addition of a sign and "0x". 200 | width := 3 + f.wid + f.prec // wid and prec are always positive. 201 | if width > len(buf) { 202 | // We're going to need a bigger boat. 203 | buf = make([]byte, width) 204 | } 205 | } 206 | 207 | // Two ways to ask for extra leading zero digits: %.3d or %03d. 208 | // If both are specified the f.zero flag is ignored and 209 | // padding with spaces is used instead. 210 | prec := 0 211 | if f.precPresent { 212 | prec = f.prec 213 | // Precision of 0 and value of 0 means "print nothing" but padding. 214 | if prec == 0 && u == 0 { 215 | oldZero := f.zero 216 | f.zero = false 217 | f.writePadding(f.wid) 218 | f.zero = oldZero 219 | return 220 | } 221 | } else if f.zero && f.widPresent { 222 | prec = f.wid 223 | if negative || f.plus || f.space { 224 | prec-- // leave room for sign 225 | } 226 | } 227 | 228 | // Because printing is easier right-to-left: format u into buf, ending at buf[i]. 229 | // We could make things marginally faster by splitting the 32-bit case out 230 | // into a separate block but it's not worth the duplication, so u has 64 bits. 231 | i := len(buf) 232 | // Use constants for the division and modulo for more efficient code. 233 | // Switch cases ordered by popularity. 234 | switch base { 235 | case 10: 236 | for u >= 10 { 237 | i-- 238 | next := u / 10 239 | buf[i] = byte('0' + u - next*10) 240 | u = next 241 | } 242 | case 16: 243 | for u >= 16 { 244 | i-- 245 | buf[i] = digits[u&0xF] 246 | u >>= 4 247 | } 248 | case 8: 249 | for u >= 8 { 250 | i-- 251 | buf[i] = byte('0' + u&7) 252 | u >>= 3 253 | } 254 | case 2: 255 | for u >= 2 { 256 | i-- 257 | buf[i] = byte('0' + u&1) 258 | u >>= 1 259 | } 260 | default: 261 | panic("fmt: unknown base; can't happen") 262 | } 263 | i-- 264 | buf[i] = digits[u] 265 | for i > 0 && prec > len(buf)-i { 266 | i-- 267 | buf[i] = '0' 268 | } 269 | 270 | // Various prefixes: 0x, -, etc. 271 | if f.sharp { 272 | switch base { 273 | case 2: 274 | // Add a leading 0b. 275 | i-- 276 | buf[i] = 'b' 277 | i-- 278 | buf[i] = '0' 279 | case 8: 280 | if buf[i] != '0' { 281 | i-- 282 | buf[i] = '0' 283 | } 284 | case 16: 285 | // Add a leading 0x or 0X. 286 | i-- 287 | buf[i] = digits[16] 288 | i-- 289 | buf[i] = '0' 290 | } 291 | } 292 | if verb == 'O' { 293 | i-- 294 | buf[i] = 'o' 295 | i-- 296 | buf[i] = '0' 297 | } 298 | 299 | if negative { 300 | i-- 301 | buf[i] = '-' 302 | } else if f.plus { 303 | i-- 304 | buf[i] = '+' 305 | } else if f.space { 306 | i-- 307 | buf[i] = ' ' 308 | } 309 | 310 | // Left padding with zeros has already been handled like precision earlier 311 | // or the f.zero flag is ignored due to an explicitly set precision. 312 | oldZero := f.zero 313 | f.zero = false 314 | f.pad(buf[i:]) 315 | f.zero = oldZero 316 | } 317 | 318 | // truncateString truncates the string s to the specified precision, if present. 319 | func (f *fmt) truncateString(s string) string { 320 | if f.precPresent { 321 | n := f.prec 322 | for i := range s { 323 | n-- 324 | if n < 0 { 325 | return s[:i] 326 | } 327 | } 328 | } 329 | return s 330 | } 331 | 332 | // truncate truncates the byte slice b as a string of the specified precision, if present. 333 | func (f *fmt) truncate(b []byte) []byte { 334 | if f.precPresent { 335 | n := f.prec 336 | for i := 0; i < len(b); { 337 | n-- 338 | if n < 0 { 339 | return b[:i] 340 | } 341 | wid := 1 342 | if b[i] >= utf8.RuneSelf { 343 | _, wid = utf8.DecodeRune(b[i:]) 344 | } 345 | i += wid 346 | } 347 | } 348 | return b 349 | } 350 | 351 | // fmtS formats a string. 352 | func (f *fmt) fmtS(s string) { 353 | s = f.truncateString(s) 354 | f.padString(s) 355 | } 356 | 357 | // fmtBs formats the byte slice b as if it was formatted as string with fmtS. 358 | func (f *fmt) fmtBs(b []byte) { 359 | b = f.truncate(b) 360 | f.pad(b) 361 | } 362 | 363 | // fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes. 364 | func (f *fmt) fmtSbx(s string, b []byte, digits string) { 365 | length := len(b) 366 | if b == nil { 367 | // No byte slice present. Assume string s should be encoded. 368 | length = len(s) 369 | } 370 | // Set length to not process more bytes than the precision demands. 371 | if f.precPresent && f.prec < length { 372 | length = f.prec 373 | } 374 | // Compute width of the encoding taking into account the f.sharp and f.space flag. 375 | width := 2 * length 376 | if width > 0 { 377 | if f.space { 378 | // Each element encoded by two hexadecimals will get a leading 0x or 0X. 379 | if f.sharp { 380 | width *= 2 381 | } 382 | // Elements will be separated by a space. 383 | width += length - 1 384 | } else if f.sharp { 385 | // Only a leading 0x or 0X will be added for the whole string. 386 | width += 2 387 | } 388 | } else { // The byte slice or string that should be encoded is empty. 389 | if f.widPresent { 390 | f.writePadding(f.wid) 391 | } 392 | return 393 | } 394 | // Handle padding to the left. 395 | if f.widPresent && f.wid > width && !f.minus { 396 | f.writePadding(f.wid - width) 397 | } 398 | // Write the encoding directly into the output buffer. 399 | if f.sharp { 400 | // Add leading 0x or 0X. 401 | f.buf.WriteByte('0') 402 | f.buf.WriteByte(digits[16]) 403 | } 404 | var c byte 405 | for i := 0; i < length; i++ { 406 | if f.space && i > 0 { 407 | // Separate elements with a space. 408 | f.buf.WriteByte(' ') 409 | if f.sharp { 410 | // Add leading 0x or 0X for each element. 411 | f.buf.WriteByte('0') 412 | f.buf.WriteByte(digits[16]) 413 | } 414 | } 415 | if b != nil { 416 | c = b[i] // Take a byte from the input byte slice. 417 | } else { 418 | c = s[i] // Take a byte from the input string. 419 | } 420 | // Encode each byte as two hexadecimal digits. 421 | f.buf.WriteByte(digits[c>>4]) 422 | f.buf.WriteByte(digits[c&0xF]) 423 | } 424 | // Handle padding to the right. 425 | if f.widPresent && f.wid > width && f.minus { 426 | f.writePadding(f.wid - width) 427 | } 428 | } 429 | 430 | // fmtSx formats a string as a hexadecimal encoding of its bytes. 431 | func (f *fmt) fmtSx(s, digits string) { 432 | f.fmtSbx(s, nil, digits) 433 | } 434 | 435 | // fmtBx formats a byte slice as a hexadecimal encoding of its bytes. 436 | func (f *fmt) fmtBx(b []byte, digits string) { 437 | f.fmtSbx("", b, digits) 438 | } 439 | 440 | // fmtQ formats a string as a double-quoted, escaped Go string constant. 441 | // If f.sharp is set a raw (backquoted) string may be returned instead 442 | // if the string does not contain any control characters other than tab. 443 | func (f *fmt) fmtQ(s string) { 444 | s = f.truncateString(s) 445 | if f.sharp && strconv.CanBackquote(s) { 446 | f.padString("`" + s + "`") 447 | return 448 | } 449 | buf := f.intbuf[:0] 450 | if f.plus { 451 | f.pad(strconv.AppendQuoteToASCII(buf, s)) 452 | } else { 453 | f.pad(strconv.AppendQuote(buf, s)) 454 | } 455 | } 456 | 457 | // fmtC formats an integer as a Unicode character. 458 | // If the character is not valid Unicode, it will print '\ufffd'. 459 | func (f *fmt) fmtC(c uint64) { 460 | r := rune(c) 461 | if c > utf8.MaxRune { 462 | r = utf8.RuneError 463 | } 464 | buf := f.intbuf[:0] 465 | w := utf8.EncodeRune(buf[:utf8.UTFMax], r) 466 | f.pad(buf[:w]) 467 | } 468 | 469 | // fmtQc formats an integer as a single-quoted, escaped Go character constant. 470 | // If the character is not valid Unicode, it will print '\ufffd'. 471 | func (f *fmt) fmtQc(c uint64) { 472 | r := rune(c) 473 | if c > utf8.MaxRune { 474 | r = utf8.RuneError 475 | } 476 | buf := f.intbuf[:0] 477 | if f.plus { 478 | f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) 479 | } else { 480 | f.pad(strconv.AppendQuoteRune(buf, r)) 481 | } 482 | } 483 | 484 | // fmtFloat formats a float64. It assumes that verb is a valid format specifier 485 | // for strconv.AppendFloat and therefore fits into a byte. 486 | func (f *fmt) fmtFloat(v float64, size int, verb rune, prec int) { 487 | // Explicit precision in format specifier overrules default precision. 488 | if f.precPresent { 489 | prec = f.prec 490 | } 491 | // Format number, reserving space for leading + sign if needed. 492 | num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) 493 | if num[1] == '-' || num[1] == '+' { 494 | num = num[1:] 495 | } else { 496 | num[0] = '+' 497 | } 498 | // f.space means to add a leading space instead of a "+" sign unless 499 | // the sign is explicitly asked for by f.plus. 500 | if f.space && num[0] == '+' && !f.plus { 501 | num[0] = ' ' 502 | } 503 | // Special handling for infinities and NaN, 504 | // which don't look like a number so shouldn't be padded with zeros. 505 | if num[1] == 'I' || num[1] == 'N' { 506 | oldZero := f.zero 507 | f.zero = false 508 | // Remove sign before NaN if not asked for. 509 | if num[1] == 'N' && !f.space && !f.plus { 510 | num = num[1:] 511 | } 512 | f.pad(num) 513 | f.zero = oldZero 514 | return 515 | } 516 | // The sharp flag forces printing a decimal point for non-binary formats 517 | // and retains trailing zeros, which we may need to restore. 518 | if f.sharp && verb != 'b' { 519 | digits := 0 520 | switch verb { 521 | case 'v', 'g', 'G', 'x': 522 | digits = prec 523 | // If no precision is set explicitly use a precision of 6. 524 | if digits == -1 { 525 | digits = 6 526 | } 527 | } 528 | 529 | // Buffer pre-allocated with enough room for 530 | // exponent notations of the form "e+123" or "p-1023". 531 | var tailBuf [6]byte 532 | tail := tailBuf[:0] 533 | 534 | hasDecimalPoint := false 535 | sawNonzeroDigit := false 536 | // Starting from i = 1 to skip sign at num[0]. 537 | for i := 1; i < len(num); i++ { 538 | switch num[i] { 539 | case '.': 540 | hasDecimalPoint = true 541 | case 'p', 'P': 542 | tail = append(tail, num[i:]...) 543 | num = num[:i] 544 | case 'e', 'E': 545 | if verb != 'x' && verb != 'X' { 546 | tail = append(tail, num[i:]...) 547 | num = num[:i] 548 | break 549 | } 550 | fallthrough 551 | default: 552 | if num[i] != '0' { 553 | sawNonzeroDigit = true 554 | } 555 | // Count significant digits after the first non-zero digit. 556 | if sawNonzeroDigit { 557 | digits-- 558 | } 559 | } 560 | } 561 | if !hasDecimalPoint { 562 | // Leading digit 0 should contribute once to digits. 563 | if len(num) == 2 && num[1] == '0' { 564 | digits-- 565 | } 566 | num = append(num, '.') 567 | } 568 | for digits > 0 { 569 | num = append(num, '0') 570 | digits-- 571 | } 572 | num = append(num, tail...) 573 | } 574 | // We want a sign if asked for and if the sign is not positive. 575 | if f.plus || num[0] != '+' { 576 | // If we're zero padding to the left we want the sign before the leading zeros. 577 | // Achieve this by writing the sign out and then padding the unsigned number. 578 | if f.zero && f.widPresent && f.wid > len(num) { 579 | f.buf.writeByte(num[0]) 580 | f.writePadding(f.wid - len(num)) 581 | f.buf.write(num[1:]) 582 | return 583 | } 584 | f.pad(num) 585 | return 586 | } 587 | // No sign to show and the number is positive; just print the unsigned number. 588 | f.pad(num[1:]) 589 | } 590 | -------------------------------------------------------------------------------- /internal/rfmt/format.go.diff: -------------------------------------------------------------------------------- 1 | --- format.go.orig 2022-01-07 15:25:57.762807000 +0100 2 | +++ format.go 2022-01-07 15:26:16.154491000 +0100 3 | @@ -1,8 +1,11 @@ 4 | +// Code generated from the Go standard library. DO NOT EDIT 5 | +// GENERATED FILE DO NOT EDIT 6 | +// 7 | // Copyright 2009 The Go Authors. All rights reserved. 8 | // Use of this source code is governed by a BSD-style 9 | // license that can be found in the LICENSE file. 10 | 11 | -package fmt 12 | +package rfmt 13 | 14 | import ( 15 | "strconv" 16 | @@ -65,25 +68,17 @@ 17 | if n <= 0 { // No padding bytes needed. 18 | return 19 | } 20 | - buf := *f.buf 21 | - oldLen := len(buf) 22 | - newLen := oldLen + n 23 | // Make enough room for padding. 24 | - if newLen > cap(buf) { 25 | - buf = make(buffer, cap(buf)*2+n) 26 | - copy(buf, *f.buf) 27 | - } 28 | + f.buf.Grow(n) 29 | // Decide which byte the padding should be filled with. 30 | padByte := byte(' ') 31 | if f.zero { 32 | padByte = byte('0') 33 | } 34 | // Fill padding with padByte. 35 | - padding := buf[oldLen:newLen] 36 | - for i := range padding { 37 | - padding[i] = padByte 38 | + for i := 0; i < n; i++ { 39 | + f.buf.writeByte(padByte) 40 | } 41 | - *f.buf = buf[:newLen] 42 | } 43 | 44 | // pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). 45 | @@ -401,19 +396,20 @@ 46 | f.writePadding(f.wid - width) 47 | } 48 | // Write the encoding directly into the output buffer. 49 | - buf := *f.buf 50 | if f.sharp { 51 | // Add leading 0x or 0X. 52 | - buf = append(buf, '0', digits[16]) 53 | + f.buf.WriteByte('0') 54 | + f.buf.WriteByte(digits[16]) 55 | } 56 | var c byte 57 | for i := 0; i < length; i++ { 58 | if f.space && i > 0 { 59 | // Separate elements with a space. 60 | - buf = append(buf, ' ') 61 | + f.buf.WriteByte(' ') 62 | if f.sharp { 63 | // Add leading 0x or 0X for each element. 64 | - buf = append(buf, '0', digits[16]) 65 | + f.buf.WriteByte('0') 66 | + f.buf.WriteByte(digits[16]) 67 | } 68 | } 69 | if b != nil { 70 | @@ -422,9 +418,9 @@ 71 | c = s[i] // Take a byte from the input string. 72 | } 73 | // Encode each byte as two hexadecimal digits. 74 | - buf = append(buf, digits[c>>4], digits[c&0xF]) 75 | + f.buf.WriteByte(digits[c>>4]) 76 | + f.buf.WriteByte(digits[c&0xF]) 77 | } 78 | - *f.buf = buf 79 | // Handle padding to the right. 80 | if f.widPresent && f.wid > width && f.minus { 81 | f.writePadding(f.wid - width) 82 | -------------------------------------------------------------------------------- /internal/rfmt/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package rfmt 16 | 17 | import ( 18 | "reflect" 19 | 20 | i "github.com/cockroachdb/redact/interfaces" 21 | b "github.com/cockroachdb/redact/internal/buffer" 22 | "github.com/cockroachdb/redact/internal/escape" 23 | m "github.com/cockroachdb/redact/internal/markers" 24 | rwrap "github.com/cockroachdb/redact/internal/redact" 25 | ) 26 | 27 | type overrideMode int 28 | 29 | const ( 30 | noOverride overrideMode = iota 31 | // Consider unsafe values as safe. 32 | overrideSafe 33 | // Consider safe and pre-redactable values as unsafe. 34 | overrideUnsafe 35 | ) 36 | 37 | func (p *pp) startUnsafe() restorer { 38 | prevMode := p.buf.GetMode() 39 | if p.override != overrideSafe { 40 | p.buf.SetMode(b.UnsafeEscaped) 41 | } 42 | return restorer{p, prevMode, p.override} 43 | } 44 | 45 | func (p *pp) startPreRedactable() restorer { 46 | prevMode := p.buf.GetMode() 47 | if p.override != overrideUnsafe { 48 | p.buf.SetMode(b.PreRedactable) 49 | } 50 | return restorer{p, prevMode, p.override} 51 | } 52 | 53 | func (p *pp) startSafeOverride() restorer { 54 | prevMode := p.buf.GetMode() 55 | prevOverride := p.override 56 | if p.override == noOverride { 57 | p.buf.SetMode(b.SafeEscaped) 58 | p.override = overrideSafe 59 | } 60 | return restorer{p, prevMode, prevOverride} 61 | } 62 | 63 | func (p *pp) startUnsafeOverride() restorer { 64 | prevMode := p.buf.GetMode() 65 | prevOverride := p.override 66 | if p.override == noOverride { 67 | p.buf.SetMode(b.UnsafeEscaped) 68 | p.override = overrideUnsafe 69 | } 70 | return restorer{p, prevMode, prevOverride} 71 | } 72 | 73 | type restorer struct { 74 | p *pp 75 | prevMode b.OutputMode 76 | prevOverride overrideMode 77 | } 78 | 79 | func (r restorer) restore() { 80 | r.p.buf.SetMode(r.prevMode) 81 | r.p.override = r.prevOverride 82 | } 83 | 84 | func (p *pp) handleSpecialValues( 85 | value reflect.Value, t reflect.Type, verb rune, depth int, 86 | ) (handled bool) { 87 | switch t { 88 | case safeWrapperType: 89 | handled = true 90 | defer p.startSafeOverride().restore() 91 | p.printValue(value.Field(0), verb, depth+1) 92 | 93 | case unsafeWrapperType: 94 | handled = true 95 | defer p.startUnsafeOverride().restore() 96 | p.printValue(value.Field(0), verb, depth+1) 97 | 98 | case redactableStringType: 99 | handled = true 100 | defer p.startPreRedactable().restore() 101 | p.buf.WriteString(value.String()) 102 | 103 | case redactableBytesType: 104 | handled = true 105 | defer p.startPreRedactable().restore() 106 | p.buf.Write(value.Bytes()) 107 | } 108 | 109 | return handled 110 | } 111 | 112 | // Sprintfn produces a RedactableString using the provided 113 | // SafeFormat-alike function. 114 | func Sprintfn(printer func(w i.SafePrinter)) m.RedactableString { 115 | p := newPrinter() 116 | printer(p) 117 | s := p.buf.TakeRedactableString() 118 | p.free() 119 | return s 120 | } 121 | 122 | // HelperForErrorf is a helper to implement a redaction-aware 123 | // fmt.Errorf-compatible function in a different package. It formats 124 | // the string according to the given format and arguments in the same 125 | // way as Sprintf, but in addition to this if the format contains %w 126 | // and an error object in the proper argument position it also returns 127 | // that error object. 128 | // 129 | // Note: This function only works if an error redaction function 130 | // has been injected with RegisterRedactErrorFn(). 131 | func HelperForErrorf(format string, args ...interface{}) (m.RedactableString, error) { 132 | p := newPrinter() 133 | p.wrapErrs = true 134 | p.doPrintf(format, args) 135 | e := p.wrappedErr 136 | s := p.buf.TakeRedactableString() 137 | p.free() 138 | return s, e 139 | } 140 | 141 | // EscapeBytes escapes markers inside the given byte slice and encloses 142 | // the entire byte slice between redaction markers. 143 | // EscapeBytes escapes markers inside the given byte slice and encloses 144 | // the entire byte slice between redaction markers. 145 | func EscapeBytes(s []byte) m.RedactableBytes { 146 | buf := make([]byte, 0, len(s)+len(m.StartS)+len(m.EndS)) 147 | buf = append(buf, m.StartS...) 148 | start := len(buf) 149 | buf = append(buf, s...) 150 | buf = escape.InternalEscapeBytes(buf, start, true /* breakNewLine */, false /* strip */) 151 | buf = append(buf, m.EndS...) 152 | return m.RedactableBytes(buf) 153 | } 154 | 155 | var ( 156 | unsafeWrapperType = reflect.TypeOf(rwrap.UnsafeWrap{}) 157 | safeWrapperType = reflect.TypeOf(rwrap.SafeWrapper{}) 158 | redactableStringType = reflect.TypeOf(m.RedactableString("")) 159 | redactableBytesType = reflect.TypeOf(m.RedactableBytes{}) 160 | ) 161 | -------------------------------------------------------------------------------- /internal/rfmt/print.go: -------------------------------------------------------------------------------- 1 | // Code generated from print.go.orig. DO NOT EDIT 2 | // GENERATED FILE DO NOT EDIT 3 | // 4 | // Copyright 2009 The Go Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | package rfmt 9 | 10 | import ( 11 | // CUSTOM: needed to avoid a type mismatch on Formatter. 12 | origFmt "fmt" 13 | "io" 14 | "os" 15 | "reflect" 16 | "sync" 17 | "unicode/utf8" 18 | 19 | // CUSTOM: our own imports. 20 | i "github.com/cockroachdb/redact/interfaces" 21 | b "github.com/cockroachdb/redact/internal/buffer" 22 | m "github.com/cockroachdb/redact/internal/markers" 23 | w "github.com/cockroachdb/redact/internal/redact" 24 | "github.com/cockroachdb/redact/internal/rfmt/fmtsort" 25 | ) 26 | 27 | // Strings for use with buffer.WriteString. 28 | // This is less overhead than using buffer.Write with byte arrays. 29 | const ( 30 | commaSpaceString = ", " 31 | nilAngleString = "" 32 | nilParenString = "(nil)" 33 | nilString = "nil" 34 | mapString = "map[" 35 | percentBangString = "%!" 36 | missingString = "(MISSING)" 37 | badIndexString = "(BADINDEX)" 38 | panicString = "(PANIC=" 39 | extraString = "%!(EXTRA " 40 | badWidthString = "%!(BADWIDTH)" 41 | badPrecString = "%!(BADPREC)" 42 | noVerbString = "%!(NOVERB)" 43 | invReflectString = "" 44 | ) 45 | 46 | // State represents the printer state passed to custom formatters. 47 | // It provides access to the io.Writer interface plus information about 48 | // the flags and options for the operand's format specifier. 49 | type State interface { 50 | // Write is the function to call to emit formatted output to be printed. 51 | Write(b []byte) (n int, err error) 52 | // Width returns the value of the width option and whether it has been set. 53 | Width() (wid int, ok bool) 54 | // Precision returns the value of the precision option and whether it has been set. 55 | Precision() (prec int, ok bool) 56 | 57 | // Flag reports whether the flag c, a character, has been set. 58 | Flag(c int) bool 59 | } 60 | 61 | // Formatter is implemented by any value that has a Format method. 62 | // The implementation controls how State and rune are interpreted, 63 | // and may call Sprint(f) or Fprint(f) etc. to generate its output. 64 | type Formatter interface { 65 | // CUSTOM: refer to the original type, not the one defined here. 66 | Format(f origFmt.State, verb rune) 67 | } 68 | 69 | // Stringer is implemented by any value that has a String method, 70 | // which defines the ``native'' format for that value. 71 | // The String method is used to print values passed as an operand 72 | // to any format that accepts a string or to an unformatted printer 73 | // such as Print. 74 | type Stringer interface { 75 | String() string 76 | } 77 | 78 | // GoStringer is implemented by any value that has a GoString method, 79 | // which defines the Go syntax for that value. 80 | // The GoString method is used to print values passed as an operand 81 | // to a %#v format. 82 | type GoStringer interface { 83 | GoString() string 84 | } 85 | 86 | // CUSTOM: smarter buffer. 87 | type buffer struct { 88 | b.Buffer 89 | } 90 | 91 | func (b *buffer) write(p []byte) { 92 | b.Write(p) 93 | } 94 | 95 | func (b *buffer) writeString(s string) { 96 | b.WriteString(s) 97 | } 98 | 99 | func (b *buffer) writeByte(c byte) { 100 | b.WriteByte(c) 101 | } 102 | 103 | func (bp *buffer) writeRune(r rune) { 104 | bp.WriteRune(r) 105 | } 106 | 107 | // pp is used to store a printer's state and is reused with sync.Pool to avoid allocations. 108 | type pp struct { 109 | buf buffer 110 | 111 | // CUSTOM: override safety in recursive calls. 112 | override overrideMode 113 | 114 | // arg holds the current item, as an interface{}. 115 | arg interface{} 116 | 117 | // value is used instead of arg for reflect values. 118 | value reflect.Value 119 | 120 | // fmt is used to format basic items such as integers or strings. 121 | fmt fmt 122 | 123 | // reordered records whether the format string used argument reordering. 124 | reordered bool 125 | // goodArgNum records whether the most recent reordering directive was valid. 126 | goodArgNum bool 127 | // panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion. 128 | panicking bool 129 | // erroring is set when printing an error string to guard against calling handleMethods. 130 | erroring bool 131 | // wrapErrs is set when the format string may contain a %w verb. 132 | wrapErrs bool 133 | // wrappedErr records the target of the %w verb. 134 | wrappedErr error 135 | } 136 | 137 | var ppFree = sync.Pool{ 138 | New: func() interface{} { return new(pp) }, 139 | } 140 | 141 | // newPrinter allocates a new pp struct or grabs a cached one. 142 | func newPrinter() *pp { 143 | p := ppFree.Get().(*pp) 144 | p.panicking = false 145 | p.erroring = false 146 | p.wrapErrs = false 147 | p.fmt.init(&p.buf) 148 | return p 149 | } 150 | 151 | // free saves used pp structs in ppFree; avoids an allocation per invocation. 152 | func (p *pp) free() { 153 | // Proper usage of a sync.Pool requires each entry to have approximately 154 | // the same memory cost. To obtain this property when the stored type 155 | // contains a variably-sized buffer, we add a hard limit on the maximum buffer 156 | // to place back in the pool. 157 | // 158 | // See https://golang.org/issue/23199 159 | if p.buf.Cap() > 64<<10 { 160 | return 161 | } 162 | 163 | p.buf.Reset() 164 | p.arg = nil 165 | p.value = reflect.Value{} 166 | p.wrappedErr = nil 167 | ppFree.Put(p) 168 | } 169 | 170 | func (p *pp) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent } 171 | 172 | func (p *pp) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent } 173 | 174 | func (p *pp) Flag(b int) bool { 175 | switch b { 176 | case '-': 177 | return p.fmt.minus 178 | case '+': 179 | return p.fmt.plus || p.fmt.plusV 180 | case '#': 181 | return p.fmt.sharp || p.fmt.sharpV 182 | case ' ': 183 | return p.fmt.space 184 | case '0': 185 | return p.fmt.zero 186 | } 187 | return false 188 | } 189 | 190 | // Implement Write so we can call Fprintf on a pp (through State), for 191 | // recursive use in custom verbs. 192 | func (p *pp) Write(bs []byte) (ret int, err error) { 193 | defer p.startUnsafe().restore() 194 | p.buf.write(bs) 195 | return len(bs), nil 196 | } 197 | 198 | // Implement WriteString so that we can call io.WriteString 199 | // on a pp (through state), for efficiency. 200 | func (p *pp) WriteString(s string) (ret int, err error) { 201 | defer p.startUnsafe().restore() 202 | p.buf.writeString(s) 203 | return len(s), nil 204 | } 205 | 206 | // These routines end in 'f' and take a format string. 207 | 208 | // Fprintf formats according to a format specifier and writes to w. 209 | // It returns the number of bytes written and any write error encountered. 210 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 211 | p := newPrinter() 212 | p.doPrintf(format, a) 213 | n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 214 | p.free() 215 | return 216 | } 217 | 218 | // Printf formats according to a format specifier and writes to standard output. 219 | // It returns the number of bytes written and any write error encountered. 220 | func Printf(format string, a ...interface{}) (n int, err error) { 221 | return Fprintf(os.Stdout, format, a...) 222 | } 223 | 224 | // Sprintf formats according to a format specifier and returns the resulting string. 225 | func Sprintf(format string, a ...interface{}) m.RedactableString { 226 | p := newPrinter() 227 | p.doPrintf(format, a) 228 | s := p.buf.TakeRedactableString() 229 | p.free() 230 | return s 231 | } 232 | 233 | // These routines do not take a format string 234 | 235 | // Fprint formats using the default formats for its operands and writes to w. 236 | // Spaces are added between operands when neither is a string. 237 | // It returns the number of bytes written and any write error encountered. 238 | func Fprint(w io.Writer, a ...interface{}) (n int, err error) { 239 | p := newPrinter() 240 | p.doPrint(a) 241 | n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 242 | p.free() 243 | return 244 | } 245 | 246 | // Print formats using the default formats for its operands and writes to standard output. 247 | // Spaces are added between operands when neither is a string. 248 | // It returns the number of bytes written and any write error encountered. 249 | func Print(a ...interface{}) (n int, err error) { 250 | return Fprint(os.Stdout, a...) 251 | } 252 | 253 | // Sprint formats using the default formats for its operands and returns the resulting string. 254 | // Spaces are added between operands when neither is a string. 255 | func Sprint(a ...interface{}) m.RedactableString { 256 | p := newPrinter() 257 | p.doPrint(a) 258 | s := p.buf.TakeRedactableString() 259 | p.free() 260 | return s 261 | } 262 | 263 | // These routines end in 'ln', do not take a format string, 264 | // always add spaces between operands, and add a newline 265 | // after the last operand. 266 | 267 | // Fprintln formats using the default formats for its operands and writes to w. 268 | // Spaces are always added between operands and a newline is appended. 269 | // It returns the number of bytes written and any write error encountered. 270 | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { 271 | p := newPrinter() 272 | p.doPrintln(a) 273 | n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 274 | p.free() 275 | return 276 | } 277 | 278 | // Println formats using the default formats for its operands and writes to standard output. 279 | // Spaces are always added between operands and a newline is appended. 280 | // It returns the number of bytes written and any write error encountered. 281 | func Println(a ...interface{}) (n int, err error) { 282 | return Fprintln(os.Stdout, a...) 283 | } 284 | 285 | // Sprintln formats using the default formats for its operands and returns the resulting string. 286 | // Spaces are always added between operands and a newline is appended. 287 | func Sprintln(a ...interface{}) m.RedactableString { 288 | p := newPrinter() 289 | p.doPrintln(a) 290 | s := p.buf.TakeRedactableString() 291 | p.free() 292 | return s 293 | } 294 | 295 | // getField gets the i'th field of the struct value. 296 | // If the field is itself is an interface, return a value for 297 | // the thing inside the interface, not the interface itself. 298 | func getField(v reflect.Value, i int) reflect.Value { 299 | val := v.Field(i) 300 | if val.Kind() == reflect.Interface && !val.IsNil() { 301 | val = val.Elem() 302 | } 303 | return val 304 | } 305 | 306 | // tooLarge reports whether the magnitude of the integer is 307 | // too large to be used as a formatting width or precision. 308 | func tooLarge(x int) bool { 309 | const max int = 1e6 310 | return x > max || x < -max 311 | } 312 | 313 | // parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. 314 | func parsenum(s string, start, end int) (num int, isnum bool, newi int) { 315 | if start >= end { 316 | return 0, false, end 317 | } 318 | for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { 319 | if tooLarge(num) { 320 | return 0, false, end // Overflow; crazy long number most likely. 321 | } 322 | num = num*10 + int(s[newi]-'0') 323 | isnum = true 324 | } 325 | return 326 | } 327 | 328 | func (p *pp) unknownType(v reflect.Value) { 329 | if !v.IsValid() { 330 | p.buf.writeString(nilAngleString) 331 | return 332 | } 333 | p.buf.writeByte('?') 334 | p.buf.writeString(v.Type().String()) 335 | p.buf.writeByte('?') 336 | } 337 | 338 | func (p *pp) badVerb(verb rune) { 339 | p.erroring = true 340 | p.buf.writeString(percentBangString) 341 | p.buf.writeRune(verb) 342 | p.buf.writeByte('(') 343 | switch { 344 | case p.arg != nil: 345 | p.buf.writeString(reflect.TypeOf(p.arg).String()) 346 | p.buf.writeByte('=') 347 | p.printArg(p.arg, 'v') 348 | case p.value.IsValid(): 349 | p.buf.writeString(p.value.Type().String()) 350 | p.buf.writeByte('=') 351 | p.printValue(p.value, 'v', 0) 352 | default: 353 | p.buf.writeString(nilAngleString) 354 | } 355 | p.buf.writeByte(')') 356 | p.erroring = false 357 | } 358 | 359 | func (p *pp) fmtBool(v bool, verb rune) { 360 | switch verb { 361 | case 't', 'v': 362 | defer p.startUnsafe().restore() 363 | p.fmt.fmtBoolean(v) 364 | default: 365 | p.badVerb(verb) 366 | } 367 | } 368 | 369 | // fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or 370 | // not, as requested, by temporarily setting the sharp flag. 371 | func (p *pp) fmt0x64(v uint64, leading0x bool) { 372 | sharp := p.fmt.sharp 373 | p.fmt.sharp = leading0x 374 | defer p.startUnsafe().restore() 375 | p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits) 376 | p.fmt.sharp = sharp 377 | } 378 | 379 | // fmtInteger formats a signed or unsigned integer. 380 | func (p *pp) fmtInteger(v uint64, isSigned bool, verb rune) { 381 | switch verb { 382 | case 'v': 383 | if p.fmt.sharpV && !isSigned { 384 | p.fmt0x64(v, true) 385 | } else { 386 | defer p.startUnsafe().restore() 387 | p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) 388 | } 389 | case 'd': 390 | defer p.startUnsafe().restore() 391 | p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) 392 | case 'b': 393 | defer p.startUnsafe().restore() 394 | p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits) 395 | case 'o', 'O': 396 | defer p.startUnsafe().restore() 397 | p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits) 398 | case 'x': 399 | defer p.startUnsafe().restore() 400 | p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits) 401 | case 'X': 402 | defer p.startUnsafe().restore() 403 | p.fmt.fmtInteger(v, 16, isSigned, verb, udigits) 404 | case 'c': 405 | defer p.startUnsafe().restore() 406 | p.fmt.fmtC(v) 407 | case 'q': 408 | defer p.startUnsafe().restore() 409 | p.fmt.fmtQc(v) 410 | case 'U': 411 | defer p.startUnsafe().restore() 412 | p.fmt.fmtUnicode(v) 413 | default: 414 | p.badVerb(verb) 415 | } 416 | } 417 | 418 | // fmtFloat formats a float. The default precision for each verb 419 | // is specified as last argument in the call to fmt_float. 420 | func (p *pp) fmtFloat(v float64, size int, verb rune) { 421 | switch verb { 422 | case 'v': 423 | defer p.startUnsafe().restore() 424 | p.fmt.fmtFloat(v, size, 'g', -1) 425 | case 'b', 'g', 'G', 'x', 'X': 426 | defer p.startUnsafe().restore() 427 | p.fmt.fmtFloat(v, size, verb, -1) 428 | case 'f', 'e', 'E': 429 | defer p.startUnsafe().restore() 430 | p.fmt.fmtFloat(v, size, verb, 6) 431 | case 'F': 432 | defer p.startUnsafe().restore() 433 | p.fmt.fmtFloat(v, size, 'f', 6) 434 | default: 435 | p.badVerb(verb) 436 | } 437 | } 438 | 439 | // fmtComplex formats a complex number v with 440 | // r = real(v) and j = imag(v) as (r+ji) using 441 | // fmtFloat for r and j formatting. 442 | func (p *pp) fmtComplex(v complex128, size int, verb rune) { 443 | // Make sure any unsupported verbs are found before the 444 | // calls to fmtFloat to not generate an incorrect error string. 445 | switch verb { 446 | case 'v', 'b', 'g', 'G', 'x', 'X', 'f', 'F', 'e', 'E': 447 | oldPlus := p.fmt.plus 448 | p.buf.writeByte('(') 449 | p.fmtFloat(real(v), size/2, verb) 450 | // Imaginary part always has a sign. 451 | p.fmt.plus = true 452 | p.fmtFloat(imag(v), size/2, verb) 453 | p.buf.writeString("i)") 454 | p.fmt.plus = oldPlus 455 | default: 456 | p.badVerb(verb) 457 | } 458 | } 459 | 460 | func (p *pp) fmtString(v string, verb rune) { 461 | switch verb { 462 | case 'v': 463 | defer p.startUnsafe().restore() 464 | if p.fmt.sharpV { 465 | p.fmt.fmtQ(v) 466 | } else { 467 | p.fmt.fmtS(v) 468 | } 469 | case 's': 470 | defer p.startUnsafe().restore() 471 | p.fmt.fmtS(v) 472 | case 'x': 473 | defer p.startUnsafe().restore() 474 | p.fmt.fmtSx(v, ldigits) 475 | case 'X': 476 | defer p.startUnsafe().restore() 477 | p.fmt.fmtSx(v, udigits) 478 | case 'q': 479 | defer p.startUnsafe().restore() 480 | p.fmt.fmtQ(v) 481 | default: 482 | p.badVerb(verb) 483 | } 484 | } 485 | 486 | func (p *pp) fmtBytes(v []byte, verb rune, typeString string) { 487 | switch verb { 488 | case 'v', 'd': 489 | if p.fmt.sharpV { 490 | p.buf.writeString(typeString) 491 | if v == nil { 492 | p.buf.writeString(nilParenString) 493 | return 494 | } 495 | p.buf.writeByte('{') 496 | for i, c := range v { 497 | if i > 0 { 498 | p.buf.writeString(commaSpaceString) 499 | } 500 | p.fmt0x64(uint64(c), true) 501 | } 502 | p.buf.writeByte('}') 503 | } else { 504 | p.buf.writeByte('[') 505 | for i, c := range v { 506 | if i > 0 { 507 | p.buf.writeByte(' ') 508 | } 509 | func(p *pp, c byte, verb rune) { 510 | defer p.startUnsafe().restore() 511 | p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) 512 | }(p, c, verb) 513 | } 514 | p.buf.writeByte(']') 515 | } 516 | case 's': 517 | defer p.startUnsafe().restore() 518 | p.fmt.fmtBs(v) 519 | case 'x': 520 | defer p.startUnsafe().restore() 521 | p.fmt.fmtBx(v, ldigits) 522 | case 'X': 523 | defer p.startUnsafe().restore() 524 | p.fmt.fmtBx(v, udigits) 525 | case 'q': 526 | defer p.startUnsafe().restore() 527 | p.fmt.fmtQ(string(v)) 528 | default: 529 | p.printValue(reflect.ValueOf(v), verb, 0) 530 | } 531 | } 532 | 533 | func (p *pp) fmtPointer(value reflect.Value, verb rune) { 534 | var u uintptr 535 | switch value.Kind() { 536 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 537 | u = value.Pointer() 538 | default: 539 | p.badVerb(verb) 540 | return 541 | } 542 | 543 | switch verb { 544 | case 'v': 545 | if p.fmt.sharpV { 546 | p.buf.writeByte('(') 547 | p.buf.writeString(value.Type().String()) 548 | p.buf.writeString(")(") 549 | if u == 0 { 550 | p.buf.writeString(nilString) 551 | } else { 552 | p.fmt0x64(uint64(u), true) 553 | } 554 | p.buf.writeByte(')') 555 | } else { 556 | if u == 0 { 557 | defer p.startUnsafe().restore() 558 | p.fmt.padString(nilAngleString) 559 | } else { 560 | p.fmt0x64(uint64(u), !p.fmt.sharp) 561 | } 562 | } 563 | case 'p': 564 | p.fmt0x64(uint64(u), !p.fmt.sharp) 565 | case 'b', 'o', 'd', 'x', 'X': 566 | p.fmtInteger(uint64(u), unsigned, verb) 567 | default: 568 | p.badVerb(verb) 569 | } 570 | } 571 | 572 | func (p *pp) catchPanic(arg interface{}, verb rune, method string) { 573 | if err := recover(); err != nil { 574 | // If it's a nil pointer, just say "". The likeliest causes are a 575 | // Stringer that fails to guard against nil or a nil pointer for a 576 | // value receiver, and in either case, "" is a nice result. 577 | if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() { 578 | p.buf.writeString(nilAngleString) 579 | return 580 | } 581 | // Otherwise print a concise panic message. Most of the time the panic 582 | // value will print itself nicely. 583 | if p.panicking { 584 | // Nested panics; the recursion in printArg cannot succeed. 585 | panic(err) 586 | } 587 | 588 | oldFlags := p.fmt.fmtFlags 589 | // For this output we want default behavior. 590 | p.fmt.clearflags() 591 | 592 | p.buf.writeString(percentBangString) 593 | p.buf.writeRune(verb) 594 | p.buf.writeString(panicString) 595 | p.buf.writeString(method) 596 | p.buf.writeString(" method: ") 597 | p.panicking = true 598 | p.printArg(err, 'v') 599 | p.panicking = false 600 | p.buf.writeByte(')') 601 | 602 | p.fmt.fmtFlags = oldFlags 603 | } 604 | } 605 | 606 | func (p *pp) handleMethods(verb rune) (handled bool) { 607 | if p.erroring { 608 | return 609 | } 610 | if verb == 'w' { 611 | // It is invalid to use %w other than with Errorf, more than once, 612 | // or with a non-error arg. 613 | err, ok := p.arg.(error) 614 | if !ok || !p.wrapErrs || p.wrappedErr != nil { 615 | p.wrappedErr = nil 616 | p.wrapErrs = false 617 | p.badVerb(verb) 618 | return true 619 | } 620 | p.wrappedErr = err 621 | // If the arg is a Formatter, pass 'v' as the verb to it. 622 | verb = 'v' 623 | } 624 | 625 | if p.override != overrideUnsafe { 626 | switch v := p.arg.(type) { 627 | case i.SafeFormatter: 628 | handled = true 629 | defer p.catchPanic(p.arg, verb, "SafeFormat") 630 | v.SafeFormat(p, verb) 631 | return 632 | 633 | case i.SafeMessager: 634 | handled = true 635 | defer p.catchPanic(p.arg, verb, "SafeMessager") 636 | defer p.startSafeOverride().restore() 637 | p.fmtString(v.SafeMessage(), verb) 638 | return 639 | 640 | case error: 641 | if redactErrorFn != nil { 642 | handled = true 643 | defer p.catchPanic(p.arg, verb, "SafeFormatter") 644 | redactErrorFn(v, p, verb) 645 | return 646 | } 647 | } 648 | } 649 | 650 | // Is it a Formatter? 651 | if formatter, ok := p.arg.(Formatter); ok { 652 | handled = true 653 | defer p.catchPanic(p.arg, verb, "Format") 654 | // The mode is implicit: the Format() call will 655 | // use p.Write() which already sets the mode. 656 | formatter.Format(p, verb) 657 | return 658 | } 659 | 660 | // If we're doing Go syntax and the argument knows how to supply it, take care of it now. 661 | if p.fmt.sharpV { 662 | if stringer, ok := p.arg.(GoStringer); ok { 663 | handled = true 664 | defer p.catchPanic(p.arg, verb, "GoString") 665 | // Print the result of GoString unadorned. 666 | defer p.startUnsafe().restore() 667 | p.fmt.fmtS(stringer.GoString()) 668 | return 669 | } 670 | } else { 671 | // If a string is acceptable according to the format, see if 672 | // the value satisfies one of the string-valued interfaces. 673 | // Println etc. set verb to %v, which is "stringable". 674 | switch verb { 675 | case 'v', 's', 'x', 'X', 'q': 676 | // Is it an error or Stringer? 677 | // The duplication in the bodies is necessary: 678 | // setting handled and deferring catchPanic 679 | // must happen before calling the method. 680 | switch v := p.arg.(type) { 681 | case error: 682 | handled = true 683 | defer p.catchPanic(p.arg, verb, "Error") 684 | p.fmtString(v.Error(), verb) 685 | return 686 | 687 | case Stringer: 688 | handled = true 689 | defer p.catchPanic(p.arg, verb, "String") 690 | p.fmtString(v.String(), verb) 691 | return 692 | } 693 | } 694 | } 695 | return false 696 | } 697 | 698 | func (p *pp) printArg(arg interface{}, verb rune) { 699 | t := reflect.TypeOf(arg) 700 | if safeTypeRegistry[t] { 701 | defer p.startSafeOverride().restore() 702 | } else if t == safeWrapperType { 703 | defer p.startSafeOverride().restore() 704 | arg = arg.(w.SafeWrapper).GetValue() 705 | } else if t == unsafeWrapperType { 706 | defer p.startUnsafeOverride().restore() 707 | arg = arg.(w.UnsafeWrap).GetValue() 708 | } 709 | 710 | if _, ok := arg.(i.SafeValue); ok { 711 | defer p.startSafeOverride().restore() 712 | } 713 | 714 | p.arg = arg 715 | p.value = reflect.Value{} 716 | 717 | if arg == nil { 718 | switch verb { 719 | case 'T', 'v': 720 | p.fmt.padString(nilAngleString) 721 | default: 722 | p.badVerb(verb) 723 | } 724 | return 725 | } 726 | 727 | // Special processing considerations. 728 | // %T (the value's type) and %p (its address) are special; we always do them first. 729 | switch verb { 730 | case 'T': 731 | p.fmt.fmtS(reflect.TypeOf(arg).String()) 732 | return 733 | case 'p': 734 | p.fmtPointer(reflect.ValueOf(arg), 'p') 735 | return 736 | } 737 | 738 | // Some types can be done without reflection. 739 | switch f := arg.(type) { 740 | case bool: 741 | p.fmtBool(f, verb) 742 | case float32: 743 | p.fmtFloat(float64(f), 32, verb) 744 | case float64: 745 | p.fmtFloat(f, 64, verb) 746 | case complex64: 747 | p.fmtComplex(complex128(f), 64, verb) 748 | case complex128: 749 | p.fmtComplex(f, 128, verb) 750 | case int: 751 | p.fmtInteger(uint64(f), signed, verb) 752 | case int8: 753 | p.fmtInteger(uint64(f), signed, verb) 754 | case int16: 755 | p.fmtInteger(uint64(f), signed, verb) 756 | case int32: 757 | p.fmtInteger(uint64(f), signed, verb) 758 | case int64: 759 | p.fmtInteger(uint64(f), signed, verb) 760 | case uint: 761 | p.fmtInteger(uint64(f), unsigned, verb) 762 | case uint8: 763 | p.fmtInteger(uint64(f), unsigned, verb) 764 | case uint16: 765 | p.fmtInteger(uint64(f), unsigned, verb) 766 | case uint32: 767 | p.fmtInteger(uint64(f), unsigned, verb) 768 | case uint64: 769 | p.fmtInteger(f, unsigned, verb) 770 | case uintptr: 771 | p.fmtInteger(uint64(f), unsigned, verb) 772 | case string: 773 | p.fmtString(f, verb) 774 | case []byte: 775 | p.fmtBytes(f, verb, "[]byte") 776 | case reflect.Value: 777 | // Handle extractable values with special methods 778 | // since printValue does not handle them at depth 0. 779 | if f.IsValid() { 780 | t := f.Type() 781 | if p.handleSpecialValues(f, t, verb, 0) { 782 | return 783 | } 784 | 785 | if safeTypeRegistry[t] { 786 | defer p.startSafeOverride().restore() 787 | } 788 | 789 | if f.CanInterface() { 790 | p.arg = f.Interface() 791 | if _, ok := p.arg.(i.SafeValue); ok { 792 | defer p.startSafeOverride().restore() 793 | } 794 | if p.handleMethods(verb) { 795 | return 796 | } 797 | } 798 | } 799 | p.printValue(f, verb, 0) 800 | case m.RedactableString: 801 | defer p.startPreRedactable().restore() 802 | p.buf.WriteString(string(f)) 803 | return 804 | case m.RedactableBytes: 805 | defer p.startPreRedactable().restore() 806 | p.buf.Write([]byte(f)) 807 | return 808 | default: 809 | // If the type is not simple, it might have methods. 810 | if !p.handleMethods(verb) { 811 | // Need to use reflection, since the type had no 812 | // interface methods that could be used for formatting. 813 | p.printValue(reflect.ValueOf(f), verb, 0) 814 | } 815 | } 816 | } 817 | 818 | // printValue is similar to printArg but starts with a reflect value, not an interface{} value. 819 | // It does not handle 'p' and 'T' verbs because these should have been already handled by printArg. 820 | func (p *pp) printValue(value reflect.Value, verb rune, depth int) { 821 | // Handle values with special methods if not already handled by printArg (depth == 0). 822 | if depth > 0 && value.IsValid() { 823 | t := value.Type() 824 | if p.handleSpecialValues(value, t, verb, depth) { 825 | return 826 | } 827 | 828 | if safeTypeRegistry[t] { 829 | defer p.startSafeOverride().restore() 830 | } 831 | 832 | if value.CanInterface() { 833 | p.arg = value.Interface() 834 | if _, ok := p.arg.(i.SafeValue); ok { 835 | defer p.startSafeOverride().restore() 836 | } 837 | if p.handleMethods(verb) { 838 | return 839 | } 840 | } 841 | } 842 | p.arg = nil 843 | p.value = value 844 | 845 | switch f := value; value.Kind() { 846 | case reflect.Invalid: 847 | if depth == 0 { 848 | p.buf.writeString(invReflectString) 849 | } else { 850 | switch verb { 851 | case 'v': 852 | p.buf.writeString(nilAngleString) 853 | default: 854 | p.badVerb(verb) 855 | } 856 | } 857 | case reflect.Bool: 858 | p.fmtBool(f.Bool(), verb) 859 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 860 | p.fmtInteger(uint64(f.Int()), signed, verb) 861 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 862 | p.fmtInteger(f.Uint(), unsigned, verb) 863 | case reflect.Float32: 864 | p.fmtFloat(f.Float(), 32, verb) 865 | case reflect.Float64: 866 | p.fmtFloat(f.Float(), 64, verb) 867 | case reflect.Complex64: 868 | p.fmtComplex(f.Complex(), 64, verb) 869 | case reflect.Complex128: 870 | p.fmtComplex(f.Complex(), 128, verb) 871 | case reflect.String: 872 | p.fmtString(f.String(), verb) 873 | case reflect.Map: 874 | if p.fmt.sharpV { 875 | p.buf.writeString(f.Type().String()) 876 | if f.IsNil() { 877 | p.buf.writeString(nilParenString) 878 | return 879 | } 880 | p.buf.writeByte('{') 881 | } else { 882 | p.buf.writeString(mapString) 883 | } 884 | sorted := fmtsort.Sort(f) 885 | for i, key := range sorted.Key { 886 | if i > 0 { 887 | if p.fmt.sharpV { 888 | p.buf.writeString(commaSpaceString) 889 | } else { 890 | p.buf.writeByte(' ') 891 | } 892 | } 893 | p.printValue(key, verb, depth+1) 894 | p.buf.writeByte(':') 895 | p.printValue(sorted.Value[i], verb, depth+1) 896 | } 897 | if p.fmt.sharpV { 898 | p.buf.writeByte('}') 899 | } else { 900 | p.buf.writeByte(']') 901 | } 902 | case reflect.Struct: 903 | if p.fmt.sharpV { 904 | p.buf.writeString(f.Type().String()) 905 | } 906 | p.buf.writeByte('{') 907 | for i := 0; i < f.NumField(); i++ { 908 | if i > 0 { 909 | if p.fmt.sharpV { 910 | p.buf.writeString(commaSpaceString) 911 | } else { 912 | p.buf.writeByte(' ') 913 | } 914 | } 915 | if p.fmt.plusV || p.fmt.sharpV { 916 | if name := f.Type().Field(i).Name; name != "" { 917 | p.buf.writeString(name) 918 | p.buf.writeByte(':') 919 | } 920 | } 921 | p.printValue(getField(f, i), verb, depth+1) 922 | } 923 | p.buf.writeByte('}') 924 | case reflect.Interface: 925 | value := f.Elem() 926 | if !value.IsValid() { 927 | if p.fmt.sharpV { 928 | p.buf.writeString(f.Type().String()) 929 | p.buf.writeString(nilParenString) 930 | } else { 931 | p.buf.writeString(nilAngleString) 932 | } 933 | } else { 934 | p.printValue(value, verb, depth+1) 935 | } 936 | case reflect.Array, reflect.Slice: 937 | switch verb { 938 | case 's', 'q', 'x', 'X': 939 | // Handle byte and uint8 slices and arrays special for the above verbs. 940 | t := f.Type() 941 | if t.Elem().Kind() == reflect.Uint8 { 942 | var bytes []byte 943 | if f.Kind() == reflect.Slice { 944 | bytes = f.Bytes() 945 | } else if f.CanAddr() { 946 | bytes = f.Slice(0, f.Len()).Bytes() 947 | } else { 948 | // We have an array, but we cannot Slice() a non-addressable array, 949 | // so we build a slice by hand. This is a rare case but it would be nice 950 | // if reflection could help a little more. 951 | bytes = make([]byte, f.Len()) 952 | for i := range bytes { 953 | bytes[i] = byte(f.Index(i).Uint()) 954 | } 955 | } 956 | p.fmtBytes(bytes, verb, t.String()) 957 | return 958 | } 959 | } 960 | if p.fmt.sharpV { 961 | p.buf.writeString(f.Type().String()) 962 | if f.Kind() == reflect.Slice && f.IsNil() { 963 | p.buf.writeString(nilParenString) 964 | return 965 | } 966 | p.buf.writeByte('{') 967 | for i := 0; i < f.Len(); i++ { 968 | if i > 0 { 969 | p.buf.writeString(commaSpaceString) 970 | } 971 | p.printValue(f.Index(i), verb, depth+1) 972 | } 973 | p.buf.writeByte('}') 974 | } else { 975 | p.buf.writeByte('[') 976 | for i := 0; i < f.Len(); i++ { 977 | if i > 0 { 978 | p.buf.writeByte(' ') 979 | } 980 | p.printValue(f.Index(i), verb, depth+1) 981 | } 982 | p.buf.writeByte(']') 983 | } 984 | case reflect.Ptr: 985 | // pointer to array or slice or struct? ok at top level 986 | // but not embedded (avoid loops) 987 | if depth == 0 && f.Pointer() != 0 { 988 | switch a := f.Elem(); a.Kind() { 989 | case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: 990 | p.buf.writeByte('&') 991 | p.printValue(a, verb, depth+1) 992 | return 993 | } 994 | } 995 | fallthrough 996 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 997 | p.fmtPointer(f, verb) 998 | default: 999 | p.unknownType(f) 1000 | } 1001 | } 1002 | 1003 | // intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type. 1004 | func intFromArg(a []interface{}, argNum int) (num int, isInt bool, newArgNum int) { 1005 | newArgNum = argNum 1006 | if argNum < len(a) { 1007 | num, isInt = a[argNum].(int) // Almost always OK. 1008 | if !isInt { 1009 | // Work harder. 1010 | switch v := reflect.ValueOf(a[argNum]); v.Kind() { 1011 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 1012 | n := v.Int() 1013 | if int64(int(n)) == n { 1014 | num = int(n) 1015 | isInt = true 1016 | } 1017 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 1018 | n := v.Uint() 1019 | if int64(n) >= 0 && uint64(int(n)) == n { 1020 | num = int(n) 1021 | isInt = true 1022 | } 1023 | default: 1024 | // Already 0, false. 1025 | } 1026 | } 1027 | newArgNum = argNum + 1 1028 | if tooLarge(num) { 1029 | num = 0 1030 | isInt = false 1031 | } 1032 | } 1033 | return 1034 | } 1035 | 1036 | // parseArgNumber returns the value of the bracketed number, minus 1 1037 | // (explicit argument numbers are one-indexed but we want zero-indexed). 1038 | // The opening bracket is known to be present at format[0]. 1039 | // The returned values are the index, the number of bytes to consume 1040 | // up to the closing paren, if present, and whether the number parsed 1041 | // ok. The bytes to consume will be 1 if no closing paren is present. 1042 | func parseArgNumber(format string) (index int, wid int, ok bool) { 1043 | // There must be at least 3 bytes: [n]. 1044 | if len(format) < 3 { 1045 | return 0, 1, false 1046 | } 1047 | 1048 | // Find closing bracket. 1049 | for i := 1; i < len(format); i++ { 1050 | if format[i] == ']' { 1051 | width, ok, newi := parsenum(format, 1, i) 1052 | if !ok || newi != i { 1053 | return 0, i + 1, false 1054 | } 1055 | return width - 1, i + 1, true // arg numbers are one-indexed and skip paren. 1056 | } 1057 | } 1058 | return 0, 1, false 1059 | } 1060 | 1061 | // argNumber returns the next argument to evaluate, which is either the value of the passed-in 1062 | // argNum or the value of the bracketed integer that begins format[i:]. It also returns 1063 | // the new value of i, that is, the index of the next byte of the format to process. 1064 | func (p *pp) argNumber( 1065 | argNum int, format string, i int, numArgs int, 1066 | ) (newArgNum, newi int, found bool) { 1067 | if len(format) <= i || format[i] != '[' { 1068 | return argNum, i, false 1069 | } 1070 | p.reordered = true 1071 | index, wid, ok := parseArgNumber(format[i:]) 1072 | if ok && 0 <= index && index < numArgs { 1073 | return index, i + wid, true 1074 | } 1075 | p.goodArgNum = false 1076 | return argNum, i + wid, ok 1077 | } 1078 | 1079 | func (p *pp) badArgNum(verb rune) { 1080 | p.buf.writeString(percentBangString) 1081 | p.buf.writeRune(verb) 1082 | p.buf.writeString(badIndexString) 1083 | } 1084 | 1085 | func (p *pp) missingArg(verb rune) { 1086 | p.buf.writeString(percentBangString) 1087 | p.buf.writeRune(verb) 1088 | p.buf.writeString(missingString) 1089 | } 1090 | 1091 | func (p *pp) doPrintf(format string, a []interface{}) { 1092 | p.buf.SetMode(b.SafeEscaped) 1093 | end := len(format) 1094 | argNum := 0 // we process one argument per non-trivial format 1095 | afterIndex := false // previous item in format was an index like [3]. 1096 | p.reordered = false 1097 | formatLoop: 1098 | for i := 0; i < end; { 1099 | p.goodArgNum = true 1100 | lasti := i 1101 | for i < end && format[i] != '%' { 1102 | i++ 1103 | } 1104 | if i > lasti { 1105 | p.buf.writeString(format[lasti:i]) 1106 | } 1107 | if i >= end { 1108 | // done processing format string 1109 | break 1110 | } 1111 | 1112 | // Process one verb 1113 | i++ 1114 | 1115 | // Do we have flags? 1116 | p.fmt.clearflags() 1117 | simpleFormat: 1118 | for ; i < end; i++ { 1119 | c := format[i] 1120 | switch c { 1121 | case '#': 1122 | p.fmt.sharp = true 1123 | case '0': 1124 | p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left. 1125 | case '+': 1126 | p.fmt.plus = true 1127 | case '-': 1128 | p.fmt.minus = true 1129 | p.fmt.zero = false // Do not pad with zeros to the right. 1130 | case ' ': 1131 | p.fmt.space = true 1132 | default: 1133 | // Fast path for common case of ascii lower case simple verbs 1134 | // without precision or width or argument indices. 1135 | if 'a' <= c && c <= 'z' && argNum < len(a) { 1136 | if c == 'v' { 1137 | // Go syntax 1138 | p.fmt.sharpV = p.fmt.sharp 1139 | p.fmt.sharp = false 1140 | // Struct-field syntax 1141 | p.fmt.plusV = p.fmt.plus 1142 | p.fmt.plus = false 1143 | } 1144 | p.printArg(a[argNum], rune(c)) 1145 | argNum++ 1146 | i++ 1147 | continue formatLoop 1148 | } 1149 | // Format is more complex than simple flags and a verb or is malformed. 1150 | break simpleFormat 1151 | } 1152 | } 1153 | 1154 | // Do we have an explicit argument index? 1155 | argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) 1156 | 1157 | // Do we have width? 1158 | if i < end && format[i] == '*' { 1159 | i++ 1160 | p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum) 1161 | 1162 | if !p.fmt.widPresent { 1163 | p.buf.writeString(badWidthString) 1164 | } 1165 | 1166 | // We have a negative width, so take its value and ensure 1167 | // that the minus flag is set 1168 | if p.fmt.wid < 0 { 1169 | p.fmt.wid = -p.fmt.wid 1170 | p.fmt.minus = true 1171 | p.fmt.zero = false // Do not pad with zeros to the right. 1172 | } 1173 | afterIndex = false 1174 | } else { 1175 | p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) 1176 | if afterIndex && p.fmt.widPresent { // "%[3]2d" 1177 | p.goodArgNum = false 1178 | } 1179 | } 1180 | 1181 | // Do we have precision? 1182 | if i+1 < end && format[i] == '.' { 1183 | i++ 1184 | if afterIndex { // "%[3].2d" 1185 | p.goodArgNum = false 1186 | } 1187 | argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) 1188 | if i < end && format[i] == '*' { 1189 | i++ 1190 | p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum) 1191 | // Negative precision arguments don't make sense 1192 | if p.fmt.prec < 0 { 1193 | p.fmt.prec = 0 1194 | p.fmt.precPresent = false 1195 | } 1196 | if !p.fmt.precPresent { 1197 | p.buf.writeString(badPrecString) 1198 | } 1199 | afterIndex = false 1200 | } else { 1201 | p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end) 1202 | if !p.fmt.precPresent { 1203 | p.fmt.prec = 0 1204 | p.fmt.precPresent = true 1205 | } 1206 | } 1207 | } 1208 | 1209 | if !afterIndex { 1210 | argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) 1211 | } 1212 | 1213 | if i >= end { 1214 | p.buf.writeString(noVerbString) 1215 | break 1216 | } 1217 | 1218 | verb, size := rune(format[i]), 1 1219 | if verb >= utf8.RuneSelf { 1220 | verb, size = utf8.DecodeRuneInString(format[i:]) 1221 | } 1222 | i += size 1223 | 1224 | switch { 1225 | case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec. 1226 | p.buf.writeByte('%') 1227 | case !p.goodArgNum: 1228 | p.badArgNum(verb) 1229 | case argNum >= len(a): // No argument left over to print for the current verb. 1230 | p.missingArg(verb) 1231 | case verb == 'v': 1232 | // Go syntax 1233 | p.fmt.sharpV = p.fmt.sharp 1234 | p.fmt.sharp = false 1235 | // Struct-field syntax 1236 | p.fmt.plusV = p.fmt.plus 1237 | p.fmt.plus = false 1238 | fallthrough 1239 | default: 1240 | p.printArg(a[argNum], verb) 1241 | argNum++ 1242 | } 1243 | } 1244 | 1245 | // Check for extra arguments unless the call accessed the arguments 1246 | // out of order, in which case it's too expensive to detect if they've all 1247 | // been used and arguably OK if they're not. 1248 | if !p.reordered && argNum < len(a) { 1249 | p.fmt.clearflags() 1250 | p.buf.writeString(extraString) 1251 | for i, arg := range a[argNum:] { 1252 | if i > 0 { 1253 | p.buf.writeString(commaSpaceString) 1254 | } 1255 | if arg == nil { 1256 | p.buf.writeString(nilAngleString) 1257 | } else { 1258 | p.buf.writeString(reflect.TypeOf(arg).String()) 1259 | p.buf.writeByte('=') 1260 | p.printArg(arg, 'v') 1261 | } 1262 | } 1263 | p.buf.writeByte(')') 1264 | } 1265 | } 1266 | 1267 | func (p *pp) doPrint(a []interface{}) { 1268 | p.buf.SetMode(b.SafeEscaped) 1269 | prevString := false 1270 | for argNum, arg := range a { 1271 | isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String 1272 | // Add a space between two non-string arguments. 1273 | if argNum > 0 && !isString && !prevString { 1274 | p.buf.writeByte(' ') 1275 | } 1276 | p.printArg(arg, 'v') 1277 | prevString = isString 1278 | } 1279 | } 1280 | 1281 | // doPrintln is like doPrint but always adds a space between arguments 1282 | // and a newline after the last argument. 1283 | func (p *pp) doPrintln(a []interface{}) { 1284 | p.buf.SetMode(b.SafeEscaped) 1285 | for argNum, arg := range a { 1286 | if argNum > 0 { 1287 | p.buf.writeByte(' ') 1288 | } 1289 | p.printArg(arg, 'v') 1290 | } 1291 | p.buf.writeByte('\n') 1292 | } 1293 | -------------------------------------------------------------------------------- /internal/rfmt/print.go.diff: -------------------------------------------------------------------------------- 1 | --- print.go.orig 2021-06-18 16:45:56.623967000 +0200 2 | +++ print.go 2021-06-19 16:26:32.751061000 +0200 3 | @@ -1,16 +1,27 @@ 4 | +// Code generated from print.go.orig. DO NOT EDIT 5 | +// GENERATED FILE DO NOT EDIT 6 | +// 7 | // Copyright 2009 The Go Authors. All rights reserved. 8 | // Use of this source code is governed by a BSD-style 9 | // license that can be found in the LICENSE file. 10 | 11 | -package fmt 12 | +package rfmt 13 | 14 | import ( 15 | - "internal/fmtsort" 16 | + // CUSTOM: needed to avoid a type mismatch on Formatter. 17 | + origFmt "fmt" 18 | "io" 19 | "os" 20 | "reflect" 21 | "sync" 22 | "unicode/utf8" 23 | + 24 | + // CUSTOM: our own imports. 25 | + i "github.com/cockroachdb/redact/interfaces" 26 | + b "github.com/cockroachdb/redact/internal/buffer" 27 | + m "github.com/cockroachdb/redact/internal/markers" 28 | + w "github.com/cockroachdb/redact/internal/redact" 29 | + "github.com/cockroachdb/redact/internal/rfmt/fmtsort" 30 | ) 31 | 32 | // Strings for use with buffer.WriteString. 33 | @@ -51,7 +62,8 @@ 34 | // The implementation controls how State and rune are interpreted, 35 | // and may call Sprint(f) or Fprint(f) etc. to generate its output. 36 | type Formatter interface { 37 | - Format(f State, verb rune) 38 | + // CUSTOM: refer to the original type, not the one defined here. 39 | + Format(f origFmt.State, verb rune) 40 | } 41 | 42 | // Stringer is implemented by any value that has a String method, 43 | @@ -71,40 +83,34 @@ 44 | GoString() string 45 | } 46 | 47 | -// Use simple []byte instead of bytes.Buffer to avoid large dependency. 48 | -type buffer []byte 49 | +// CUSTOM: smarter buffer. 50 | +type buffer struct { 51 | + b.Buffer 52 | +} 53 | 54 | func (b *buffer) write(p []byte) { 55 | - *b = append(*b, p...) 56 | + b.Write(p) 57 | } 58 | 59 | func (b *buffer) writeString(s string) { 60 | - *b = append(*b, s...) 61 | + b.WriteString(s) 62 | } 63 | 64 | func (b *buffer) writeByte(c byte) { 65 | - *b = append(*b, c) 66 | + b.WriteByte(c) 67 | } 68 | 69 | func (bp *buffer) writeRune(r rune) { 70 | - if r < utf8.RuneSelf { 71 | - *bp = append(*bp, byte(r)) 72 | - return 73 | - } 74 | - 75 | - b := *bp 76 | - n := len(b) 77 | - for n+utf8.UTFMax > cap(b) { 78 | - b = append(b, 0) 79 | - } 80 | - w := utf8.EncodeRune(b[n:n+utf8.UTFMax], r) 81 | - *bp = b[:n+w] 82 | + bp.WriteRune(r) 83 | } 84 | 85 | // pp is used to store a printer's state and is reused with sync.Pool to avoid allocations. 86 | type pp struct { 87 | buf buffer 88 | 89 | + // CUSTOM: override safety in recursive calls. 90 | + override overrideMode 91 | + 92 | // arg holds the current item, as an interface{}. 93 | arg interface{} 94 | 95 | @@ -150,11 +156,11 @@ 96 | // to place back in the pool. 97 | // 98 | // See https://golang.org/issue/23199 99 | - if cap(p.buf) > 64<<10 { 100 | + if p.buf.Cap() > 64<<10 { 101 | return 102 | } 103 | 104 | - p.buf = p.buf[:0] 105 | + p.buf.Reset() 106 | p.arg = nil 107 | p.value = reflect.Value{} 108 | p.wrappedErr = nil 109 | @@ -183,14 +189,16 @@ 110 | 111 | // Implement Write so we can call Fprintf on a pp (through State), for 112 | // recursive use in custom verbs. 113 | -func (p *pp) Write(b []byte) (ret int, err error) { 114 | - p.buf.write(b) 115 | - return len(b), nil 116 | +func (p *pp) Write(bs []byte) (ret int, err error) { 117 | + defer p.startUnsafe().restore() 118 | + p.buf.write(bs) 119 | + return len(bs), nil 120 | } 121 | 122 | // Implement WriteString so that we can call io.WriteString 123 | // on a pp (through state), for efficiency. 124 | func (p *pp) WriteString(s string) (ret int, err error) { 125 | + defer p.startUnsafe().restore() 126 | p.buf.writeString(s) 127 | return len(s), nil 128 | } 129 | @@ -202,7 +210,7 @@ 130 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 131 | p := newPrinter() 132 | p.doPrintf(format, a) 133 | - n, err = w.Write(p.buf) 134 | + n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 135 | p.free() 136 | return 137 | } 138 | @@ -214,10 +222,10 @@ 139 | } 140 | 141 | // Sprintf formats according to a format specifier and returns the resulting string. 142 | -func Sprintf(format string, a ...interface{}) string { 143 | +func Sprintf(format string, a ...interface{}) m.RedactableString { 144 | p := newPrinter() 145 | p.doPrintf(format, a) 146 | - s := string(p.buf) 147 | + s := p.buf.TakeRedactableString() 148 | p.free() 149 | return s 150 | } 151 | @@ -230,7 +238,7 @@ 152 | func Fprint(w io.Writer, a ...interface{}) (n int, err error) { 153 | p := newPrinter() 154 | p.doPrint(a) 155 | - n, err = w.Write(p.buf) 156 | + n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 157 | p.free() 158 | return 159 | } 160 | @@ -244,10 +252,10 @@ 161 | 162 | // Sprint formats using the default formats for its operands and returns the resulting string. 163 | // Spaces are added between operands when neither is a string. 164 | -func Sprint(a ...interface{}) string { 165 | +func Sprint(a ...interface{}) m.RedactableString { 166 | p := newPrinter() 167 | p.doPrint(a) 168 | - s := string(p.buf) 169 | + s := p.buf.TakeRedactableString() 170 | p.free() 171 | return s 172 | } 173 | @@ -262,7 +270,7 @@ 174 | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { 175 | p := newPrinter() 176 | p.doPrintln(a) 177 | - n, err = w.Write(p.buf) 178 | + n, err = w.Write([]byte(p.buf.TakeRedactableBytes())) 179 | p.free() 180 | return 181 | } 182 | @@ -276,10 +284,10 @@ 183 | 184 | // Sprintln formats using the default formats for its operands and returns the resulting string. 185 | // Spaces are always added between operands and a newline is appended. 186 | -func Sprintln(a ...interface{}) string { 187 | +func Sprintln(a ...interface{}) m.RedactableString { 188 | p := newPrinter() 189 | p.doPrintln(a) 190 | - s := string(p.buf) 191 | + s := p.buf.TakeRedactableString() 192 | p.free() 193 | return s 194 | } 195 | @@ -351,6 +359,7 @@ 196 | func (p *pp) fmtBool(v bool, verb rune) { 197 | switch verb { 198 | case 't', 'v': 199 | + defer p.startUnsafe().restore() 200 | p.fmt.fmtBoolean(v) 201 | default: 202 | p.badVerb(verb) 203 | @@ -362,6 +371,7 @@ 204 | func (p *pp) fmt0x64(v uint64, leading0x bool) { 205 | sharp := p.fmt.sharp 206 | p.fmt.sharp = leading0x 207 | + defer p.startUnsafe().restore() 208 | p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits) 209 | p.fmt.sharp = sharp 210 | } 211 | @@ -373,23 +383,32 @@ 212 | if p.fmt.sharpV && !isSigned { 213 | p.fmt0x64(v, true) 214 | } else { 215 | + defer p.startUnsafe().restore() 216 | p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) 217 | } 218 | case 'd': 219 | + defer p.startUnsafe().restore() 220 | p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) 221 | case 'b': 222 | + defer p.startUnsafe().restore() 223 | p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits) 224 | case 'o', 'O': 225 | + defer p.startUnsafe().restore() 226 | p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits) 227 | case 'x': 228 | + defer p.startUnsafe().restore() 229 | p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits) 230 | case 'X': 231 | + defer p.startUnsafe().restore() 232 | p.fmt.fmtInteger(v, 16, isSigned, verb, udigits) 233 | case 'c': 234 | + defer p.startUnsafe().restore() 235 | p.fmt.fmtC(v) 236 | case 'q': 237 | + defer p.startUnsafe().restore() 238 | p.fmt.fmtQc(v) 239 | case 'U': 240 | + defer p.startUnsafe().restore() 241 | p.fmt.fmtUnicode(v) 242 | default: 243 | p.badVerb(verb) 244 | @@ -401,12 +420,16 @@ 245 | func (p *pp) fmtFloat(v float64, size int, verb rune) { 246 | switch verb { 247 | case 'v': 248 | + defer p.startUnsafe().restore() 249 | p.fmt.fmtFloat(v, size, 'g', -1) 250 | case 'b', 'g', 'G', 'x', 'X': 251 | + defer p.startUnsafe().restore() 252 | p.fmt.fmtFloat(v, size, verb, -1) 253 | case 'f', 'e', 'E': 254 | + defer p.startUnsafe().restore() 255 | p.fmt.fmtFloat(v, size, verb, 6) 256 | case 'F': 257 | + defer p.startUnsafe().restore() 258 | p.fmt.fmtFloat(v, size, 'f', 6) 259 | default: 260 | p.badVerb(verb) 261 | @@ -437,18 +460,23 @@ 262 | func (p *pp) fmtString(v string, verb rune) { 263 | switch verb { 264 | case 'v': 265 | + defer p.startUnsafe().restore() 266 | if p.fmt.sharpV { 267 | p.fmt.fmtQ(v) 268 | } else { 269 | p.fmt.fmtS(v) 270 | } 271 | case 's': 272 | + defer p.startUnsafe().restore() 273 | p.fmt.fmtS(v) 274 | case 'x': 275 | + defer p.startUnsafe().restore() 276 | p.fmt.fmtSx(v, ldigits) 277 | case 'X': 278 | + defer p.startUnsafe().restore() 279 | p.fmt.fmtSx(v, udigits) 280 | case 'q': 281 | + defer p.startUnsafe().restore() 282 | p.fmt.fmtQ(v) 283 | default: 284 | p.badVerb(verb) 285 | @@ -478,17 +506,24 @@ 286 | if i > 0 { 287 | p.buf.writeByte(' ') 288 | } 289 | - p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) 290 | + func(p *pp, c byte, verb rune) { 291 | + defer p.startUnsafe().restore() 292 | + p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) 293 | + }(p, c, verb) 294 | } 295 | p.buf.writeByte(']') 296 | } 297 | case 's': 298 | + defer p.startUnsafe().restore() 299 | p.fmt.fmtBs(v) 300 | case 'x': 301 | + defer p.startUnsafe().restore() 302 | p.fmt.fmtBx(v, ldigits) 303 | case 'X': 304 | + defer p.startUnsafe().restore() 305 | p.fmt.fmtBx(v, udigits) 306 | case 'q': 307 | + defer p.startUnsafe().restore() 308 | p.fmt.fmtQ(string(v)) 309 | default: 310 | p.printValue(reflect.ValueOf(v), verb, 0) 311 | @@ -519,6 +554,7 @@ 312 | p.buf.writeByte(')') 313 | } else { 314 | if u == 0 { 315 | + defer p.startUnsafe().restore() 316 | p.fmt.padString(nilAngleString) 317 | } else { 318 | p.fmt0x64(uint64(u), !p.fmt.sharp) 319 | @@ -586,10 +622,37 @@ 320 | verb = 'v' 321 | } 322 | 323 | + if p.override != overrideUnsafe { 324 | + switch v := p.arg.(type) { 325 | + case i.SafeFormatter: 326 | + handled = true 327 | + defer p.catchPanic(p.arg, verb, "SafeFormat") 328 | + v.SafeFormat(p, verb) 329 | + return 330 | + 331 | + case i.SafeMessager: 332 | + handled = true 333 | + defer p.catchPanic(p.arg, verb, "SafeMessager") 334 | + defer p.startSafeOverride().restore() 335 | + p.fmtString(v.SafeMessage(), verb) 336 | + return 337 | + 338 | + case error: 339 | + if redactErrorFn != nil { 340 | + handled = true 341 | + defer p.catchPanic(p.arg, verb, "SafeFormatter") 342 | + redactErrorFn(v, p, verb) 343 | + return 344 | + } 345 | + } 346 | + } 347 | + 348 | // Is it a Formatter? 349 | if formatter, ok := p.arg.(Formatter); ok { 350 | handled = true 351 | defer p.catchPanic(p.arg, verb, "Format") 352 | + // The mode is implicit: the Format() call will 353 | + // use p.Write() which already sets the mode. 354 | formatter.Format(p, verb) 355 | return 356 | } 357 | @@ -600,6 +663,7 @@ 358 | handled = true 359 | defer p.catchPanic(p.arg, verb, "GoString") 360 | // Print the result of GoString unadorned. 361 | + defer p.startUnsafe().restore() 362 | p.fmt.fmtS(stringer.GoString()) 363 | return 364 | } 365 | @@ -632,6 +696,21 @@ 366 | } 367 | 368 | func (p *pp) printArg(arg interface{}, verb rune) { 369 | + t := reflect.TypeOf(arg) 370 | + if safeTypeRegistry[t] { 371 | + defer p.startSafeOverride().restore() 372 | + } else if t == safeWrapperType { 373 | + defer p.startSafeOverride().restore() 374 | + arg = arg.(w.SafeWrapper).GetValue() 375 | + } else if t == unsafeWrapperType { 376 | + defer p.startUnsafeOverride().restore() 377 | + arg = arg.(w.UnsafeWrap).GetValue() 378 | + } 379 | + 380 | + if _, ok := arg.(i.SafeValue); ok { 381 | + defer p.startSafeOverride().restore() 382 | + } 383 | + 384 | p.arg = arg 385 | p.value = reflect.Value{} 386 | 387 | @@ -697,13 +776,35 @@ 388 | case reflect.Value: 389 | // Handle extractable values with special methods 390 | // since printValue does not handle them at depth 0. 391 | - if f.IsValid() && f.CanInterface() { 392 | - p.arg = f.Interface() 393 | - if p.handleMethods(verb) { 394 | + if f.IsValid() { 395 | + t := f.Type() 396 | + if p.handleSpecialValues(f, t, verb, 0) { 397 | return 398 | } 399 | + 400 | + if safeTypeRegistry[t] { 401 | + defer p.startSafeOverride().restore() 402 | + } 403 | + 404 | + if f.CanInterface() { 405 | + p.arg = f.Interface() 406 | + if _, ok := p.arg.(i.SafeValue); ok { 407 | + defer p.startSafeOverride().restore() 408 | + } 409 | + if p.handleMethods(verb) { 410 | + return 411 | + } 412 | + } 413 | } 414 | p.printValue(f, verb, 0) 415 | + case m.RedactableString: 416 | + defer p.startPreRedactable().restore() 417 | + p.buf.WriteString(string(f)) 418 | + return 419 | + case m.RedactableBytes: 420 | + defer p.startPreRedactable().restore() 421 | + p.buf.Write([]byte(f)) 422 | + return 423 | default: 424 | // If the type is not simple, it might have methods. 425 | if !p.handleMethods(verb) { 426 | @@ -718,11 +819,25 @@ 427 | // It does not handle 'p' and 'T' verbs because these should have been already handled by printArg. 428 | func (p *pp) printValue(value reflect.Value, verb rune, depth int) { 429 | // Handle values with special methods if not already handled by printArg (depth == 0). 430 | - if depth > 0 && value.IsValid() && value.CanInterface() { 431 | - p.arg = value.Interface() 432 | - if p.handleMethods(verb) { 433 | + if depth > 0 && value.IsValid() { 434 | + t := value.Type() 435 | + if p.handleSpecialValues(value, t, verb, depth) { 436 | return 437 | } 438 | + 439 | + if safeTypeRegistry[t] { 440 | + defer p.startSafeOverride().restore() 441 | + } 442 | + 443 | + if value.CanInterface() { 444 | + p.arg = value.Interface() 445 | + if _, ok := p.arg.(i.SafeValue); ok { 446 | + defer p.startSafeOverride().restore() 447 | + } 448 | + if p.handleMethods(verb) { 449 | + return 450 | + } 451 | + } 452 | } 453 | p.arg = nil 454 | p.value = value 455 | @@ -946,7 +1061,9 @@ 456 | // argNumber returns the next argument to evaluate, which is either the value of the passed-in 457 | // argNum or the value of the bracketed integer that begins format[i:]. It also returns 458 | // the new value of i, that is, the index of the next byte of the format to process. 459 | -func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) { 460 | +func (p *pp) argNumber( 461 | + argNum int, format string, i int, numArgs int, 462 | +) (newArgNum, newi int, found bool) { 463 | if len(format) <= i || format[i] != '[' { 464 | return argNum, i, false 465 | } 466 | @@ -972,6 +1089,7 @@ 467 | } 468 | 469 | func (p *pp) doPrintf(format string, a []interface{}) { 470 | + p.buf.SetMode(b.SafeEscaped) 471 | end := len(format) 472 | argNum := 0 // we process one argument per non-trivial format 473 | afterIndex := false // previous item in format was an index like [3]. 474 | @@ -1147,6 +1265,7 @@ 475 | } 476 | 477 | func (p *pp) doPrint(a []interface{}) { 478 | + p.buf.SetMode(b.SafeEscaped) 479 | prevString := false 480 | for argNum, arg := range a { 481 | isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String 482 | @@ -1162,6 +1281,7 @@ 483 | // doPrintln is like doPrint but always adds a space between arguments 484 | // and a newline after the last argument. 485 | func (p *pp) doPrintln(a []interface{}) { 486 | + p.buf.SetMode(b.SafeEscaped) 487 | for argNum, arg := range a { 488 | if argNum > 0 { 489 | p.buf.writeByte(' ') 490 | -------------------------------------------------------------------------------- /internal/rfmt/printer_adapter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package rfmt 16 | 17 | import i "github.com/cockroachdb/redact/interfaces" 18 | 19 | // SafeString implements SafePrinter. 20 | func (p *pp) SafeString(s i.SafeString) { 21 | defer p.startSafeOverride().restore() 22 | p.buf.WriteString(string(s)) 23 | } 24 | 25 | // SafeInt implements SafePrinter. 26 | func (p *pp) SafeInt(s i.SafeInt) { 27 | defer p.startSafeOverride().restore() 28 | p.fmtInteger(uint64(s), signed, 'd') 29 | } 30 | 31 | // SafeUint implements SafePrinter. 32 | func (p *pp) SafeUint(s i.SafeUint) { 33 | defer p.startSafeOverride().restore() 34 | p.fmtInteger(uint64(s), unsigned, 'd') 35 | } 36 | 37 | // SafeFloat implements SafePrinter. 38 | func (p *pp) SafeFloat(s i.SafeFloat) { 39 | defer p.startSafeOverride().restore() 40 | p.fmtFloat(float64(s), 64, 'v') 41 | } 42 | 43 | // SafeRune implements SafePrinter. 44 | func (p *pp) SafeRune(r i.SafeRune) { 45 | defer p.startSafeOverride().restore() 46 | p.buf.WriteRune(rune(r)) 47 | } 48 | 49 | // SafeByte implements SafePrinter. 50 | func (p *pp) SafeByte(r i.SafeByte) { 51 | defer p.startSafeOverride().restore() 52 | p.buf.WriteByte(byte(r)) 53 | } 54 | 55 | // SafeBytes implements SafePrinter. 56 | func (p *pp) SafeBytes(r i.SafeBytes) { 57 | defer p.startSafeOverride().restore() 58 | p.buf.Write(r) 59 | } 60 | 61 | func (p *pp) Print(args ...interface{}) { 62 | defer p.buf.SetMode(p.buf.GetMode()) 63 | np := newPrinter() 64 | np.buf = p.buf 65 | np.doPrint(args) 66 | p.buf = np.buf 67 | np.buf = buffer{} 68 | np.free() 69 | } 70 | 71 | func (p *pp) Printf(format string, arg ...interface{}) { 72 | defer p.buf.SetMode(p.buf.GetMode()) 73 | np := newPrinter() 74 | np.buf = p.buf 75 | np.doPrintf(format, arg) 76 | p.buf = np.buf 77 | np.buf = buffer{} 78 | np.free() 79 | } 80 | 81 | func (p *pp) UnsafeString(s string) { 82 | defer p.startUnsafe().restore() 83 | _, _ = p.buf.WriteString(s) 84 | } 85 | 86 | func (p *pp) UnsafeByte(bb byte) { 87 | defer p.startUnsafe().restore() 88 | _ = p.buf.WriteByte(bb) 89 | } 90 | func (p *pp) UnsafeBytes(bs []byte) { 91 | defer p.startUnsafe().restore() 92 | _, _ = p.buf.Write(bs) 93 | } 94 | func (p *pp) UnsafeRune(r rune) { 95 | defer p.startUnsafe().restore() 96 | _ = p.buf.WriteRune(r) 97 | } 98 | -------------------------------------------------------------------------------- /internal/rfmt/refresh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This file re-generates the sources in this directory from the Go 4 | # standard library. 5 | # 6 | set -euxo pipefail 7 | 8 | cp $GOROOT/src/fmt/format.go format.go 9 | patch -p0 fmtsort/"$n" 25 | done 26 | 27 | -------------------------------------------------------------------------------- /internal/rfmt/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package rfmt 16 | 17 | import ( 18 | "reflect" 19 | 20 | i "github.com/cockroachdb/redact/interfaces" 21 | ) 22 | 23 | // RegisterSafeType registers a data type to always be considered safe 24 | // during the production of redactable strings. 25 | func RegisterSafeType(t reflect.Type) { 26 | safeTypeRegistry[t] = true 27 | } 28 | 29 | // safeTypeRegistry registers Go data types that are to be always 30 | // considered safe, even when they don't implement SafeValue. 31 | var safeTypeRegistry = map[reflect.Type]bool{} 32 | 33 | func isSafeValue(a interface{}) bool { 34 | return safeTypeRegistry[reflect.TypeOf(a)] 35 | } 36 | 37 | // redactErrorFn can be injected from an error library 38 | // to render error objects safely. 39 | var redactErrorFn func(err error, p i.SafePrinter, verb rune) 40 | 41 | // RegisterRedactErrorFn registers an error redaction function for use 42 | // during automatic redaction by this package. 43 | // Provided e.g. by cockroachdb/errors. 44 | func RegisterRedactErrorFn(fn func(err error, p i.SafePrinter, verb rune)) { 45 | redactErrorFn = fn 46 | } 47 | -------------------------------------------------------------------------------- /internal/rfmt/registry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package rfmt 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | 21 | w "github.com/cockroachdb/redact/internal/redact" 22 | ) 23 | 24 | func TestCustomSafeTypes(t *testing.T) { 25 | defer func(prev map[reflect.Type]bool) { safeTypeRegistry = prev }(safeTypeRegistry) 26 | RegisterSafeType(reflect.TypeOf(int32(123))) 27 | 28 | // Also a struct containing the safe type. 29 | type s struct { 30 | v int32 31 | } 32 | x := s{v: 999} 33 | 34 | actual := Sprint(123, 35 | int32(456), 36 | x, 37 | reflect.ValueOf(int32(789)), 38 | reflect.ValueOf(x), 39 | ) 40 | const expected = `‹123› 456 {999} 789 {999}` 41 | if actual != expected { 42 | t.Errorf("expected %q, got %q", expected, actual) 43 | } 44 | 45 | // Unsafe can override. 46 | actual = Sprint(123, 47 | w.Unsafe(int32(456)), 48 | w.Unsafe(x), 49 | w.Unsafe(reflect.ValueOf(int32(789))), 50 | w.Unsafe(reflect.ValueOf(x)), 51 | ) 52 | const expected2 = `‹123› ‹456› ‹{999}› ‹789› ‹{999}›` 53 | if actual != expected2 { 54 | t.Errorf("expected %q, got %q", expected2, actual) 55 | } 56 | 57 | // Safe can override unsafe. 58 | actual = Sprint(123, 59 | w.Safe(w.Unsafe(int32(456))), 60 | w.Safe(w.Unsafe(x)), 61 | w.Safe(w.Unsafe(reflect.ValueOf(int32(789)))), 62 | w.Safe(w.Unsafe(reflect.ValueOf(x))), 63 | ) 64 | const expected3 = `‹123› 456 {999} 789 {999}` 65 | if actual != expected3 { 66 | t.Errorf("expected %q, got %q", expected3, actual) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /markers_print.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | "io" 19 | 20 | "github.com/cockroachdb/redact/internal/rfmt" 21 | ) 22 | 23 | // Sprint prints out the arguments and encloses unsafe bits 24 | // between redaction markers. 25 | // If either safe and unsafe bits of data contain the markers 26 | // in their representation already, they are escaped first. 27 | // If a RedactableString or RedactableBytes argument is passed, 28 | // it is reproduced as-is without escaping. 29 | func Sprint(args ...interface{}) RedactableString { 30 | return rfmt.Sprint(args...) 31 | } 32 | 33 | // Sprintf formats the arguments and encloses unsafe bits 34 | // between redaction markers. 35 | // If either safe and unsafe bits of data contain the markers 36 | // in their representation already, they are escaped first. 37 | // The format is always considered safe and the caller 38 | // is responsible to ensure that the markers are not present 39 | // in the format string. 40 | func Sprintf(format string, args ...interface{}) RedactableString { 41 | return rfmt.Sprintf(format, args...) 42 | } 43 | 44 | // HelperForErrorf is a helper to implement a redaction-aware 45 | // fmt.Errorf-compatible function in a different package. It formats 46 | // the string according to the given format and arguments in the same 47 | // way as Sprintf, but in addition to this if the format contains %w 48 | // and an error object in the proper argument position it also returns 49 | // that error object. 50 | // 51 | // Note: This function only works if an error redaction function 52 | // has been injected with RegisterRedactErrorFn(). 53 | func HelperForErrorf(format string, args ...interface{}) (RedactableString, error) { 54 | return rfmt.HelperForErrorf(format, args...) 55 | } 56 | 57 | // Sprintfn produces a RedactableString using the provided 58 | // SafeFormat-alike function. 59 | func Sprintfn(printer func(w SafePrinter)) RedactableString { 60 | return rfmt.Sprintfn(printer) 61 | } 62 | 63 | // StringWithoutMarkers formats the provided SafeFormatter and strips 64 | // the redaction markers from the result. This is provided for 65 | // convenience to facilitate the implementation of String() methods 66 | // alongside SafeFormat() to avoid code duplication. 67 | // 68 | // Note: if this function is ever found to be a performance 69 | // bottleneck, one can consider using an alternate implementation of 70 | // Sprint() which similarly calls the SafeFormat() methods but does 71 | // not introduce markers and instead writes to a string buffer 72 | // directly. 73 | func StringWithoutMarkers(f SafeFormatter) string { 74 | return Sprint(f).StripMarkers() 75 | } 76 | 77 | // Fprint is like Sprint but outputs the redactable 78 | // string to the provided Writer. 79 | func Fprint(w io.Writer, args ...interface{}) (n int, err error) { 80 | return rfmt.Fprint(w, args...) 81 | } 82 | 83 | // Fprintf is like Sprintf but outputs the redactable string to the 84 | // provided Writer. 85 | func Fprintf(w io.Writer, format string, args ...interface{}) (n int, err error) { 86 | return rfmt.Fprintf(w, format, args...) 87 | } 88 | -------------------------------------------------------------------------------- /markers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "reflect" 22 | "regexp" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/cockroachdb/redact/builder" 27 | i "github.com/cockroachdb/redact/interfaces" 28 | m "github.com/cockroachdb/redact/internal/markers" 29 | ) 30 | 31 | type p = SafePrinter 32 | 33 | func TestPrinter(t *testing.T) { 34 | var sn *safeNil 35 | var unsafeSptr *string 36 | ptrptr := &unsafeSptr 37 | ptrptrP := fmt.Sprintf("%p", ptrptr) 38 | ptrptrD := fmt.Sprintf("%d", ptrptr) 39 | ptrptrV := fmt.Sprintf("%v", ptrptr) 40 | var buf builder.StringBuilder 41 | buf.Printf("safe %s", "unsafe") 42 | 43 | testData := []struct { 44 | fn func(p) 45 | expected string 46 | }{ 47 | {func(w p) { w.SafeString("ab") }, `ab`}, 48 | {func(w p) { w.SafeRune('☃') }, `☃`}, 49 | {func(w p) { w.SafeRune(' ') }, ` `}, 50 | {func(w p) { w.SafeInt(-123) }, `-123`}, 51 | {func(w p) { w.SafeUint(123) }, `123`}, 52 | {func(w p) { w.SafeFloat(3.14) }, `3.14`}, 53 | {func(w p) { w.UnsafeString("rs") }, `‹rs›`}, 54 | {func(w p) { w.UnsafeByte('t') }, `‹t›`}, 55 | {func(w p) { w.UnsafeByte(m.StartS[0]) }, `‹?›`}, 56 | {func(w p) { w.UnsafeBytes([]byte("uv")) }, `‹uv›`}, 57 | {func(w p) { w.UnsafeRune('🛑') }, `‹🛑›`}, 58 | {func(w p) { w.Print("fg", safe("hi")) }, `‹fg›hi`}, 59 | {func(w p) { w.Print("fg", reflect.ValueOf(safe("hi"))) }, `‹fg›hi`}, 60 | {func(w p) { w.Printf("jk %s %s", "lm", safe("no")) }, `jk ‹lm› no`}, 61 | {func(w p) { w.Printf("jk %s %s", "lm", reflect.ValueOf(safe("no"))) }, `jk ‹lm› no`}, 62 | {func(w p) { w.Print("ar", []string{"hel", "lo"}) }, `‹ar›[‹hel› ‹lo›]`}, 63 | {func(w p) { w.Print("ar", []i.SafeValue{safe("hel"), safe("lo")}) }, `‹ar›[hel lo]`}, 64 | {func(w p) { w.Print("ar", []interface{}{safe("hel"), safe("lo")}) }, `‹ar›[hel lo]`}, 65 | {func(w p) { w.Print("ar", []int{123, 456}) }, `‹ar›[‹123› ‹456›]`}, 66 | {func(w p) { w.Print("ar", []byte{55, 56}) }, `‹ar›[‹55› ‹56›]`}, 67 | {func(w p) { w.Print("ar", Safe([]byte{55, 56})) }, `‹ar›[55 56]`}, 68 | 69 | // Numeric values. 70 | {func(w p) { w.Printf("vn %+d", SafeInt(123)) }, `vn +123`}, 71 | {func(w p) { w.Printf("vn %05d", SafeUint(123)) }, `vn 00123`}, 72 | {func(w p) { w.Printf("vn %e", SafeFloat(3.14)) }, `vn 3.140000e+00`}, 73 | 74 | // Pre-redactable strings. 75 | {func(w p) { w.Print("pr", RedactableString("hi")) }, `‹pr›hi`}, 76 | {func(w p) { w.Print("pr", RedactableBytes("hi")) }, `‹pr›hi`}, 77 | {func(w p) { w.Print("prr", reflect.ValueOf(RedactableString("hi"))) }, `‹prr›hi`}, 78 | {func(w p) { w.Print("prr", reflect.ValueOf(RedactableBytes("hi"))) }, `‹prr›hi`}, 79 | 80 | // The special string verbs are honored for plain strings. 81 | {func(w p) { w.Printf("fg %q", safe("hi")) }, `fg "hi"`}, 82 | {func(w p) { w.Printf("fg %#q", safe("hi")) }, "fg `hi`"}, 83 | {func(w p) { w.Printf("fg %x", safe("hi")) }, `fg 6869`}, 84 | {func(w p) { w.Printf("fg %X", safe("hi")) }, "fg 6869"}, 85 | {func(w p) { w.Printf("fg %q", Safe("hi")) }, `fg "hi"`}, 86 | {func(w p) { w.Printf("fg %#q", Safe("hi")) }, "fg `hi`"}, 87 | {func(w p) { w.Printf("fg %x", Safe("hi")) }, `fg 6869`}, 88 | {func(w p) { w.Printf("fg %X", Safe("hi")) }, "fg 6869"}, 89 | {func(w p) { w.Printf("fg %q", SafeString("hi")) }, `fg "hi"`}, 90 | {func(w p) { w.Printf("fg %#q", SafeString("hi")) }, "fg `hi`"}, 91 | {func(w p) { w.Printf("fg %x", SafeString("hi")) }, `fg 6869`}, 92 | {func(w p) { w.Printf("fg %X", SafeString("hi")) }, "fg 6869"}, 93 | {func(w p) { w.Printf("fg %q", "hi") }, `fg ‹"hi"›`}, 94 | {func(w p) { w.Printf("fg %#q", "hi") }, "fg ‹`hi`›"}, 95 | {func(w p) { w.Printf("fg %x", "hi") }, `fg ‹6869›`}, 96 | {func(w p) { w.Printf("fg %X", "hi") }, "fg ‹6869›"}, 97 | // However they are not honored for pre-redactable strings, 98 | // because they would unsafely mask the redaction markers 99 | {func(w p) { w.Printf("fg %q", RedactableString("hi")) }, `fg hi`}, 100 | {func(w p) { w.Printf("fg %#q", RedactableString("hi")) }, "fg hi"}, 101 | {func(w p) { w.Printf("fg %x", RedactableString("hi")) }, `fg hi`}, 102 | {func(w p) { w.Printf("fg %X", RedactableString("hi")) }, "fg hi"}, 103 | {func(w p) { w.Printf("fg %q", RedactableBytes("hi")) }, `fg hi`}, 104 | {func(w p) { w.Printf("fg %#q", RedactableBytes("hi")) }, "fg hi"}, 105 | {func(w p) { w.Printf("fg %x", RedactableBytes("hi")) }, `fg hi`}, 106 | {func(w p) { w.Printf("fg %X", RedactableBytes("hi")) }, "fg hi"}, 107 | // Direct access to the fmt.State. 108 | {func(w p) { _, _ = w.Write([]byte("pq")) }, `‹pq›`}, 109 | // Safe strings and runes containing the delimiters get escaped. 110 | {func(w p) { w.SafeString("a ‹ b › c") }, `a ? b ? c`}, 111 | {func(w p) { w.SafeRune('‹') }, `?`}, 112 | {func(w p) { w.SafeRune('›') }, `?`}, 113 | {func(w p) { w.Print("a ‹ b › c", safe("d ‹ e › f")) }, 114 | `‹a ? b ? c›d ? e ? f`}, 115 | {func(w p) { w.Printf("f %s %s", "a ‹ b › c", safe("d ‹ e › f")) }, 116 | `f ‹a ? b ? c› d ? e ? f`}, 117 | // Space and newlines at the end of an unsafe string get removed, 118 | // but not at the end of a safe string. 119 | {func(w p) { w.SafeString("ab \n ") }, "ab \n "}, 120 | {func(w p) { w.UnsafeString("cd \n ") }, "‹cd ›\n‹ ›"}, 121 | {func(w p) { w.Print("ab ", safe("cd ")) }, "‹ab ›cd "}, 122 | {func(w p) { w.Printf("ab :%s: :%s: ", "cd ", safe("de ")) }, "ab :‹cd ›: :de : "}, 123 | // Spaces as runes get preserved. 124 | {func(w p) { w.SafeRune(' ') }, ` `}, 125 | {func(w p) { w.SafeRune('\n') }, "\n"}, 126 | {func(w p) { w.UnsafeRune(' ') }, `‹ ›`}, 127 | {func(w p) { w.UnsafeRune('\n') }, "\n"}, 128 | // The Safe() API turns anything into something safe. However, the contents 129 | // still get escaped as needed. 130 | {func(w p) { w.Print("ab ", Safe("c‹d›e ")) }, "‹ab ›c?d?e "}, 131 | {func(w p) { w.Printf("ab %03d ", Safe(12)) }, "ab 012 "}, 132 | // Something that'd be otherwise safe, becomes unsafe with Unsafe(). 133 | {func(w p) { w.Print(Unsafe(SafeString("abc"))) }, "‹abc›"}, 134 | {func(w p) { w.Print(Unsafe(RedactableString("ab‹c›"))) }, "‹ab?c?›"}, 135 | {func(w p) { w.Print(Unsafe(RedactableBytes("ab‹c›"))) }, "‹ab?c?›"}, 136 | {func(w p) { w.Print(Unsafe(SafeRune('a'))) }, "‹97›"}, 137 | {func(w p) { w.Print(Unsafe(SafeInt(-123))) }, "‹-123›"}, 138 | {func(w p) { w.Print(Unsafe(SafeUint(123))) }, "‹123›"}, 139 | {func(w p) { w.Print(Unsafe(SafeFloat(3.14))) }, "‹3.14›"}, 140 | {func(w p) { w.Print(Unsafe(Sprint("abc"))) }, "‹?abc?›"}, 141 | {func(w p) { w.Print(Unsafe(Safe("abc"))) }, "‹abc›"}, 142 | {func(w p) { w.Printf("%v", Unsafe(SafeString("abc"))) }, "‹abc›"}, 143 | {func(w p) { w.Printf("%v", Unsafe(SafeRune('a'))) }, "‹97›"}, 144 | {func(w p) { w.Printf("%v", Unsafe(Sprint("abc"))) }, "‹?abc?›"}, 145 | {func(w p) { w.Printf("%v", Unsafe(Safe("abc"))) }, "‹abc›"}, 146 | {func(w p) { w.Printf("%03d", Unsafe(12)) }, "‹012›"}, 147 | // A string that's already redactable gets included as-is; 148 | // in that case, the printf verb and flags are ignored. 149 | {func(w p) { w.Print("ab ", Sprint(12, Safe(34))) }, "‹ab ›‹12› 34"}, 150 | {func(w p) { w.Printf("ab %q", Sprint(12, Safe(34))) }, "ab ‹12› 34"}, 151 | {func(w p) { w.Printf("ab %d", Sprint(12, Safe(34))) }, "ab ‹12› 34"}, 152 | // Nil untyped or interface-typed objects get formatted as safe. 153 | {func(w p) { w.Printf("ab %v", nil) }, "ab "}, 154 | {func(w p) { w.Printf("ab %v", error(nil)) }, "ab "}, 155 | {func(w p) { w.Printf("ab %v", Safe(nil)) }, "ab "}, 156 | // Nil typed objects are unsafe. 157 | {func(w p) { w.Printf("ab %v", unsafeSptr) }, "ab ‹›"}, 158 | // But a nil pointer to a type that has a SafeFormat() method is fine. 159 | {func(w p) { w.Printf("ab %v", sn) }, "ab hello ‹world›"}, 160 | {func(w p) { w.Printf("ab %v", (*safeNil)(nil)) }, "ab hello ‹world›"}, 161 | // Reflected values can be formatted too. 162 | {func(w p) { w.Printf("ab %.1f", reflect.ValueOf(12.3456)) }, "ab ‹12.3›"}, 163 | {func(w p) { w.Printf("ab %.1f", Safe(reflect.ValueOf(12.3456))) }, "ab 12.3"}, 164 | // Pointers. 165 | {func(w p) { w.Printf("pv %v", ptrptr) }, "pv ‹" + ptrptrV + "›"}, 166 | {func(w p) { w.Printf("pd %d", ptrptr) }, "pd ‹" + ptrptrD + "›"}, 167 | {func(w p) { w.Printf("pp %p", ptrptr) }, "pp ‹" + ptrptrP + "›"}, 168 | {func(w p) { w.Printf("spv %v", Safe(ptrptr)) }, "spv " + ptrptrV}, 169 | {func(w p) { w.Printf("spd %d", Safe(ptrptr)) }, "spd " + ptrptrD}, 170 | {func(w p) { w.Printf("spp %p", Safe(ptrptr)) }, "spp " + ptrptrP}, 171 | {func(w p) { w.Printf("upv %v", Unsafe(ptrptr)) }, "upv ‹" + ptrptrV + "›"}, 172 | {func(w p) { w.Printf("upd %d", Unsafe(ptrptr)) }, "upd ‹" + ptrptrD + "›"}, 173 | {func(w p) { w.Printf("upp %p", Unsafe(ptrptr)) }, "upp ‹" + ptrptrP + "›"}, 174 | 175 | // Check for bad verbs. 176 | {func(w p) { w.Printf("ab %d", true) }, "ab %!d(bool=‹true›)"}, 177 | {func(w p) { w.Printf("ab %d", Safe(true)) }, "ab %!d(bool=true)"}, 178 | {func(w p) { w.Printf("ab %d") }, "ab %!d(MISSING)"}, 179 | {func(w p) { w.Printf("ab %[2]d", 123) }, "ab %!d(BADINDEX)"}, 180 | {func(w p) { w.Printf("ab %.*d", -1, 123) }, "ab %!(BADPREC)‹123›"}, 181 | // A badly formed verb does not leak information. 182 | {func(w p) { w.Printf("ab %2", 123) }, "ab %!(NOVERB)%!(EXTRA int=‹123›)"}, 183 | 184 | // The %T verb does what it says on the label. The type itself is 185 | // considered safe. 186 | {func(w p) { w.Printf("ab %T", 123) }, "ab int"}, 187 | {func(w p) { w.Printf("ab %T", Safe(123)) }, "ab int"}, 188 | {func(w p) { w.Printf("ab %T", Unsafe(123)) }, "ab ‹int›"}, 189 | 190 | // A struct does get recursively redacted. 191 | {func(w p) { w.Print(SafeString("c1"), &complexObj{"somestring"}) }, "c1&{‹somestring›}"}, 192 | {func(w p) { w.Printf("c2 %v", &complexObj{"somestring"}) }, "c2 &{‹somestring›}"}, 193 | {func(w p) { w.Printf("c3 %+v", &complexObj{"somestring"}) }, "c3 &{v:‹somestring›}"}, 194 | {func(w p) { w.Printf("c4 %#v", &complexObj{"somestring"}) }, `c4 &redact.complexObj{v:‹"somestring"›}`}, 195 | {func(w p) { w.Printf("c5 %v", reflect.ValueOf(&complexObj{"somestring"})) }, "c5 &{‹somestring›}"}, 196 | // It can also be marked safe. 197 | {func(w p) { w.Print(SafeString("c6"), Safe(&complexObj{"somestring"})) }, "c6&{somestring}"}, 198 | {func(w p) { w.Printf("c7 %v", Safe(&complexObj{"somestring"})) }, "c7 &{somestring}"}, 199 | {func(w p) { w.Printf("c8 %+v", Safe(&complexObj{"somestring"})) }, "c8 &{v:somestring}"}, 200 | {func(w p) { w.Printf("c9 %#v", Safe(&complexObj{"somestring"})) }, `c9 &redact.complexObj{v:"somestring"}`}, 201 | {func(w p) { w.Printf("c10 %v", Safe(reflect.ValueOf(&complexObj{"somestring"}))) }, `c10 &{somestring}`}, 202 | // String builders are also printable. 203 | {func(w p) { w.Printf("%v", buf) }, "safe ‹unsafe›"}, 204 | {func(w p) { w.Print(buf) }, "safe ‹unsafe›"}, 205 | {func(w p) { w.Printf("%v", &buf) }, "safe ‹unsafe›"}, 206 | {func(w p) { w.Print(&buf) }, "safe ‹unsafe›"}, 207 | } 208 | 209 | var methods = []struct { 210 | name string 211 | fn func(interface{}) string 212 | }{ 213 | {"sprint", func(a interface{}) string { return string(Sprint(a)) }}, 214 | {"sprintf", func(a interface{}) string { return string(Sprintf("%v", a)) }}, 215 | {"fprint", func(a interface{}) string { var b strings.Builder; _, _ = Fprint(&b, a); return b.String() }}, 216 | {"fprintf", func(a interface{}) string { var b strings.Builder; _, _ = Fprintf(&b, "%v", a); return b.String() }}, 217 | } 218 | 219 | for _, m := range methods { 220 | t.Run(m.name, func(t *testing.T) { 221 | for i, tc := range testData { 222 | t.Run(fmt.Sprintf("%d:%q", i, tc.expected), func(t *testing.T) { 223 | res := m.fn(compose{fn: tc.fn}) 224 | 225 | if res != tc.expected { 226 | t.Errorf("%d: expected:\n %s\n\ngot:\n%s", i, 227 | strings.ReplaceAll(tc.expected, "\n", "\n "), 228 | strings.ReplaceAll(res, "\n", "\n ")) 229 | } 230 | }) 231 | } 232 | }) 233 | } 234 | } 235 | 236 | func TestConversions(t *testing.T) { 237 | const data = `‹123› 456` 238 | s := RedactableString(data) 239 | 240 | bconv := s.ToBytes() 241 | expected := []byte(data) 242 | if !bytes.Equal(bconv, expected) { 243 | t.Errorf("\nexpected: %+v,\n got: %+v", expected, bconv) 244 | } 245 | 246 | sconv := bconv.ToString() 247 | if s != sconv { 248 | t.Errorf("expected %q, got %q", s, sconv) 249 | } 250 | } 251 | 252 | func TestFormatPropagation(t *testing.T) { 253 | testData := []struct { 254 | actual RedactableString 255 | expected RedactableString 256 | }{ 257 | {Sprintf(":%10s:", safe("abc")), `: abc:`}, 258 | {Sprintf(":%10s:", "abc"), `:‹ abc›:`}, 259 | {Sprintf(":%+#03x:", safeint(123)), `:+0x7b:`}, 260 | {Sprintf(":%+#03x:", 123), `:‹+0x7b›:`}, 261 | } 262 | 263 | for _, tc := range testData { 264 | if tc.actual != tc.expected { 265 | t.Errorf("expected %q, got %q", tc.expected, tc.actual) 266 | } 267 | } 268 | } 269 | 270 | type compose struct { 271 | fn func(p) 272 | } 273 | 274 | func (c compose) SafeFormat(w SafePrinter, _ rune) { 275 | c.fn(w) 276 | } 277 | 278 | type safe string 279 | 280 | func (safe) SafeValue() {} 281 | 282 | type safeint int 283 | 284 | func (safeint) SafeValue() {} 285 | 286 | func TestTransform(t *testing.T) { 287 | testData := []struct { 288 | actual string 289 | expected string 290 | }{ 291 | {string(StartMarker()), `‹`}, 292 | {string(EndMarker()), `›`}, 293 | {string(RedactedMarker()), `‹×›`}, 294 | {string(EscapeMarkers([]byte(`a ‹ b › c`))), `a ? b ? c`}, 295 | {string(RedactableBytes([]byte(`a ‹ b › c`)).Redact()), `a ‹×› c`}, 296 | {string(RedactableBytes([]byte(`a ‹ b › c`)).StripMarkers()), `a b c`}, 297 | {string(RedactableString(`a ‹ b › c`).Redact()), `a ‹×› c`}, 298 | {RedactableString(`a ‹ b › c`).StripMarkers(), `a b c`}, 299 | } 300 | 301 | for _, tc := range testData { 302 | if tc.actual != tc.expected { 303 | t.Errorf("expected %q, got %q", tc.expected, tc.actual) 304 | } 305 | } 306 | } 307 | 308 | // TestRedactStream verifies that the redaction logic is able to both 309 | // add the redaction quotes and also respects the format parameters 310 | // and verb. 311 | func TestRedactStream(t *testing.T) { 312 | testData := []struct { 313 | f string 314 | input interface{} 315 | expected string 316 | }{ 317 | {"%v", "", ""}, 318 | {"%v", " ", "‹ ›"}, 319 | {"‹› %v ›››", "abc", "?? ‹abc› ???"}, 320 | {"%v", "abc ", "‹abc ›"}, 321 | {"%q", "abc ", `‹"abc "›`}, 322 | {"%v", "abc\n ", "‹abc›\n‹ ›"}, 323 | {"%v", "abc \n\n", "‹abc ›\n\n"}, 324 | {"%v", " \n\nabc", "‹ ›\n\n‹abc›"}, 325 | {"%v", "‹abc›", "‹?abc?›"}, 326 | {"%v", 123, "‹123›"}, 327 | {"%05d", 123, "‹00123›"}, 328 | {"%v", Safe(123), "123"}, 329 | {"%05d", Safe(123), "00123"}, 330 | {"%#x", 17, "‹0x11›"}, 331 | {"%+v", &complexObj{"‹›"}, "&{v:‹??›}"}, 332 | {"%v", &safestringer{"as"}, "as"}, 333 | {"%v", &stringer{"as"}, "‹as›"}, 334 | {"%v", &safefmtformatter{"af"}, "af"}, 335 | {"%v", &fmtformatter{"af"}, "‹af›"}, 336 | {"%v", &safemsg{"az"}, "az"}, 337 | // Printers that cause panics during rendering. 338 | {"%v", &safepanicObj1{"s1-x‹y›z"}, `%!v(PANIC=String method: s1-x?y?z)`}, 339 | {"%v", &safepanicObj2{"s2-x‹y›z"}, `%!v(PANIC=Format method: s2-x?y?z)`}, 340 | {"%v", &panicObj1{"p1-x‹y›z"}, `%!v(PANIC=String method: ‹p1-x?y?z›)`}, 341 | {"%v", &panicObj2{"p2-x‹y›z"}, `%!v(PANIC=Format method: ‹p2-x?y?z›)`}, 342 | {"%v", &panicObj3{"p3-x‹y›z"}, `%!v(PANIC=SafeFormat method: ‹p3-x?y?z›)`}, 343 | {"%v", &panicObj4{"unused"}, `%!v(PANIC=SafeMessager method: ‹woo›)`}, 344 | {"%v", (*safestringer)(nil), ``}, 345 | {"%v", (*safemsg)(nil), ``}, 346 | {"%v", (*safefmtformatter)(nil), ``}, 347 | {"%v", (*safepanicObj1)(nil), ``}, 348 | {"%v", (*safepanicObj2)(nil), ``}, 349 | {"%v", (*panicObj1)(nil), ``}, 350 | {"%v", (*panicObj2)(nil), ``}, 351 | {"%v", (*panicObj3)(nil), ``}, 352 | {"%v", (*panicObj4)(nil), ``}, 353 | } 354 | 355 | for i, tc := range testData { 356 | var buf strings.Builder 357 | n, _ := Fprintf(&buf, tc.f, tc.input) 358 | result := buf.String() 359 | if result != tc.expected { 360 | t.Errorf("%d: expected %q, got %q", i, tc.expected, result) 361 | } 362 | if n != len(result) { 363 | t.Errorf("%d: expected len %d, got %d", i, n, len(result)) 364 | } 365 | } 366 | } 367 | 368 | func Example_format() { 369 | testCases := []struct { 370 | format string 371 | args []interface{} 372 | }{ 373 | {"%d", []interface{}{123}}, 374 | {"%v", []interface{}{[]int{123, 456}}}, 375 | {"%v", []interface{}{[]RedactableString{"safe", "‹unsafe›"}}}, 376 | {"%v", []interface{}{[]safe{"safe", "safe2"}}}, 377 | {"%v", []interface{}{[]safestringer{{"safe"}, {"safe2"}}}}, 378 | {"%v", []interface{}{makeMixedInts()}}, 379 | {"%+v", []interface{}{makeMixedInts()}}, 380 | {"%v", []interface{}{[]mixedInts{makeMixedInts(), makeMixedInts()}}}, 381 | {"%+v", []interface{}{makeMixedSafe()}}, 382 | {"%+v", []interface{}{makeMixedSafeStringer()}}, 383 | {"%+v", []interface{}{makeMixedRedactableString()}}, 384 | {"%+v", []interface{}{makeMixedRedactableBytes()}}, 385 | } 386 | 387 | type formatterFn func(format string, args ...interface{}) string 388 | formatters := []struct { 389 | name string 390 | fn formatterFn 391 | }{ 392 | {"fmt.sprint", fmt.Sprintf}, 393 | {"redact.sprint", func(format string, args ...interface{}) string { return string(Sprintf(format, args...)) }}, 394 | {"redact.printer", func(format string, args ...interface{}) string { 395 | fn := func(w p) { w.Printf(format, args...) } 396 | return string(Sprint(compose{fn: fn})) 397 | }}, 398 | } 399 | 400 | for _, tc := range testCases { 401 | base := fmt.Sprintf("%q :: %T :: %+v", tc.format, tc.args[0], tc.args) 402 | base = ptrRe.ReplaceAllString(base, "℘") 403 | fmt.Println(base) 404 | for _, f := range formatters { 405 | result := f.fn(tc.format, tc.args...) 406 | // Erase pointers. 407 | result = ptrRe.ReplaceAllString(result, "℘") 408 | fmt.Printf("%s:\t%s\n", f.name, result) 409 | } 410 | fmt.Println() 411 | } 412 | 413 | // Output: 414 | // "%d" :: int :: [123] 415 | // fmt.sprint: 123 416 | // redact.sprint: ‹123› 417 | // redact.printer: ‹123› 418 | // 419 | // "%v" :: []int :: [[123 456]] 420 | // fmt.sprint: [123 456] 421 | // redact.sprint: [‹123› ‹456›] 422 | // redact.printer: [‹123› ‹456›] 423 | // 424 | // "%v" :: []markers.RedactableString :: [[safe ‹unsafe›]] 425 | // fmt.sprint: [safe ‹unsafe›] 426 | // redact.sprint: [safe ‹unsafe›] 427 | // redact.printer: [safe ‹unsafe›] 428 | // 429 | // "%v" :: []redact.safe :: [[safe safe2]] 430 | // fmt.sprint: [safe safe2] 431 | // redact.sprint: [safe safe2] 432 | // redact.printer: [safe safe2] 433 | // 434 | // "%v" :: []redact.safestringer :: [[{s:safe} {s:safe2}]] 435 | // fmt.sprint: [{safe} {safe2}] 436 | // redact.sprint: [{‹safe›} {‹safe2›}] 437 | // redact.printer: [{‹safe›} {‹safe2›}] 438 | // 439 | // "%v" :: redact.mixedInts :: [{a:123 ap:℘ A:123 Ap:℘ Apn:}] 440 | // fmt.sprint: {123 ℘ 123 ℘ } 441 | // redact.sprint: {‹123› ‹℘› ‹123› ‹℘› ‹›} 442 | // redact.printer: {‹123› ‹℘› ‹123› ‹℘› ‹›} 443 | // 444 | // "%+v" :: redact.mixedInts :: [{a:123 ap:℘ A:123 Ap:℘ Apn:}] 445 | // fmt.sprint: {a:123 ap:℘ A:123 Ap:℘ Apn:} 446 | // redact.sprint: {a:‹123› ap:‹℘› A:‹123› Ap:‹℘› Apn:‹›} 447 | // redact.printer: {a:‹123› ap:‹℘› A:‹123› Ap:‹℘› Apn:‹›} 448 | // 449 | // "%v" :: []redact.mixedInts :: [[{a:123 ap:℘ A:123 Ap:℘ Apn:} {a:123 ap:℘ A:123 Ap:℘ Apn:}]] 450 | // fmt.sprint: [{123 ℘ 123 ℘ } {123 ℘ 123 ℘ }] 451 | // redact.sprint: [{‹123› ‹℘› ‹123› ‹℘› ‹›} {‹123› ‹℘› ‹123› ‹℘› ‹›}] 452 | // redact.printer: [{‹123› ‹℘› ‹123› ‹℘› ‹›} {‹123› ‹℘› ‹123› ‹℘› ‹›}] 453 | // 454 | // "%+v" :: redact.mixedSafe :: [{s1:safe S1:safe s1p:℘ S1p:℘ S1pn:}] 455 | // fmt.sprint: {s1:safe S1:safe s1p:℘ S1p:℘ S1pn:} 456 | // redact.sprint: {s1:‹safe› S1:safe s1p:‹℘› S1p:℘ S1pn:} 457 | // redact.printer: {s1:‹safe› S1:safe s1p:‹℘› S1p:℘ S1pn:} 458 | // 459 | // "%+v" :: redact.mixedSafeStringer :: [{s2:{s:safe} s2p:℘ S2:{s:safe} S2p:safe S2pn:}] 460 | // fmt.sprint: {s2:{s:safe} s2p:℘ S2:{s:safe} S2p:safe S2pn:} 461 | // redact.sprint: {s2:{s:‹safe›} s2p:‹℘› S2:{s:‹safe›} S2p:safe S2pn:} 462 | // redact.printer: {s2:{s:‹safe›} s2p:‹℘› S2:{s:‹safe›} S2p:safe S2pn:} 463 | // 464 | // "%+v" :: redact.mixedRedactableString :: [{r:safe‹unsafe› rp:℘ R:safe‹unsafe› Rp:℘ Rpn:}] 465 | // fmt.sprint: {r:safe‹unsafe› rp:℘ R:safe‹unsafe› Rp:℘ Rpn:} 466 | // redact.sprint: {r:safe‹unsafe› rp:‹℘› R:safe‹unsafe› Rp:safe‹unsafe› Rpn:} 467 | // redact.printer: {r:safe‹unsafe› rp:‹℘› R:safe‹unsafe› Rp:safe‹unsafe› Rpn:} 468 | // 469 | // "%+v" :: redact.mixedRedactableBytes :: [{r:[115 97 102 101 226 128 185 117 110 115 97 102 101 226 128 186] rp:℘ R:[115 97 102 101 226 128 185 117 110 115 97 102 101 226 128 186] Rp:℘ Rpn:}] 470 | // fmt.sprint: {r:[115 97 102 101 226 128 185 117 110 115 97 102 101 226 128 186] rp:℘ R:[115 97 102 101 226 128 185 117 110 115 97 102 101 226 128 186] Rp:℘ Rpn:} 471 | // redact.sprint: {r:safe‹unsafe› rp:‹℘› R:safe‹unsafe› Rp:safe‹unsafe› Rpn:} 472 | // redact.printer: {r:safe‹unsafe› rp:‹℘› R:safe‹unsafe› Rp:safe‹unsafe› Rpn:} 473 | } 474 | 475 | var ptrRe = regexp.MustCompile(`0x[0-9a-f]{4,16}`) 476 | 477 | func TestEscapeBytes(t *testing.T) { 478 | testCases := []struct { 479 | input string 480 | expected string 481 | }{ 482 | {"", "‹›"}, 483 | {" ", "‹ ›"}, 484 | {"abc", "‹abc›"}, 485 | {"ab›‹c", "‹ab??c›"}, 486 | {"abc\n\ncde", "‹abc›\n\n‹cde›"}, 487 | {"\n abc ", "\n‹ abc ›"}, 488 | } 489 | 490 | for _, tc := range testCases { 491 | input := []byte(tc.input) 492 | actual := EscapeBytes(input) 493 | actualS := string(actual) 494 | if actualS != tc.expected { 495 | t.Errorf("%q: expected %q, got %q", tc.input, tc.expected, actualS) 496 | } 497 | } 498 | } 499 | 500 | func TestPrinterSpaceOmission(t *testing.T) { 501 | // This test catches a regression introduced in v1.1.0. 502 | vals := []safeInt{1, 2, 3, 4, 5} 503 | s := Sprintfn(func(w SafePrinter) { 504 | t.Logf("printer type %T", w) 505 | for i, v := range vals { 506 | if i > 0 { 507 | w.SafeRune(' ') 508 | } 509 | if v%2 == 0 { 510 | w.SafeRune('e') 511 | } else { 512 | w.SafeRune('o') 513 | } 514 | w.Print(v) 515 | } 516 | }) 517 | const expected = `o1 e2 o3 e4 o5` 518 | if s != expected { 519 | t.Errorf("expected %q, got %q", expected, s) 520 | } 521 | } 522 | 523 | type safeInt int 524 | 525 | func (safeInt) SafeValue() {} 526 | 527 | func TestHelperForErrorf(t *testing.T) { 528 | origErr := errors.New("small\nuniverse") 529 | s, e := HelperForErrorf("hello %s", origErr) 530 | if actual, expected := string(s), "hello ‹small›\n‹universe›"; actual != expected { 531 | t.Errorf("expected %q, got %q", expected, actual) 532 | } 533 | if e != nil { 534 | t.Errorf("expected no error, got %v", e) 535 | } 536 | 537 | s, e = HelperForErrorf("hello %w", origErr) 538 | if actual, expected := string(s), "hello ‹small›\n‹universe›"; actual != expected { 539 | t.Errorf("expected %q, got %q", expected, actual) 540 | } 541 | if e != origErr { 542 | t.Errorf("expected error %v, got %v (%T)", origErr, e, e) 543 | } 544 | } 545 | 546 | type complexObj struct { 547 | v string 548 | } 549 | 550 | type stringer struct{ s string } 551 | 552 | var _ fmt.Stringer = (*stringer)(nil) 553 | 554 | func (s *stringer) String() string { return s.s } 555 | 556 | type safestringer struct{ s string } 557 | 558 | var _ SafeValue = (*safestringer)(nil) 559 | var _ fmt.Stringer = (*safestringer)(nil) 560 | 561 | func (*safestringer) SafeValue() {} 562 | func (s *safestringer) String() string { return s.s } 563 | 564 | type fmtformatter struct{ s string } 565 | 566 | var _ fmt.Formatter = (*fmtformatter)(nil) 567 | 568 | func (s *fmtformatter) Format(w fmt.State, _ rune) { fmt.Fprint(w, s.s) } 569 | 570 | type safefmtformatter struct{ s string } 571 | 572 | var _ SafeValue = (*safefmtformatter)(nil) 573 | var _ fmt.Formatter = (*safefmtformatter)(nil) 574 | 575 | func (*safefmtformatter) SafeValue() {} 576 | func (s *safefmtformatter) Format(w fmt.State, _ rune) { fmt.Fprint(w, s.s) } 577 | 578 | type panicObj1 struct{ s string } 579 | 580 | var _ fmt.Stringer = (*panicObj1)(nil) 581 | 582 | func (p *panicObj1) String() string { panic(p.s) } 583 | 584 | type panicObj2 struct{ s string } 585 | 586 | var _ fmt.Formatter = (*panicObj2)(nil) 587 | 588 | func (p *panicObj2) Format(fmt.State, rune) { panic(p.s) } 589 | 590 | type safepanicObj1 struct{ s string } 591 | 592 | var _ SafeValue = (*safepanicObj1)(nil) 593 | var _ fmt.Stringer = (*safepanicObj1)(nil) 594 | 595 | func (*safepanicObj1) SafeValue() {} 596 | func (p *safepanicObj1) String() string { panic(p.s) } 597 | 598 | type safepanicObj2 struct{ s string } 599 | 600 | var _ SafeValue = (*safepanicObj2)(nil) 601 | var _ fmt.Formatter = (*safepanicObj2)(nil) 602 | 603 | func (*safepanicObj2) SafeValue() {} 604 | func (p *safepanicObj2) Format(fmt.State, rune) { panic(p.s) } 605 | 606 | type panicObj3 struct{ s string } 607 | 608 | var _ SafeFormatter = (*panicObj3)(nil) 609 | 610 | func (p *panicObj3) SafeFormat(SafePrinter, rune) { panic(p.s) } 611 | 612 | type safemsg struct { 613 | s string 614 | } 615 | 616 | type panicObj4 struct{ unused string } 617 | 618 | var _ SafeMessager = (*panicObj4)(nil) 619 | 620 | func (p *panicObj4) SafeMessage() string { panic("woo") } 621 | 622 | var _ SafeMessager = (*safemsg)(nil) 623 | 624 | func (p *safemsg) SafeMessage() string { return p.s } 625 | 626 | // TestFormatRedirect checks that the count return from the 627 | // (*escapeWriter).Write() method is correct when there are newline 628 | // characters. 629 | func TestFormatRedirect(t *testing.T) { 630 | v := &fmter{} 631 | if expected, actual := "‹hello›\n‹world›", string(Sprintf("%v", v)); expected != actual { 632 | t.Errorf("expected %q, got %q", expected, actual) 633 | } 634 | if expected, actual := "‹hello›\n‹world›", string(Sprintf("%+v", v)); expected != actual { 635 | t.Errorf("expected %q, got %q", expected, actual) 636 | } 637 | } 638 | 639 | type fmter struct{} 640 | 641 | // Format implements the fmt.Formatter interface. 642 | func (ef *fmter) Format(s fmt.State, verb rune) { 643 | var buf bytes.Buffer 644 | buf.WriteString("hello\nworld") 645 | _, _ = buf.WriteTo(s) 646 | } 647 | 648 | type safeNil struct { 649 | unused int 650 | } 651 | 652 | func (s *safeNil) SafeFormat(p SafePrinter, _ rune) { 653 | p.Printf("hello %v", "world") 654 | } 655 | 656 | type mixedInts struct { 657 | a int 658 | ap *int 659 | A int 660 | Ap *int 661 | Apn *int 662 | } 663 | 664 | type mixedSafe struct { 665 | s1 safe 666 | S1 safe 667 | s1p *safe 668 | S1p *safe 669 | S1pn *safe 670 | } 671 | 672 | type mixedSafeStringer struct { 673 | s2 safestringer 674 | s2p *safestringer 675 | S2 safestringer 676 | S2p *safestringer 677 | S2pn *safestringer 678 | } 679 | 680 | type mixedRedactableString struct { 681 | r RedactableString 682 | rp *RedactableString 683 | R RedactableString 684 | Rp *RedactableString 685 | Rpn *RedactableString 686 | } 687 | 688 | type mixedRedactableBytes struct { 689 | r RedactableBytes 690 | rp *RedactableBytes 691 | R RedactableBytes 692 | Rp *RedactableBytes 693 | Rpn *RedactableBytes 694 | } 695 | 696 | func makeMixedInts() mixedInts { 697 | i := 123 698 | return mixedInts{ 699 | a: i, 700 | ap: &i, 701 | A: i, 702 | Ap: &i, 703 | } 704 | } 705 | func makeMixedSafe() mixedSafe { 706 | s := safe("safe") 707 | return mixedSafe{ 708 | s1: s, 709 | s1p: &s, 710 | S1: s, 711 | S1p: &s, 712 | } 713 | } 714 | func makeMixedSafeStringer() mixedSafeStringer { 715 | ss := safestringer{"safe"} 716 | return mixedSafeStringer{ 717 | s2: ss, 718 | s2p: &ss, 719 | S2: ss, 720 | S2p: &ss, 721 | } 722 | } 723 | func makeMixedRedactableString() mixedRedactableString { 724 | r := RedactableString("safe‹unsafe›") 725 | return mixedRedactableString{ 726 | r: r, 727 | rp: &r, 728 | R: r, 729 | Rp: &r, 730 | } 731 | } 732 | 733 | func makeMixedRedactableBytes() mixedRedactableBytes { 734 | r := RedactableBytes("safe‹unsafe›") 735 | return mixedRedactableBytes{ 736 | r: r, 737 | rp: &r, 738 | R: r, 739 | Rp: &r, 740 | } 741 | } 742 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | "reflect" 19 | "sort" 20 | 21 | "github.com/cockroachdb/redact/builder" 22 | ) 23 | 24 | // JoinTo writes the given slice of values delimited by the provided 25 | // delimiter to the given SafeWriter. 26 | func JoinTo(w SafeWriter, delim RedactableString, values interface{}) { 27 | // Ideally, we'd make the interface clearer: JoinTo is a generic function, 28 | // with a "values" argument of type []T where T is a type variable. 29 | v := reflect.ValueOf(values) 30 | if v.Kind() != reflect.Slice { 31 | // Note a slice: just print the value as-is. 32 | w.Print(values) 33 | } 34 | for i, l := 0, v.Len(); i < l; i++ { 35 | if i > 0 { 36 | w.Print(delim) 37 | } 38 | w.Print(v.Index(i).Interface()) 39 | } 40 | } 41 | 42 | // Join creates a redactable string from the 43 | // given slice of redactable strings, adjoined 44 | // with the provided delimiter. 45 | func Join(delim RedactableString, s []RedactableString) RedactableString { 46 | var b builder.StringBuilder 47 | JoinTo(&b, delim, s) 48 | return b.RedactableString() 49 | } 50 | 51 | // SortStrings sorts the provided slice of redactable strings. 52 | func SortStrings(s []RedactableString) { 53 | sort.Sort(sortableSlice(s)) 54 | } 55 | 56 | // sortableSlice attaches the methods of sort.Interface to 57 | // []RedactableString, sorting in increasing order. 58 | type sortableSlice []RedactableString 59 | 60 | func (p sortableSlice) Len() int { return len(p) } 61 | func (p sortableSlice) Less(i, j int) bool { return p[i] < p[j] } 62 | func (p sortableSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 63 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Cockroach Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | // implied. See the License for the specific language governing 13 | // permissions and limitations under the License. 14 | 15 | package redact 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | 21 | "github.com/cockroachdb/redact/builder" 22 | ) 23 | 24 | func TestSort(t *testing.T) { 25 | v := []RedactableString{"c", "a", "b"} 26 | SortStrings(v) 27 | exp := []RedactableString{"a", "b", "c"} 28 | if !reflect.DeepEqual(v, exp) { 29 | t.Errorf("expected %+v, got %+v", exp, v) 30 | } 31 | } 32 | 33 | func TestJoin(t *testing.T) { 34 | v := []RedactableString{"c", "a", "b"} 35 | exp := RedactableString("c, a, b") 36 | act := Join(", ", v) 37 | if exp != act { 38 | t.Errorf("expected %q, got %q", exp, act) 39 | } 40 | } 41 | 42 | func TestJoinTo(t *testing.T) { 43 | testCases := []struct { 44 | v interface{} 45 | exp RedactableString 46 | }{ 47 | {[]int{1, 2, 3}, `‹1›, ‹2›, ‹3›`}, 48 | {[]string{"unsafe", "wo›rld"}, `‹unsafe›, ‹wo?rld›`}, 49 | {[]RedactableString{"a", "‹b›", "c"}, `a, ‹b›, c`}, 50 | {[]SafeString{"a", "b", "c"}, `a, b, c`}, 51 | } 52 | 53 | for _, tc := range testCases { 54 | var b builder.StringBuilder 55 | JoinTo(&b, ", ", tc.v) 56 | act := b.RedactableString() 57 | if act != tc.exp { 58 | t.Errorf("expected %q, got %q", tc.exp, act) 59 | } 60 | } 61 | } 62 | --------------------------------------------------------------------------------