github.com/astaxie/beego@v1.12.3/tree.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package beego
    16  
    17  import (
    18  	"path"
    19  	"regexp"
    20  	"strings"
    21  
    22  	"github.com/astaxie/beego/context"
    23  	"github.com/astaxie/beego/utils"
    24  )
    25  
    26  var (
    27  	allowSuffixExt = []string{".json", ".xml", ".html"}
    28  )
    29  
    30  // Tree has three elements: FixRouter/wildcard/leaves
    31  // fixRouter stores Fixed Router
    32  // wildcard stores params
    33  // leaves store the endpoint information
    34  type Tree struct {
    35  	//prefix set for static router
    36  	prefix string
    37  	//search fix route first
    38  	fixrouters []*Tree
    39  	//if set, failure to match fixrouters search then search wildcard
    40  	wildcard *Tree
    41  	//if set, failure to match wildcard search
    42  	leaves []*leafInfo
    43  }
    44  
    45  // NewTree return a new Tree
    46  func NewTree() *Tree {
    47  	return &Tree{}
    48  }
    49  
    50  // AddTree will add tree to the exist Tree
    51  // prefix should has no params
    52  func (t *Tree) AddTree(prefix string, tree *Tree) {
    53  	t.addtree(splitPath(prefix), tree, nil, "")
    54  }
    55  
    56  func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg string) {
    57  	if len(segments) == 0 {
    58  		panic("prefix should has path")
    59  	}
    60  	seg := segments[0]
    61  	iswild, params, regexpStr := splitSegment(seg)
    62  	// if it's ? meaning can igone this, so add one more rule for it
    63  	if len(params) > 0 && params[0] == ":" {
    64  		params = params[1:]
    65  		if len(segments[1:]) > 0 {
    66  			t.addtree(segments[1:], tree, append(wildcards, params...), reg)
    67  		} else {
    68  			filterTreeWithPrefix(tree, wildcards, reg)
    69  		}
    70  	}
    71  	//Rule: /login/*/access match /login/2009/11/access
    72  	//if already has *, and when loop the access, should as a regexpStr
    73  	if !iswild && utils.InSlice(":splat", wildcards) {
    74  		iswild = true
    75  		regexpStr = seg
    76  	}
    77  	//Rule: /user/:id/*
    78  	if seg == "*" && len(wildcards) > 0 && reg == "" {
    79  		regexpStr = "(.+)"
    80  	}
    81  	if len(segments) == 1 {
    82  		if iswild {
    83  			if regexpStr != "" {
    84  				if reg == "" {
    85  					rr := ""
    86  					for _, w := range wildcards {
    87  						if w == ":splat" {
    88  							rr = rr + "(.+)/"
    89  						} else {
    90  							rr = rr + "([^/]+)/"
    91  						}
    92  					}
    93  					regexpStr = rr + regexpStr
    94  				} else {
    95  					regexpStr = "/" + regexpStr
    96  				}
    97  			} else if reg != "" {
    98  				if seg == "*.*" {
    99  					regexpStr = "([^.]+).(.+)"
   100  				} else {
   101  					for _, w := range params {
   102  						if w == "." || w == ":" {
   103  							continue
   104  						}
   105  						regexpStr = "([^/]+)/" + regexpStr
   106  					}
   107  				}
   108  			}
   109  			reg = strings.Trim(reg+"/"+regexpStr, "/")
   110  			filterTreeWithPrefix(tree, append(wildcards, params...), reg)
   111  			t.wildcard = tree
   112  		} else {
   113  			reg = strings.Trim(reg+"/"+regexpStr, "/")
   114  			filterTreeWithPrefix(tree, append(wildcards, params...), reg)
   115  			tree.prefix = seg
   116  			t.fixrouters = append(t.fixrouters, tree)
   117  		}
   118  		return
   119  	}
   120  
   121  	if iswild {
   122  		if t.wildcard == nil {
   123  			t.wildcard = NewTree()
   124  		}
   125  		if regexpStr != "" {
   126  			if reg == "" {
   127  				rr := ""
   128  				for _, w := range wildcards {
   129  					if w == ":splat" {
   130  						rr = rr + "(.+)/"
   131  					} else {
   132  						rr = rr + "([^/]+)/"
   133  					}
   134  				}
   135  				regexpStr = rr + regexpStr
   136  			} else {
   137  				regexpStr = "/" + regexpStr
   138  			}
   139  		} else if reg != "" {
   140  			if seg == "*.*" {
   141  				regexpStr = "([^.]+).(.+)"
   142  				params = params[1:]
   143  			} else {
   144  				for range params {
   145  					regexpStr = "([^/]+)/" + regexpStr
   146  				}
   147  			}
   148  		} else {
   149  			if seg == "*.*" {
   150  				params = params[1:]
   151  			}
   152  		}
   153  		reg = strings.TrimRight(strings.TrimRight(reg, "/")+"/"+regexpStr, "/")
   154  		t.wildcard.addtree(segments[1:], tree, append(wildcards, params...), reg)
   155  	} else {
   156  		subTree := NewTree()
   157  		subTree.prefix = seg
   158  		t.fixrouters = append(t.fixrouters, subTree)
   159  		subTree.addtree(segments[1:], tree, append(wildcards, params...), reg)
   160  	}
   161  }
   162  
   163  func filterTreeWithPrefix(t *Tree, wildcards []string, reg string) {
   164  	for _, v := range t.fixrouters {
   165  		filterTreeWithPrefix(v, wildcards, reg)
   166  	}
   167  	if t.wildcard != nil {
   168  		filterTreeWithPrefix(t.wildcard, wildcards, reg)
   169  	}
   170  	for _, l := range t.leaves {
   171  		if reg != "" {
   172  			if l.regexps != nil {
   173  				l.wildcards = append(wildcards, l.wildcards...)
   174  				l.regexps = regexp.MustCompile("^" + reg + "/" + strings.Trim(l.regexps.String(), "^$") + "$")
   175  			} else {
   176  				for _, v := range l.wildcards {
   177  					if v == ":splat" {
   178  						reg = reg + "/(.+)"
   179  					} else {
   180  						reg = reg + "/([^/]+)"
   181  					}
   182  				}
   183  				l.regexps = regexp.MustCompile("^" + reg + "$")
   184  				l.wildcards = append(wildcards, l.wildcards...)
   185  			}
   186  		} else {
   187  			l.wildcards = append(wildcards, l.wildcards...)
   188  			if l.regexps != nil {
   189  				for _, w := range wildcards {
   190  					if w == ":splat" {
   191  						reg = "(.+)/" + reg
   192  					} else {
   193  						reg = "([^/]+)/" + reg
   194  					}
   195  				}
   196  				l.regexps = regexp.MustCompile("^" + reg + strings.Trim(l.regexps.String(), "^$") + "$")
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  // AddRouter call addseg function
   203  func (t *Tree) AddRouter(pattern string, runObject interface{}) {
   204  	t.addseg(splitPath(pattern), runObject, nil, "")
   205  }
   206  
   207  // "/"
   208  // "admin" ->
   209  func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, reg string) {
   210  	if len(segments) == 0 {
   211  		if reg != "" {
   212  			t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards, regexps: regexp.MustCompile("^" + reg + "$")})
   213  		} else {
   214  			t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards})
   215  		}
   216  	} else {
   217  		seg := segments[0]
   218  		iswild, params, regexpStr := splitSegment(seg)
   219  		// if it's ? meaning can igone this, so add one more rule for it
   220  		if len(params) > 0 && params[0] == ":" {
   221  			t.addseg(segments[1:], route, wildcards, reg)
   222  			params = params[1:]
   223  		}
   224  		//Rule: /login/*/access match /login/2009/11/access
   225  		//if already has *, and when loop the access, should as a regexpStr
   226  		if !iswild && utils.InSlice(":splat", wildcards) {
   227  			iswild = true
   228  			regexpStr = seg
   229  		}
   230  		//Rule: /user/:id/*
   231  		if seg == "*" && len(wildcards) > 0 && reg == "" {
   232  			regexpStr = "(.+)"
   233  		}
   234  		if iswild {
   235  			if t.wildcard == nil {
   236  				t.wildcard = NewTree()
   237  			}
   238  			if regexpStr != "" {
   239  				if reg == "" {
   240  					rr := ""
   241  					for _, w := range wildcards {
   242  						if w == ":splat" {
   243  							rr = rr + "(.+)/"
   244  						} else {
   245  							rr = rr + "([^/]+)/"
   246  						}
   247  					}
   248  					regexpStr = rr + regexpStr
   249  				} else {
   250  					regexpStr = "/" + regexpStr
   251  				}
   252  			} else if reg != "" {
   253  				if seg == "*.*" {
   254  					regexpStr = "/([^.]+).(.+)"
   255  					params = params[1:]
   256  				} else {
   257  					for range params {
   258  						regexpStr = "/([^/]+)" + regexpStr
   259  					}
   260  				}
   261  			} else {
   262  				if seg == "*.*" {
   263  					params = params[1:]
   264  				}
   265  			}
   266  			t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
   267  		} else {
   268  			var subTree *Tree
   269  			for _, sub := range t.fixrouters {
   270  				if sub.prefix == seg {
   271  					subTree = sub
   272  					break
   273  				}
   274  			}
   275  			if subTree == nil {
   276  				subTree = NewTree()
   277  				subTree.prefix = seg
   278  				t.fixrouters = append(t.fixrouters, subTree)
   279  			}
   280  			subTree.addseg(segments[1:], route, wildcards, reg)
   281  		}
   282  	}
   283  }
   284  
   285  // Match router to runObject & params
   286  func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{}) {
   287  	if len(pattern) == 0 || pattern[0] != '/' {
   288  		return nil
   289  	}
   290  	w := make([]string, 0, 20)
   291  	return t.match(pattern[1:], pattern, w, ctx)
   292  }
   293  
   294  func (t *Tree) match(treePattern string, pattern string, wildcardValues []string, ctx *context.Context) (runObject interface{}) {
   295  	if len(pattern) > 0 {
   296  		i := 0
   297  		for ; i < len(pattern) && pattern[i] == '/'; i++ {
   298  		}
   299  		pattern = pattern[i:]
   300  	}
   301  	// Handle leaf nodes:
   302  	if len(pattern) == 0 {
   303  		for _, l := range t.leaves {
   304  			if ok := l.match(treePattern, wildcardValues, ctx); ok {
   305  				return l.runObject
   306  			}
   307  		}
   308  		if t.wildcard != nil {
   309  			for _, l := range t.wildcard.leaves {
   310  				if ok := l.match(treePattern, wildcardValues, ctx); ok {
   311  					return l.runObject
   312  				}
   313  			}
   314  		}
   315  		return nil
   316  	}
   317  	var seg string
   318  	i, l := 0, len(pattern)
   319  	for ; i < l && pattern[i] != '/'; i++ {
   320  	}
   321  	if i == 0 {
   322  		seg = pattern
   323  		pattern = ""
   324  	} else {
   325  		seg = pattern[:i]
   326  		pattern = pattern[i:]
   327  	}
   328  	for _, subTree := range t.fixrouters {
   329  		if subTree.prefix == seg {
   330  			if len(pattern) != 0 && pattern[0] == '/' {
   331  				treePattern = pattern[1:]
   332  			} else {
   333  				treePattern = pattern
   334  			}
   335  			runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)
   336  			if runObject != nil {
   337  				break
   338  			}
   339  		}
   340  	}
   341  	if runObject == nil && len(t.fixrouters) > 0 {
   342  		// Filter the .json .xml .html extension
   343  		for _, str := range allowSuffixExt {
   344  			if strings.HasSuffix(seg, str) {
   345  				for _, subTree := range t.fixrouters {
   346  					if subTree.prefix == seg[:len(seg)-len(str)] {
   347  						runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)
   348  						if runObject != nil {
   349  							ctx.Input.SetParam(":ext", str[1:])
   350  						}
   351  					}
   352  				}
   353  			}
   354  		}
   355  	}
   356  	if runObject == nil && t.wildcard != nil {
   357  		runObject = t.wildcard.match(treePattern, pattern, append(wildcardValues, seg), ctx)
   358  	}
   359  
   360  	if runObject == nil && len(t.leaves) > 0 {
   361  		wildcardValues = append(wildcardValues, seg)
   362  		start, i := 0, 0
   363  		for ; i < len(pattern); i++ {
   364  			if pattern[i] == '/' {
   365  				if i != 0 && start < len(pattern) {
   366  					wildcardValues = append(wildcardValues, pattern[start:i])
   367  				}
   368  				start = i + 1
   369  				continue
   370  			}
   371  		}
   372  		if start > 0 {
   373  			wildcardValues = append(wildcardValues, pattern[start:i])
   374  		}
   375  		for _, l := range t.leaves {
   376  			if ok := l.match(treePattern, wildcardValues, ctx); ok {
   377  				return l.runObject
   378  			}
   379  		}
   380  	}
   381  	return runObject
   382  }
   383  
   384  type leafInfo struct {
   385  	// names of wildcards that lead to this leaf. eg, ["id" "name"] for the wildcard ":id" and ":name"
   386  	wildcards []string
   387  
   388  	// if the leaf is regexp
   389  	regexps *regexp.Regexp
   390  
   391  	runObject interface{}
   392  }
   393  
   394  func (leaf *leafInfo) match(treePattern string, wildcardValues []string, ctx *context.Context) (ok bool) {
   395  	//fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps)
   396  	if leaf.regexps == nil {
   397  		if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path
   398  			return true
   399  		}
   400  		// match *
   401  		if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" {
   402  			ctx.Input.SetParam(":splat", treePattern)
   403  			return true
   404  		}
   405  		// match *.* or :id
   406  		if len(leaf.wildcards) >= 2 && leaf.wildcards[len(leaf.wildcards)-2] == ":path" && leaf.wildcards[len(leaf.wildcards)-1] == ":ext" {
   407  			if len(leaf.wildcards) == 2 {
   408  				lastone := wildcardValues[len(wildcardValues)-1]
   409  				strs := strings.SplitN(lastone, ".", 2)
   410  				if len(strs) == 2 {
   411  					ctx.Input.SetParam(":ext", strs[1])
   412  				}
   413  				ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[:len(wildcardValues)-1]...), strs[0]))
   414  				return true
   415  			} else if len(wildcardValues) < 2 {
   416  				return false
   417  			}
   418  			var index int
   419  			for index = 0; index < len(leaf.wildcards)-2; index++ {
   420  				ctx.Input.SetParam(leaf.wildcards[index], wildcardValues[index])
   421  			}
   422  			lastone := wildcardValues[len(wildcardValues)-1]
   423  			strs := strings.SplitN(lastone, ".", 2)
   424  			if len(strs) == 2 {
   425  				ctx.Input.SetParam(":ext", strs[1])
   426  			}
   427  			if index > (len(wildcardValues) - 1) {
   428  				ctx.Input.SetParam(":path", "")
   429  			} else {
   430  				ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
   431  			}
   432  			return true
   433  		}
   434  		// match :id
   435  		if len(leaf.wildcards) != len(wildcardValues) {
   436  			return false
   437  		}
   438  		for j, v := range leaf.wildcards {
   439  			ctx.Input.SetParam(v, wildcardValues[j])
   440  		}
   441  		return true
   442  	}
   443  
   444  	if !leaf.regexps.MatchString(path.Join(wildcardValues...)) {
   445  		return false
   446  	}
   447  	matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
   448  	for i, match := range matches[1:] {
   449  		if i < len(leaf.wildcards) {
   450  			ctx.Input.SetParam(leaf.wildcards[i], match)
   451  		}
   452  	}
   453  	return true
   454  }
   455  
   456  // "/" -> []
   457  // "/admin" -> ["admin"]
   458  // "/admin/" -> ["admin"]
   459  // "/admin/users" -> ["admin", "users"]
   460  func splitPath(key string) []string {
   461  	key = strings.Trim(key, "/ ")
   462  	if key == "" {
   463  		return []string{}
   464  	}
   465  	return strings.Split(key, "/")
   466  }
   467  
   468  // "admin" -> false, nil, ""
   469  // ":id" -> true, [:id], ""
   470  // "?:id" -> true, [: :id], ""        : meaning can empty
   471  // ":id:int" -> true, [:id], ([0-9]+)
   472  // ":name:string" -> true, [:name], ([\w]+)
   473  // ":id([0-9]+)" -> true, [:id], ([0-9]+)
   474  // ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+)
   475  // "cms_:id_:page.html" -> true, [:id_ :page], cms_(.+)(.+).html
   476  // "cms_:id(.+)_:page.html" -> true, [:id :page], cms_(.+)_(.+).html
   477  // "*" -> true, [:splat], ""
   478  // "*.*" -> true,[. :path :ext], ""      . meaning separator
   479  func splitSegment(key string) (bool, []string, string) {
   480  	if strings.HasPrefix(key, "*") {
   481  		if key == "*.*" {
   482  			return true, []string{".", ":path", ":ext"}, ""
   483  		}
   484  		return true, []string{":splat"}, ""
   485  	}
   486  	if strings.ContainsAny(key, ":") {
   487  		var paramsNum int
   488  		var out []rune
   489  		var start bool
   490  		var startexp bool
   491  		var param []rune
   492  		var expt []rune
   493  		var skipnum int
   494  		params := []string{}
   495  		reg := regexp.MustCompile(`[a-zA-Z0-9_]+`)
   496  		for i, v := range key {
   497  			if skipnum > 0 {
   498  				skipnum--
   499  				continue
   500  			}
   501  			if start {
   502  				//:id:int and :name:string
   503  				if v == ':' {
   504  					if len(key) >= i+4 {
   505  						if key[i+1:i+4] == "int" {
   506  							out = append(out, []rune("([0-9]+)")...)
   507  							params = append(params, ":"+string(param))
   508  							start = false
   509  							startexp = false
   510  							skipnum = 3
   511  							param = make([]rune, 0)
   512  							paramsNum++
   513  							continue
   514  						}
   515  					}
   516  					if len(key) >= i+7 {
   517  						if key[i+1:i+7] == "string" {
   518  							out = append(out, []rune(`([\w]+)`)...)
   519  							params = append(params, ":"+string(param))
   520  							paramsNum++
   521  							start = false
   522  							startexp = false
   523  							skipnum = 6
   524  							param = make([]rune, 0)
   525  							continue
   526  						}
   527  					}
   528  				}
   529  				// params only support a-zA-Z0-9
   530  				if reg.MatchString(string(v)) {
   531  					param = append(param, v)
   532  					continue
   533  				}
   534  				if v != '(' {
   535  					out = append(out, []rune(`(.+)`)...)
   536  					params = append(params, ":"+string(param))
   537  					param = make([]rune, 0)
   538  					paramsNum++
   539  					start = false
   540  					startexp = false
   541  				}
   542  			}
   543  			if startexp {
   544  				if v != ')' {
   545  					expt = append(expt, v)
   546  					continue
   547  				}
   548  			}
   549  			// Escape Sequence '\'
   550  			if i > 0 && key[i-1] == '\\' {
   551  				out = append(out, v)
   552  			} else if v == ':' {
   553  				param = make([]rune, 0)
   554  				start = true
   555  			} else if v == '(' {
   556  				startexp = true
   557  				start = false
   558  				if len(param) > 0 {
   559  					params = append(params, ":"+string(param))
   560  					param = make([]rune, 0)
   561  				}
   562  				paramsNum++
   563  				expt = make([]rune, 0)
   564  				expt = append(expt, '(')
   565  			} else if v == ')' {
   566  				startexp = false
   567  				expt = append(expt, ')')
   568  				out = append(out, expt...)
   569  				param = make([]rune, 0)
   570  			} else if v == '?' {
   571  				params = append(params, ":")
   572  			} else {
   573  				out = append(out, v)
   574  			}
   575  		}
   576  		if len(param) > 0 {
   577  			if paramsNum > 0 {
   578  				out = append(out, []rune(`(.+)`)...)
   579  			}
   580  			params = append(params, ":"+string(param))
   581  		}
   582  		return true, params, string(out)
   583  	}
   584  	return false, nil, ""
   585  }