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 }