github.com/PurpleSec/escape@v1.0.0/escape.go (about) 1 // Copyright 2021 - 2022 PurpleSec Team 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 implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 16 package escape 17 18 import ( 19 "strings" 20 "sync" 21 "unicode/utf8" 22 23 // Needed to include json vars 24 _ "encoding/json" 25 // Needed to use "go:linkname" 26 _ "unsafe" 27 ) 28 29 const hex = "0123456789abcdef" 30 31 var buf = sync.Pool{ 32 New: func() interface{} { 33 return new(strings.Builder) 34 }, 35 } 36 37 //go:linkname htmlSafeSet encoding/json.safeSet 38 var htmlSafeSet [utf8.RuneSelf]bool 39 40 // JSON will escape the string provided into a JSON-like format, respecting all escaping. This will return 41 // the string value already in quotes. Acts like (and copied from) 'json.Marshal' on a string value. 42 func JSON(s string) string { 43 if len(s) == 0 { 44 return `""` 45 } 46 e := buf.Get().(*strings.Builder) 47 e.Grow(2 + len(s)) 48 e.WriteByte('"') 49 start := 0 50 for i := 0; i < len(s); { 51 if b := s[i]; b < utf8.RuneSelf { 52 if htmlSafeSet[b] { 53 i++ 54 continue 55 } 56 if start < i { 57 e.WriteString(s[start:i]) 58 } 59 e.WriteByte('\\') 60 switch b { 61 case '\\', '"': 62 e.WriteByte(b) 63 case '\n': 64 e.WriteByte('n') 65 case '\r': 66 e.WriteByte('r') 67 case '\t': 68 e.WriteByte('t') 69 default: 70 e.WriteString(`u00`) 71 e.WriteByte(hex[b>>4]) 72 e.WriteByte(hex[b&0xF]) 73 } 74 i++ 75 start = i 76 continue 77 } 78 c, size := utf8.DecodeRuneInString(s[i:]) 79 if c == utf8.RuneError && size == 1 { 80 if start < i { 81 e.WriteString(s[start:i]) 82 } 83 e.WriteString(`\ufffd`) 84 i += size 85 start = i 86 continue 87 } 88 if c == '\u2028' || c == '\u2029' { 89 if start < i { 90 e.WriteString(s[start:i]) 91 } 92 e.WriteString(`\u202`) 93 e.WriteByte(hex[c&0xF]) 94 i += size 95 start = i 96 continue 97 } 98 i += size 99 } 100 if start < len(s) { 101 e.WriteString(s[start:]) 102 } 103 e.WriteByte('"') 104 r := e.String() 105 e.Reset() 106 buf.Put(e) 107 return r 108 }