github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/gin/tree.go (about)

     1  // Copyright 2013 Julien Schmidt. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be found
     3  // at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
     4  
     5  package gin
     6  
     7  import (
     8  	"bytes"
     9  	"net/url"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  
    14  	"github.com/hellobchain/third_party/gin/internal/bytesconv"
    15  )
    16  
    17  var (
    18  	strColon = []byte(":")
    19  	strStar  = []byte("*")
    20  )
    21  
    22  // Param is a single URL parameter, consisting of a key and a value.
    23  type Param struct {
    24  	Key   string
    25  	Value string
    26  }
    27  
    28  // Params is a Param-slice, as returned by the router.
    29  // The slice is ordered, the first URL parameter is also the first slice value.
    30  // It is therefore safe to read values by the index.
    31  type Params []Param
    32  
    33  // Get returns the value of the first Param which key matches the given name.
    34  // If no matching Param is found, an empty string is returned.
    35  func (ps Params) Get(name string) (string, bool) {
    36  	for _, entry := range ps {
    37  		if entry.Key == name {
    38  			return entry.Value, true
    39  		}
    40  	}
    41  	return "", false
    42  }
    43  
    44  // ByName returns the value of the first Param which key matches the given name.
    45  // If no matching Param is found, an empty string is returned.
    46  func (ps Params) ByName(name string) (va string) {
    47  	va, _ = ps.Get(name)
    48  	return
    49  }
    50  
    51  type methodTree struct {
    52  	method string
    53  	root   *node
    54  }
    55  
    56  type methodTrees []methodTree
    57  
    58  func (trees methodTrees) get(method string) *node {
    59  	for _, tree := range trees {
    60  		if tree.method == method {
    61  			return tree.root
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  func min(a, b int) int {
    68  	if a <= b {
    69  		return a
    70  	}
    71  	return b
    72  }
    73  
    74  func longestCommonPrefix(a, b string) int {
    75  	i := 0
    76  	max := min(len(a), len(b))
    77  	for i < max && a[i] == b[i] {
    78  		i++
    79  	}
    80  	return i
    81  }
    82  
    83  // addChild will add a child node, keeping wildcards at the end
    84  func (n *node) addChild(child *node) {
    85  	if n.wildChild && len(n.children) > 0 {
    86  		wildcardChild := n.children[len(n.children)-1]
    87  		n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
    88  	} else {
    89  		n.children = append(n.children, child)
    90  	}
    91  }
    92  
    93  func countParams(path string) uint16 {
    94  	var n uint16
    95  	s := bytesconv.StringToBytes(path)
    96  	n += uint16(bytes.Count(s, strColon))
    97  	n += uint16(bytes.Count(s, strStar))
    98  	return n
    99  }
   100  
   101  type nodeType uint8
   102  
   103  const (
   104  	static nodeType = iota // default
   105  	root
   106  	param
   107  	catchAll
   108  )
   109  
   110  type node struct {
   111  	path      string
   112  	indices   string
   113  	wildChild bool
   114  	nType     nodeType
   115  	priority  uint32
   116  	children  []*node // child nodes, at most 1 :param style node at the end of the array
   117  	handlers  HandlersChain
   118  	fullPath  string
   119  }
   120  
   121  type skip struct {
   122  	path      string
   123  	paramNode *node
   124  }
   125  
   126  // Increments priority of the given child and reorders if necessary
   127  func (n *node) incrementChildPrio(pos int) int {
   128  	cs := n.children
   129  	cs[pos].priority++
   130  	prio := cs[pos].priority
   131  
   132  	// Adjust position (move to front)
   133  	newPos := pos
   134  	for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
   135  		// Swap node positions
   136  		cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
   137  	}
   138  
   139  	// Build new index char string
   140  	if newPos != pos {
   141  		n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
   142  			n.indices[pos:pos+1] + // The index char we move
   143  			n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
   144  	}
   145  
   146  	return newPos
   147  }
   148  
   149  // addRoute adds a node with the given handle to the path.
   150  // Not concurrency-safe!
   151  func (n *node) addRoute(path string, handlers HandlersChain) {
   152  	fullPath := path
   153  	n.priority++
   154  
   155  	// Empty tree
   156  	if len(n.path) == 0 && len(n.children) == 0 {
   157  		n.insertChild(path, fullPath, handlers)
   158  		n.nType = root
   159  		return
   160  	}
   161  
   162  	parentFullPathIndex := 0
   163  
   164  walk:
   165  	for {
   166  		// Find the longest common prefix.
   167  		// This also implies that the common prefix contains no ':' or '*'
   168  		// since the existing key can't contain those chars.
   169  		i := longestCommonPrefix(path, n.path)
   170  
   171  		// Split edge
   172  		if i < len(n.path) {
   173  			child := node{
   174  				path:      n.path[i:],
   175  				wildChild: n.wildChild,
   176  				indices:   n.indices,
   177  				children:  n.children,
   178  				handlers:  n.handlers,
   179  				priority:  n.priority - 1,
   180  				fullPath:  n.fullPath,
   181  			}
   182  
   183  			n.children = []*node{&child}
   184  			// []byte for proper unicode char conversion, see #65
   185  			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
   186  			n.path = path[:i]
   187  			n.handlers = nil
   188  			n.wildChild = false
   189  			n.fullPath = fullPath[:parentFullPathIndex+i]
   190  		}
   191  
   192  		// Make new node a child of this node
   193  		if i < len(path) {
   194  			path = path[i:]
   195  			c := path[0]
   196  
   197  			// '/' after param
   198  			if n.nType == param && c == '/' && len(n.children) == 1 {
   199  				parentFullPathIndex += len(n.path)
   200  				n = n.children[0]
   201  				n.priority++
   202  				continue walk
   203  			}
   204  
   205  			// Check if a child with the next path byte exists
   206  			for i, max := 0, len(n.indices); i < max; i++ {
   207  				if c == n.indices[i] {
   208  					parentFullPathIndex += len(n.path)
   209  					i = n.incrementChildPrio(i)
   210  					n = n.children[i]
   211  					continue walk
   212  				}
   213  			}
   214  
   215  			// Otherwise insert it
   216  			if c != ':' && c != '*' && n.nType != catchAll {
   217  				// []byte for proper unicode char conversion, see #65
   218  				n.indices += bytesconv.BytesToString([]byte{c})
   219  				child := &node{
   220  					fullPath: fullPath,
   221  				}
   222  				n.addChild(child)
   223  				n.incrementChildPrio(len(n.indices) - 1)
   224  				n = child
   225  			} else if n.wildChild {
   226  				// inserting a wildcard node, need to check if it conflicts with the existing wildcard
   227  				n = n.children[len(n.children)-1]
   228  				n.priority++
   229  
   230  				// Check if the wildcard matches
   231  				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
   232  					// Adding a child to a catchAll is not possible
   233  					n.nType != catchAll &&
   234  					// Check for longer wildcard, e.g. :name and :names
   235  					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
   236  					continue walk
   237  				}
   238  
   239  				// Wildcard conflict
   240  				pathSeg := path
   241  				if n.nType != catchAll {
   242  					pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
   243  				}
   244  				prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
   245  				panic("'" + pathSeg +
   246  					"' in new path '" + fullPath +
   247  					"' conflicts with existing wildcard '" + n.path +
   248  					"' in existing prefix '" + prefix +
   249  					"'")
   250  			}
   251  
   252  			n.insertChild(path, fullPath, handlers)
   253  			return
   254  		}
   255  
   256  		// Otherwise add handle to current node
   257  		if n.handlers != nil {
   258  			panic("handlers are already registered for path '" + fullPath + "'")
   259  		}
   260  		n.handlers = handlers
   261  		n.fullPath = fullPath
   262  		return
   263  	}
   264  }
   265  
   266  // Search for a wildcard segment and check the name for invalid characters.
   267  // Returns -1 as index, if no wildcard was found.
   268  func findWildcard(path string) (wildcard string, i int, valid bool) {
   269  	// Find start
   270  	for start, c := range []byte(path) {
   271  		// A wildcard starts with ':' (param) or '*' (catch-all)
   272  		if c != ':' && c != '*' {
   273  			continue
   274  		}
   275  
   276  		// Find end and check for invalid characters
   277  		valid = true
   278  		for end, c := range []byte(path[start+1:]) {
   279  			switch c {
   280  			case '/':
   281  				return path[start : start+1+end], start, valid
   282  			case ':', '*':
   283  				valid = false
   284  			}
   285  		}
   286  		return path[start:], start, valid
   287  	}
   288  	return "", -1, false
   289  }
   290  
   291  func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
   292  	for {
   293  		// Find prefix until first wildcard
   294  		wildcard, i, valid := findWildcard(path)
   295  		if i < 0 { // No wildcard found
   296  			break
   297  		}
   298  
   299  		// The wildcard name must not contain ':' and '*'
   300  		if !valid {
   301  			panic("only one wildcard per path segment is allowed, has: '" +
   302  				wildcard + "' in path '" + fullPath + "'")
   303  		}
   304  
   305  		// check if the wildcard has a name
   306  		if len(wildcard) < 2 {
   307  			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
   308  		}
   309  
   310  		if wildcard[0] == ':' { // param
   311  			if i > 0 {
   312  				// Insert prefix before the current wildcard
   313  				n.path = path[:i]
   314  				path = path[i:]
   315  			}
   316  
   317  			child := &node{
   318  				nType:    param,
   319  				path:     wildcard,
   320  				fullPath: fullPath,
   321  			}
   322  			n.addChild(child)
   323  			n.wildChild = true
   324  			n = child
   325  			n.priority++
   326  
   327  			// if the path doesn't end with the wildcard, then there
   328  			// will be another non-wildcard subpath starting with '/'
   329  			if len(wildcard) < len(path) {
   330  				path = path[len(wildcard):]
   331  
   332  				child := &node{
   333  					priority: 1,
   334  					fullPath: fullPath,
   335  				}
   336  				n.addChild(child)
   337  				n = child
   338  				continue
   339  			}
   340  
   341  			// Otherwise we're done. Insert the handle in the new leaf
   342  			n.handlers = handlers
   343  			return
   344  		}
   345  
   346  		// catchAll
   347  		if i+len(wildcard) != len(path) {
   348  			panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
   349  		}
   350  
   351  		if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
   352  			panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
   353  		}
   354  
   355  		// currently fixed width 1 for '/'
   356  		i--
   357  		if path[i] != '/' {
   358  			panic("no / before catch-all in path '" + fullPath + "'")
   359  		}
   360  
   361  		n.path = path[:i]
   362  
   363  		// First node: catchAll node with empty path
   364  		child := &node{
   365  			wildChild: true,
   366  			nType:     catchAll,
   367  			fullPath:  fullPath,
   368  		}
   369  
   370  		n.addChild(child)
   371  		n.indices = string('/')
   372  		n = child
   373  		n.priority++
   374  
   375  		// second node: node holding the variable
   376  		child = &node{
   377  			path:     path[i:],
   378  			nType:    catchAll,
   379  			handlers: handlers,
   380  			priority: 1,
   381  			fullPath: fullPath,
   382  		}
   383  		n.children = []*node{child}
   384  
   385  		return
   386  	}
   387  
   388  	// If no wildcard was found, simply insert the path and handle
   389  	n.path = path
   390  	n.handlers = handlers
   391  	n.fullPath = fullPath
   392  }
   393  
   394  // nodeValue holds return values of (*Node).getValue method
   395  type nodeValue struct {
   396  	handlers HandlersChain
   397  	params   *Params
   398  	tsr      bool
   399  	fullPath string
   400  }
   401  
   402  // Returns the handle registered with the given path (key). The values of
   403  // wildcards are saved to a map.
   404  // If no handle can be found, a TSR (trailing slash redirect) recommendation is
   405  // made if a handle exists with an extra (without the) trailing slash for the
   406  // given path.
   407  func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
   408  	var skipped *skip
   409  
   410  walk: // Outer loop for walking the tree
   411  	for {
   412  		prefix := n.path
   413  		if len(path) > len(prefix) {
   414  			if path[:len(prefix)] == prefix {
   415  				path = path[len(prefix):]
   416  
   417  				// Try all the non-wildcard children first by matching the indices
   418  				idxc := path[0]
   419  				for i, c := range []byte(n.indices) {
   420  					if c == idxc {
   421  						if strings.HasPrefix(n.children[len(n.children)-1].path, ":") {
   422  							skipped = &skip{
   423  								path: prefix + path,
   424  								paramNode: &node{
   425  									path:      n.path,
   426  									wildChild: n.wildChild,
   427  									nType:     n.nType,
   428  									priority:  n.priority,
   429  									children:  n.children,
   430  									handlers:  n.handlers,
   431  									fullPath:  n.fullPath,
   432  								},
   433  							}
   434  						}
   435  
   436  						n = n.children[i]
   437  						continue walk
   438  					}
   439  				}
   440  
   441  				// If there is no wildcard pattern, recommend a redirection
   442  				if !n.wildChild {
   443  					// Nothing found.
   444  					// We can recommend to redirect to the same URL without a
   445  					// trailing slash if a leaf exists for that path.
   446  					value.tsr = (path == "/" && n.handlers != nil)
   447  					return
   448  				}
   449  
   450  				// Handle wildcard child, which is always at the end of the array
   451  				n = n.children[len(n.children)-1]
   452  
   453  				switch n.nType {
   454  				case param:
   455  					// Find param end (either '/' or path end)
   456  					end := 0
   457  					for end < len(path) && path[end] != '/' {
   458  						end++
   459  					}
   460  
   461  					// Save param value
   462  					if params != nil {
   463  						if value.params == nil {
   464  							value.params = params
   465  						}
   466  						// Expand slice within preallocated capacity
   467  						i := len(*value.params)
   468  						*value.params = (*value.params)[:i+1]
   469  						val := path[:end]
   470  						if unescape {
   471  							if v, err := url.QueryUnescape(val); err == nil {
   472  								val = v
   473  							}
   474  						}
   475  						(*value.params)[i] = Param{
   476  							Key:   n.path[1:],
   477  							Value: val,
   478  						}
   479  					}
   480  
   481  					// we need to go deeper!
   482  					if end < len(path) {
   483  						if len(n.children) > 0 {
   484  							path = path[end:]
   485  							n = n.children[0]
   486  							continue walk
   487  						}
   488  
   489  						// ... but we can't
   490  						value.tsr = (len(path) == end+1)
   491  						return
   492  					}
   493  
   494  					if value.handlers = n.handlers; value.handlers != nil {
   495  						value.fullPath = n.fullPath
   496  						return
   497  					}
   498  					if len(n.children) == 1 {
   499  						// No handle found. Check if a handle for this path + a
   500  						// trailing slash exists for TSR recommendation
   501  						n = n.children[0]
   502  						value.tsr = (n.path == "/" && n.handlers != nil)
   503  					}
   504  					return
   505  
   506  				case catchAll:
   507  					// Save param value
   508  					if params != nil {
   509  						if value.params == nil {
   510  							value.params = params
   511  						}
   512  						// Expand slice within preallocated capacity
   513  						i := len(*value.params)
   514  						*value.params = (*value.params)[:i+1]
   515  						val := path
   516  						if unescape {
   517  							if v, err := url.QueryUnescape(path); err == nil {
   518  								val = v
   519  							}
   520  						}
   521  						(*value.params)[i] = Param{
   522  							Key:   n.path[2:],
   523  							Value: val,
   524  						}
   525  					}
   526  
   527  					value.handlers = n.handlers
   528  					value.fullPath = n.fullPath
   529  					return
   530  
   531  				default:
   532  					panic("invalid node type")
   533  				}
   534  			}
   535  		}
   536  
   537  		if path == prefix {
   538  			// We should have reached the node containing the handle.
   539  			// Check if this node has a handle registered.
   540  			if value.handlers = n.handlers; value.handlers != nil {
   541  				value.fullPath = n.fullPath
   542  				return
   543  			}
   544  
   545  			// If there is no handle for this route, but this route has a
   546  			// wildcard child, there must be a handle for this path with an
   547  			// additional trailing slash
   548  			if path == "/" && n.wildChild && n.nType != root {
   549  				value.tsr = true
   550  				return
   551  			}
   552  
   553  			// No handle found. Check if a handle for this path + a
   554  			// trailing slash exists for trailing slash recommendation
   555  			for i, c := range []byte(n.indices) {
   556  				if c == '/' {
   557  					n = n.children[i]
   558  					value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
   559  						(n.nType == catchAll && n.children[0].handlers != nil)
   560  					return
   561  				}
   562  			}
   563  
   564  			return
   565  		}
   566  
   567  		if path != "/" && skipped != nil && strings.HasSuffix(skipped.path, path) {
   568  			path = skipped.path
   569  			n = skipped.paramNode
   570  			skipped = nil
   571  			continue walk
   572  		}
   573  
   574  		// Nothing found. We can recommend to redirect to the same URL with an
   575  		// extra trailing slash if a leaf exists for that path
   576  		value.tsr = (path == "/") ||
   577  			(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
   578  				path == prefix[:len(prefix)-1] && n.handlers != nil)
   579  		return
   580  	}
   581  }
   582  
   583  // Makes a case-insensitive lookup of the given path and tries to find a handler.
   584  // It can optionally also fix trailing slashes.
   585  // It returns the case-corrected path and a bool indicating whether the lookup
   586  // was successful.
   587  func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) {
   588  	const stackBufSize = 128
   589  
   590  	// Use a static sized buffer on the stack in the common case.
   591  	// If the path is too long, allocate a buffer on the heap instead.
   592  	buf := make([]byte, 0, stackBufSize)
   593  	if length := len(path) + 1; length > stackBufSize {
   594  		buf = make([]byte, 0, length)
   595  	}
   596  
   597  	ciPath := n.findCaseInsensitivePathRec(
   598  		path,
   599  		buf,       // Preallocate enough memory for new path
   600  		[4]byte{}, // Empty rune buffer
   601  		fixTrailingSlash,
   602  	)
   603  
   604  	return ciPath, ciPath != nil
   605  }
   606  
   607  // Shift bytes in array by n bytes left
   608  func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
   609  	switch n {
   610  	case 0:
   611  		return rb
   612  	case 1:
   613  		return [4]byte{rb[1], rb[2], rb[3], 0}
   614  	case 2:
   615  		return [4]byte{rb[2], rb[3]}
   616  	case 3:
   617  		return [4]byte{rb[3]}
   618  	default:
   619  		return [4]byte{}
   620  	}
   621  }
   622  
   623  // Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
   624  func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
   625  	npLen := len(n.path)
   626  
   627  walk: // Outer loop for walking the tree
   628  	for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
   629  		// Add common prefix to result
   630  		oldPath := path
   631  		path = path[npLen:]
   632  		ciPath = append(ciPath, n.path...)
   633  
   634  		if len(path) == 0 {
   635  			// We should have reached the node containing the handle.
   636  			// Check if this node has a handle registered.
   637  			if n.handlers != nil {
   638  				return ciPath
   639  			}
   640  
   641  			// No handle found.
   642  			// Try to fix the path by adding a trailing slash
   643  			if fixTrailingSlash {
   644  				for i, c := range []byte(n.indices) {
   645  					if c == '/' {
   646  						n = n.children[i]
   647  						if (len(n.path) == 1 && n.handlers != nil) ||
   648  							(n.nType == catchAll && n.children[0].handlers != nil) {
   649  							return append(ciPath, '/')
   650  						}
   651  						return nil
   652  					}
   653  				}
   654  			}
   655  			return nil
   656  		}
   657  
   658  		// If this node does not have a wildcard (param or catchAll) child,
   659  		// we can just look up the next child node and continue to walk down
   660  		// the tree
   661  		if !n.wildChild {
   662  			// Skip rune bytes already processed
   663  			rb = shiftNRuneBytes(rb, npLen)
   664  
   665  			if rb[0] != 0 {
   666  				// Old rune not finished
   667  				idxc := rb[0]
   668  				for i, c := range []byte(n.indices) {
   669  					if c == idxc {
   670  						// continue with child node
   671  						n = n.children[i]
   672  						npLen = len(n.path)
   673  						continue walk
   674  					}
   675  				}
   676  			} else {
   677  				// Process a new rune
   678  				var rv rune
   679  
   680  				// Find rune start.
   681  				// Runes are up to 4 byte long,
   682  				// -4 would definitely be another rune.
   683  				var off int
   684  				for max := min(npLen, 3); off < max; off++ {
   685  					if i := npLen - off; utf8.RuneStart(oldPath[i]) {
   686  						// read rune from cached path
   687  						rv, _ = utf8.DecodeRuneInString(oldPath[i:])
   688  						break
   689  					}
   690  				}
   691  
   692  				// Calculate lowercase bytes of current rune
   693  				lo := unicode.ToLower(rv)
   694  				utf8.EncodeRune(rb[:], lo)
   695  
   696  				// Skip already processed bytes
   697  				rb = shiftNRuneBytes(rb, off)
   698  
   699  				idxc := rb[0]
   700  				for i, c := range []byte(n.indices) {
   701  					// Lowercase matches
   702  					if c == idxc {
   703  						// must use a recursive approach since both the
   704  						// uppercase byte and the lowercase byte might exist
   705  						// as an index
   706  						if out := n.children[i].findCaseInsensitivePathRec(
   707  							path, ciPath, rb, fixTrailingSlash,
   708  						); out != nil {
   709  							return out
   710  						}
   711  						break
   712  					}
   713  				}
   714  
   715  				// If we found no match, the same for the uppercase rune,
   716  				// if it differs
   717  				if up := unicode.ToUpper(rv); up != lo {
   718  					utf8.EncodeRune(rb[:], up)
   719  					rb = shiftNRuneBytes(rb, off)
   720  
   721  					idxc := rb[0]
   722  					for i, c := range []byte(n.indices) {
   723  						// Uppercase matches
   724  						if c == idxc {
   725  							// Continue with child node
   726  							n = n.children[i]
   727  							npLen = len(n.path)
   728  							continue walk
   729  						}
   730  					}
   731  				}
   732  			}
   733  
   734  			// Nothing found. We can recommend to redirect to the same URL
   735  			// without a trailing slash if a leaf exists for that path
   736  			if fixTrailingSlash && path == "/" && n.handlers != nil {
   737  				return ciPath
   738  			}
   739  			return nil
   740  		}
   741  
   742  		n = n.children[0]
   743  		switch n.nType {
   744  		case param:
   745  			// Find param end (either '/' or path end)
   746  			end := 0
   747  			for end < len(path) && path[end] != '/' {
   748  				end++
   749  			}
   750  
   751  			// Add param value to case insensitive path
   752  			ciPath = append(ciPath, path[:end]...)
   753  
   754  			// We need to go deeper!
   755  			if end < len(path) {
   756  				if len(n.children) > 0 {
   757  					// Continue with child node
   758  					n = n.children[0]
   759  					npLen = len(n.path)
   760  					path = path[end:]
   761  					continue
   762  				}
   763  
   764  				// ... but we can't
   765  				if fixTrailingSlash && len(path) == end+1 {
   766  					return ciPath
   767  				}
   768  				return nil
   769  			}
   770  
   771  			if n.handlers != nil {
   772  				return ciPath
   773  			}
   774  
   775  			if fixTrailingSlash && len(n.children) == 1 {
   776  				// No handle found. Check if a handle for this path + a
   777  				// trailing slash exists
   778  				n = n.children[0]
   779  				if n.path == "/" && n.handlers != nil {
   780  					return append(ciPath, '/')
   781  				}
   782  			}
   783  
   784  			return nil
   785  
   786  		case catchAll:
   787  			return append(ciPath, path...)
   788  
   789  		default:
   790  			panic("invalid node type")
   791  		}
   792  	}
   793  
   794  	// Nothing found.
   795  	// Try to fix the path by adding / removing a trailing slash
   796  	if fixTrailingSlash {
   797  		if path == "/" {
   798  			return ciPath
   799  		}
   800  		if len(path)+1 == npLen && n.path[len(path)] == '/' &&
   801  			strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil {
   802  			return append(ciPath, n.path...)
   803  		}
   804  	}
   805  	return nil
   806  }