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  }