buildQueryString takes an input query string and performs replacements on each component, returning the resulting query string. This function appends duplicate keys rather than replaces.
(qs string, repl *caddy.Replacer)
| 334 | // the resulting query string. This function appends |
| 335 | // duplicate keys rather than replaces. |
| 336 | func buildQueryString(qs string, repl *caddy.Replacer) string { |
| 337 | var sb strings.Builder |
| 338 | |
| 339 | // first component must be key, which is the same |
| 340 | // as if we just wrote a value in previous iteration |
| 341 | wroteVal := true |
| 342 | |
| 343 | for len(qs) > 0 { |
| 344 | // determine the end of this component, which will be at |
| 345 | // the next equal sign or ampersand, whichever comes first |
| 346 | nextEq, nextAmp := strings.Index(qs, "="), strings.Index(qs, "&") |
| 347 | ampIsNext := nextAmp >= 0 && (nextAmp < nextEq || nextEq < 0) |
| 348 | end := len(qs) // assume no delimiter remains... |
| 349 | if ampIsNext { |
| 350 | end = nextAmp // ...unless ampersand is first... |
| 351 | } else if nextEq >= 0 && (nextEq < nextAmp || nextAmp < 0) { |
| 352 | end = nextEq // ...or unless equal is first. |
| 353 | } |
| 354 | |
| 355 | // consume the component and write the result |
| 356 | comp := qs[:end] |
| 357 | comp, _ = repl.ReplaceFunc(comp, func(name string, val any) (any, error) { |
| 358 | if name == "http.request.uri.query" && wroteVal { |
| 359 | return val, nil // already escaped |
| 360 | } |
| 361 | var valStr string |
| 362 | switch v := val.(type) { |
| 363 | case string: |
| 364 | valStr = v |
| 365 | case fmt.Stringer: |
| 366 | valStr = v.String() |
| 367 | case int: |
| 368 | valStr = strconv.Itoa(v) |
| 369 | default: |
| 370 | valStr = fmt.Sprintf("%+v", v) |
| 371 | } |
| 372 | return url.QueryEscape(valStr), nil |
| 373 | }) |
| 374 | if end < len(qs) { |
| 375 | end++ // consume delimiter |
| 376 | } |
| 377 | qs = qs[end:] |
| 378 | |
| 379 | // if previous iteration wrote a value, |
| 380 | // that means we are writing a key |
| 381 | if wroteVal { |
| 382 | if sb.Len() > 0 && len(comp) > 0 { |
| 383 | sb.WriteRune('&') |
| 384 | } |
| 385 | } else { |
| 386 | sb.WriteRune('=') |
| 387 | } |
| 388 | sb.WriteString(comp) |
| 389 | |
| 390 | // remember for the next iteration that we just wrote a value, |
| 391 | // which means the next iteration MUST write a key |
| 392 | wroteVal = ampIsNext |
| 393 | } |
no test coverage detected