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  }