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 }