kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/kytheuri/escape.go (about)

     1  /*
     2   * Copyright 2014 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package kytheuri
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"strings"
    23  )
    24  
    25  type escaper int
    26  
    27  const (
    28  	all escaper = iota
    29  	paths
    30  )
    31  
    32  // shouldEscape reports whether c should be %-escaped for inclusion in a Kythe
    33  // URI string.
    34  func (e escaper) shouldEscape(c byte) bool {
    35  	if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
    36  		return false // unreserved: ALPHA, DIGIT
    37  	}
    38  	switch c {
    39  	case '-', '.', '_', '~':
    40  		return false // unreserved: punctuation
    41  	case '/':
    42  		return e != paths
    43  	}
    44  	return true
    45  }
    46  
    47  const hexDigits = "0123456789ABCDEF"
    48  
    49  // escape encodes a string for use in a Kythe URI, %-escaping values as needed.
    50  func (e escaper) escape(s string) string {
    51  	var numEsc int
    52  	for i := 0; i < len(s); i++ {
    53  		if e.shouldEscape(s[i]) {
    54  			numEsc++
    55  		}
    56  	}
    57  	if numEsc == 0 {
    58  		return s
    59  	}
    60  	b := bytes.NewBuffer(make([]byte, 0, len(s)+2*numEsc))
    61  	for i := 0; i < len(s); i++ {
    62  		if c := s[i]; e.shouldEscape(c) {
    63  			b.WriteByte('%')
    64  			b.WriteByte(hexDigits[c>>4])
    65  			b.WriteByte(hexDigits[c%16])
    66  		} else {
    67  			b.WriteByte(c)
    68  		}
    69  	}
    70  	return b.String()
    71  }
    72  
    73  // dehex returns the digit value of the hex digit encoded by c, or -1 if c does
    74  // not denote a hex digit.
    75  func dehex(c byte) int {
    76  	switch {
    77  	case 'a' <= c && c <= 'f':
    78  		return int(c - 'a' + 10)
    79  	case 'A' <= c && c <= 'F':
    80  		return int(c - 'A' + 10)
    81  	case '0' <= c && c <= '9':
    82  		return int(c - '0')
    83  	}
    84  	return -1
    85  }
    86  
    87  // unescape unencodes a %-escaped string using buf as a temporary buffer,
    88  // replacing *v with the result on success. Requires len(buf) >= len(*v).
    89  func unescape(v *string, buf []byte) error {
    90  	s := *v
    91  	if strings.IndexByte(s, '%') < 0 {
    92  		return nil // nothing to do
    93  	}
    94  
    95  	pos := 0
    96  	for i := 0; i < len(s); i++ {
    97  		if c := s[i]; c != '%' {
    98  			buf[pos] = c
    99  			pos++
   100  			continue
   101  		}
   102  		if i+2 >= len(s) {
   103  			return errors.New("invalid hex escape")
   104  		}
   105  		hi := dehex(s[i+1])
   106  		lo := dehex(s[i+2])
   107  		if hi < 0 || lo < 0 {
   108  			return errors.New("invalid hex digit")
   109  		}
   110  		buf[pos] = byte(hi<<4 | lo)
   111  		pos++
   112  		i += 2 // skip the extra bytes from the escape
   113  	}
   114  	*v = string(buf[:pos])
   115  	return nil
   116  }