// MinIO Cloud Storage, (C) 2020 MinIO, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package crypto import ( "bytes" "unicode/utf8" ) // Adapted from Go stdlib. var hexTable = "0123456789abcdef" // EscapeStringJSON will escape a string for JSON and write it to dst. func EscapeStringJSON(dst *bytes.Buffer, s string) { start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { if htmlSafeSet[b] { i++ continue } if start < i { dst.WriteString(s[start:i]) } dst.WriteByte('\\') switch b { case '\\', '"': dst.WriteByte(b) case '\n': dst.WriteByte('n') case '\r': dst.WriteByte('r') case '\t': dst.WriteByte('t') default: // This encodes bytes < 0x20 except for \t, \n and \r. // If escapeHTML is set, it also escapes <, >, and & // because they can lead to security holes when // user-controlled strings are rendered into JSON // and served to some browsers. dst.WriteString(`u00`) dst.WriteByte(hexTable[b>>4]) dst.WriteByte(hexTable[b&0xF]) } i++ start = i continue } c, size := utf8.DecodeRuneInString(s[i:]) if c == utf8.RuneError && size == 1 { if start < i { dst.WriteString(s[start:i]) } dst.WriteString(`\ufffd`) i += size start = i continue } // U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. if c == '\u2028' || c == '\u2029' { if start < i { dst.WriteString(s[start:i]) } dst.WriteString(`\u202`) dst.WriteByte(hexTable[c&0xF]) i += size start = i continue } i += size } if start < len(s) { dst.WriteString(s[start:]) } } // htmlSafeSet holds the value true if the ASCII character with the given // array position can be safely represented inside a JSON string, embedded // inside of HTML <script> tags, without any additional escaping. // // All values are true except for the ASCII control characters (0-31), the // double quote ("), the backslash character ("\"), HTML opening and closing // tags ("<" and ">"), and the ampersand ("&"). var htmlSafeSet = [utf8.RuneSelf]bool{ ' ': true, '!': true, '"': false, '#': true, '$': true, '%': true, '&': false, '\'': true, '(': true, ')': true, '*': true, '+': true, ',': true, '-': true, '.': true, '/': true, '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, ':': true, ';': true, '<': false, '=': true, '>': false, '?': true, '@': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true, '[': true, '\\': false, ']': true, '^': true, '_': true, '`': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '{': true, '|': true, '}': true, '~': true, '\u007f': true, }