validateCondValue checks that a cond string is a well-formed @if(...) or @filter(...) clause with balanced parentheses and no trailing content. This prevents DQL injection via crafted cond values that close the parenthesized expression and append additional query blocks.
(cond string)
| 769 | // via crafted cond values that close the parenthesized expression and append additional |
| 770 | // query blocks. |
| 771 | func validateCondValue(cond string) error { |
| 772 | cond = strings.TrimSpace(cond) |
| 773 | if cond == "" { |
| 774 | return nil |
| 775 | } |
| 776 | |
| 777 | lower := strings.ToLower(cond) |
| 778 | if !strings.HasPrefix(lower, "@if") && !strings.HasPrefix(lower, "@filter") { |
| 779 | return errors.Errorf("invalid cond value: must start with @if( or @filter(") |
| 780 | } |
| 781 | |
| 782 | // Strip the directive prefix and verify the remainder (after optional whitespace) starts with '('. |
| 783 | prefix := "@if" |
| 784 | if strings.HasPrefix(lower, "@filter") { |
| 785 | prefix = "@filter" |
| 786 | } |
| 787 | rest := strings.TrimSpace(cond[len(prefix):]) |
| 788 | if len(rest) == 0 || rest[0] != '(' { |
| 789 | return errors.Errorf("invalid cond value: must start with @if( or @filter(") |
| 790 | } |
| 791 | // Rebuild cond without the space so the paren-balancing logic works on the normalized form. |
| 792 | cond = prefix + rest |
| 793 | |
| 794 | openIdx := strings.Index(cond, "(") |
| 795 | if openIdx == -1 { |
| 796 | return errors.Errorf("invalid cond value: missing opening parenthesis") |
| 797 | } |
| 798 | |
| 799 | depth := 0 |
| 800 | inString := false |
| 801 | escaped := false |
| 802 | closingIdx := -1 |
| 803 | |
| 804 | for i := openIdx; i < len(cond); i++ { |
| 805 | ch := cond[i] |
| 806 | if escaped { |
| 807 | escaped = false |
| 808 | continue |
| 809 | } |
| 810 | if ch == '\\' { |
| 811 | escaped = true |
| 812 | continue |
| 813 | } |
| 814 | if ch == '"' { |
| 815 | inString = !inString |
| 816 | continue |
| 817 | } |
| 818 | if inString { |
| 819 | continue |
| 820 | } |
| 821 | if ch == '(' { |
| 822 | depth++ |
| 823 | } else if ch == ')' { |
| 824 | depth-- |
| 825 | if depth == 0 { |
| 826 | closingIdx = i |
| 827 | break |
| 828 | } |