istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/validation/header_value_validator.go (about) 1 // Copyright Istio Authors 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 validation 16 17 import ( 18 "errors" 19 "fmt" 20 ) 21 22 type ParserState int32 23 24 const ( 25 LiteralParserState ParserState = iota // processing literal data 26 VariableNameParserState // consuming a %VAR% name 27 ExpectArrayParserState // expect starting [ in %VAR([...])% 28 ExpectStringParserState // expect starting " in array of strings 29 StringParserState // consuming an array element string 30 ExpectArrayDelimiterOrEndParserState // expect array delimiter (,) or end of array (]) 31 ExpectArgsEndParserState // expect closing ) in %VAR(...)% 32 ExpectVariableEndParserState // expect closing % in %VAR(...)% 33 ) 34 35 // validateHeaderValue is golang port version of 36 // https://github.com/envoyproxy/envoy/blob/master/source/common/router/header_parser.cc#L73 37 func validateHeaderValue(headerValue string) error { 38 if headerValue == "" { 39 return nil 40 } 41 42 var ( 43 pos = 0 44 state = LiteralParserState 45 ) 46 47 for pos < len(headerValue) { 48 ch := headerValue[pos] 49 hasNextCh := (pos + 1) < len(headerValue) 50 51 switch state { 52 case LiteralParserState: 53 // Searching for start of %VARIABLE% expression. 54 if ch != '%' { 55 break 56 } 57 58 if !hasNextCh { 59 return errors.New("invalid header configuration. Un-escaped %") 60 } 61 62 if headerValue[pos+1] == '%' { 63 // Escaped %, skip next character. 64 pos++ 65 break 66 } 67 68 // Un-escaped %: start of variable name. Create a formatter for preceding characters, if any. 69 state = VariableNameParserState 70 71 case VariableNameParserState: 72 // Consume "VAR" from "%VAR%" or "%VAR(...)%" 73 if ch == '%' { 74 state = LiteralParserState 75 break 76 } 77 78 if ch == '(' { 79 // Variable with arguments, search for start of arg array. 80 state = ExpectArrayParserState 81 } 82 83 case ExpectArrayParserState: 84 // Skip over whitespace searching for the start of JSON array args. 85 if ch == '[' { 86 // Search for first argument string 87 state = ExpectStringParserState 88 } else if !isSpace(ch) { 89 // Consume it as a string argument. 90 state = StringParserState 91 } 92 93 case ExpectArrayDelimiterOrEndParserState: 94 // Skip over whitespace searching for a comma or close bracket. 95 if ch == ',' { 96 state = ExpectStringParserState 97 } else if ch == ']' { 98 state = ExpectArgsEndParserState 99 } else if !isSpace(ch) { 100 return errors.New("invalid header configuration. Expecting ',', ']', or whitespace") 101 } 102 103 case ExpectStringParserState: 104 // Skip over whitespace looking for the starting quote of a JSON string. 105 if ch == '"' { 106 state = StringParserState 107 } else if !isSpace(ch) { 108 return errors.New("invalid header configuration. Expecting '\"'") 109 } 110 111 case StringParserState: 112 // Consume a JSON string (ignoring backslash-escaped chars). 113 if ch == '\\' { 114 if !hasNextCh { 115 return errors.New("invalid header configuration. Un-terminated backslash in JSON string") 116 } 117 118 // Skip escaped char. 119 pos++ 120 } else if ch == ')' { 121 state = ExpectVariableEndParserState 122 } else if ch == '"' { 123 state = ExpectArrayDelimiterOrEndParserState 124 } 125 126 case ExpectArgsEndParserState: 127 // Search for the closing paren of a %VAR(...)% expression. 128 if ch == ')' { 129 state = ExpectVariableEndParserState 130 } else if !isSpace(ch) { 131 return errors.New("invalid header configuration. Expecting ')' or whitespace after '{}', but found '{}'") 132 } 133 134 case ExpectVariableEndParserState: 135 // Search for closing % of a %VAR(...)% expression 136 if ch == '%' { 137 state = LiteralParserState 138 break 139 } 140 141 if !isSpace(ch) { 142 return errors.New("invalid header configuration. Expecting '%' or whitespace after") 143 } 144 } 145 146 pos++ 147 } 148 149 if state != LiteralParserState { 150 // Parsing terminated mid-variable. 151 return fmt.Errorf("invalid header configuration. Un-terminated variable expression '%s'", headerValue) 152 } 153 154 return nil 155 } 156 157 func isSpace(chr byte) bool { 158 return chr == ' ' 159 }