Mercurial Hosting > d2o
comparison icf/icf.go @ 12:84384cccda0e
bubfix subpaths and remove hardcode
| author | atarwn@g5 |
|---|---|
| date | Mon, 23 Mar 2026 12:05:05 +0500 |
| parents | 2ffb8028ccbb |
| children | 9460f83a5664 |
comparison
equal
deleted
inserted
replaced
| 11:350589d762a0 | 12:84384cccda0e |
|---|---|
| 144 } | 144 } |
| 145 | 145 |
| 146 // Match finds the most specific block matching input (e.g. "host/path") and | 146 // Match finds the most specific block matching input (e.g. "host/path") and |
| 147 // returns resolved directives plus named captures. | 147 // returns resolved directives plus named captures. |
| 148 // Domain is matched exactly; path uses prefix match. | 148 // Domain is matched exactly; path uses prefix match. |
| 149 func (c *Config) Match(input string) ([]Directive, map[string]string) { | 149 func (c *Config) Match(input string) ([]Directive, map[string]string, string) { |
| 150 inHost, inPath, _ := strings.Cut(input, "/") | 150 inHost, inPath, _ := strings.Cut(input, "/") |
| 151 | 151 |
| 152 type hit struct { | 152 type hit struct { |
| 153 block ParsedBlock | 153 block ParsedBlock |
| 154 captures map[string]string | 154 captures map[string]string |
| 155 score int | 155 score int |
| 156 rem string | |
| 156 } | 157 } |
| 157 var best *hit | 158 var best *hit |
| 158 | 159 |
| 159 for _, b := range c.Blocks { | 160 for _, b := range c.Blocks { |
| 160 patHost, patPath, hasPath := strings.Cut(b.ID, "/") | 161 patHost, patPath, hasPath := strings.Cut(b.ID, "/") |
| 164 if !ok { | 165 if !ok { |
| 165 continue | 166 continue |
| 166 } | 167 } |
| 167 | 168 |
| 168 pathScore := 0 | 169 pathScore := 0 |
| 170 rem := inPath | |
| 169 if hasPath { | 171 if hasPath { |
| 170 pathScore, ok = matchPrefix(patPath, inPath, caps) | 172 var pathRem string |
| 173 pathScore, pathRem, ok = matchPrefix(patPath, inPath, caps) | |
| 171 if !ok { | 174 if !ok { |
| 172 continue | 175 continue |
| 173 } | 176 } |
| 177 rem = pathRem | |
| 174 } | 178 } |
| 175 | 179 |
| 176 score := domScore*1000 + pathScore | 180 score := domScore*1000 + pathScore |
| 177 if best == nil || score > best.score { | 181 if best == nil || score > best.score { |
| 178 best = &hit{block: b, captures: caps, score: score} | 182 best = &hit{block: b, captures: caps, score: score, rem: rem} |
| 179 } | 183 } |
| 180 } | 184 } |
| 181 | 185 |
| 182 if best == nil { | 186 if best == nil { |
| 183 return nil, nil | 187 return nil, nil, "" |
| 184 } | 188 } |
| 185 | 189 |
| 186 return c.ResolveBlock(best.block, best.captures), best.captures | 190 // rem is the unmatched suffix after the path pattern. |
| 191 // Ensure it starts with "/" to form a valid absolute subpath. | |
| 192 subPath := best.rem | |
| 193 if !strings.HasPrefix(subPath, "/") { | |
| 194 subPath = "/" + subPath | |
| 195 } | |
| 196 return c.ResolveBlock(best.block, best.captures), best.captures, subPath | |
| 187 } | 197 } |
| 188 | 198 |
| 189 // ResolveBlock merges mixin directives (lower priority) with block directives, | 199 // ResolveBlock merges mixin directives (lower priority) with block directives, |
| 190 // then substitutes capture variables. | 200 // then substitutes capture variables. |
| 191 func (c *Config) ResolveBlock(b ParsedBlock, caps map[string]string) []Directive { | 201 func (c *Config) ResolveBlock(b ParsedBlock, caps map[string]string) []Directive { |
| 284 return 0, false | 294 return 0, false |
| 285 } | 295 } |
| 286 return score, true | 296 return score, true |
| 287 } | 297 } |
| 288 | 298 |
| 289 func matchPrefix(pat, s string, caps map[string]string) (int, bool) { | 299 func matchPrefix(pat, s string, caps map[string]string) (int, string, bool) { |
| 290 score, _, ok := matchCaptures(pat, s, caps) | 300 score, rem, ok := matchCaptures(pat, s, caps) |
| 291 return score, ok | 301 return score, rem, ok |
| 292 } | 302 } |
| 293 | 303 |
| 294 func matchCaptures(pat, inp string, caps map[string]string) (int, string, bool) { | 304 func matchCaptures(pat, inp string, caps map[string]string) (int, string, bool) { |
| 295 for { | 305 for { |
| 296 if pat == "" { | 306 if pat == "" { |
| 304 } | 314 } |
| 305 capName := pat[1:end] | 315 capName := pat[1:end] |
| 306 rest := pat[end+1:] | 316 rest := pat[end+1:] |
| 307 | 317 |
| 308 for split := len(inp); split >= 1; split-- { | 318 for split := len(inp); split >= 1; split-- { |
| 319 // Captures never span path separators: <n> matches one segment only. | |
| 320 if strings.Contains(inp[:split], "/") { | |
| 321 continue | |
| 322 } | |
| 309 score, finalRem, ok := matchCaptures(rest, inp[split:], caps) | 323 score, finalRem, ok := matchCaptures(rest, inp[split:], caps) |
| 310 if ok { | 324 if ok { |
| 311 if capName != "_" { | 325 if capName != "_" { |
| 312 caps[capName] = inp[:split] | 326 caps[capName] = inp[:split] |
| 313 } | 327 } |
