github.com/cloudwego/hertz@v0.9.3/pkg/common/utils/path.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 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 * The MIT License (MIT) 16 * 17 * Copyright (c) 2014 Manuel MartÃnez-Almeida 18 * 19 * Permission is hereby granted, free of charge, to any person obtaining a copy 20 * of this software and associated documentation files (the "Software"), to deal 21 * in the Software without restriction, including without limitation the rights 22 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 * copies of the Software, and to permit persons to whom the Software is 24 * furnished to do so, subject to the following conditions: 25 * 26 * The above copyright notice and this permission notice shall be included in 27 * all copies or substantial portions of the Software. 28 * 29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 * THE SOFTWARE. 36 * 37 * This file may have been modified by CloudWeGo authors. All CloudWeGo 38 * Modifications are Copyright 2022 CloudWeGo Authors 39 */ 40 41 package utils 42 43 import "strings" 44 45 // CleanPath is the URL version of path.Clean, it returns a canonical URL path 46 // for p, eliminating . and .. elements. 47 // 48 // The following rules are applied iteratively until no further processing can 49 // be done: 50 // 1. Replace multiple slashes with a single slash. 51 // 2. Eliminate each . path name element (the current directory). 52 // 3. Eliminate each inner .. path name element (the parent directory) 53 // along with the non-.. element that precedes it. 54 // 4. Eliminate .. elements that begin a rooted path: 55 // that is, replace "/.." by "/" at the beginning of a path. 56 // 57 // If the result of this process is an empty string, "/" is returned 58 func CleanPath(p string) string { 59 const stackBufSize = 128 60 61 // Turn empty string into "/" 62 if p == "" { 63 return "/" 64 } 65 66 // Reasonably sized buffer on stack to avoid allocations in the common case. 67 // If a larger buffer is required, it gets allocated dynamically. 68 buf := make([]byte, 0, stackBufSize) 69 70 n := len(p) 71 72 // Invariants: 73 // reading from path; r is index of next byte to process. 74 // writing to buf; w is index of next byte to write. 75 76 // path must start with '/' 77 r := 1 78 w := 1 79 80 if p[0] != '/' { 81 r = 0 82 83 if n+1 > stackBufSize { 84 buf = make([]byte, n+1) 85 } else { 86 buf = buf[:n+1] 87 } 88 buf[0] = '/' 89 } 90 91 trailing := n > 1 && p[n-1] == '/' 92 93 // A bit more clunky without a 'lazybuf' like the path package, but the loop 94 // gets completely inlined (bufApp calls). 95 // So in contrast to the path package this loop has no expensive function 96 // calls (except make, if needed). 97 98 for r < n { 99 switch { 100 case p[r] == '/': 101 // empty path element, trailing slash is added after the end 102 r++ 103 104 case p[r] == '.' && r+1 == n: 105 trailing = true 106 r++ 107 108 case p[r] == '.' && p[r+1] == '/': 109 // . element 110 r += 2 111 112 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 113 // .. element: remove to last / 114 r += 3 115 116 if w > 1 { 117 // can backtrack 118 w-- 119 120 if len(buf) == 0 { 121 for w > 1 && p[w] != '/' { 122 w-- 123 } 124 } else { 125 for w > 1 && buf[w] != '/' { 126 w-- 127 } 128 } 129 } 130 131 default: 132 // Real path element. 133 // Add slash if needed 134 if w > 1 { 135 bufApp(&buf, p, w, '/') 136 w++ 137 } 138 139 // Copy element 140 for r < n && p[r] != '/' { 141 bufApp(&buf, p, w, p[r]) 142 w++ 143 r++ 144 } 145 } 146 } 147 148 // Re-append trailing slash 149 if trailing && w > 1 { 150 bufApp(&buf, p, w, '/') 151 w++ 152 } 153 154 // If the original string was not modified (or only shortened at the end), 155 // return the respective substring of the original string. 156 // Otherwise return a new string from the buffer. 157 if len(buf) == 0 { 158 return p[:w] 159 } 160 return string(buf[:w]) 161 } 162 163 // Internal helper to lazily create a buffer if necessary. 164 // Calls to this function get inlined. 165 func bufApp(buf *[]byte, s string, w int, c byte) { 166 b := *buf 167 if len(b) == 0 { 168 // No modification of the original string so far. 169 // If the next character is the same as in the original string, we do 170 // not yet have to allocate a buffer. 171 if s[w] == c { 172 return 173 } 174 175 // Otherwise use either the stack buffer, if it is large enough, or 176 // allocate a new buffer on the heap, and copy all previous characters. 177 if l := len(s); l > cap(b) { 178 *buf = make([]byte, len(s)) 179 } else { 180 *buf = (*buf)[:l] 181 } 182 b = *buf 183 184 copy(b, s[:w]) 185 } 186 b[w] = c 187 } 188 189 // AddMissingPort adds a port to a host if it is missing. 190 // A literal IPv6 address in hostport must be enclosed in square 191 // brackets, as in "[::1]:80", "[::1%lo0]:80". 192 func AddMissingPort(addr string, isTLS bool) string { 193 if strings.IndexByte(addr, ':') >= 0 { 194 endOfV6 := strings.IndexByte(addr, ']') 195 // we do not care about the validity of the address, just check if it has more bytes after ']' 196 if endOfV6 < len(addr)-1 { 197 return addr 198 } 199 } 200 if !isTLS { 201 return addr + ":80" 202 } 203 return addr + ":443" 204 }