storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/madmin/parse-kv.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package madmin 19 20 import ( 21 "bufio" 22 "bytes" 23 "fmt" 24 "sort" 25 "strings" 26 "unicode" 27 ) 28 29 // KV - is a shorthand of each key value. 30 type KV struct { 31 Key string `json:"key"` 32 Value string `json:"value"` 33 } 34 35 // KVS - is a shorthand for some wrapper functions 36 // to operate on list of key values. 37 type KVS []KV 38 39 // Empty - return if kv is empty 40 func (kvs KVS) Empty() bool { 41 return len(kvs) == 0 42 } 43 44 // Set sets a value, if not sets a default value. 45 func (kvs *KVS) Set(key, value string) { 46 for i, kv := range *kvs { 47 if kv.Key == key { 48 (*kvs)[i] = KV{ 49 Key: key, 50 Value: value, 51 } 52 return 53 } 54 } 55 *kvs = append(*kvs, KV{ 56 Key: key, 57 Value: value, 58 }) 59 } 60 61 // Get - returns the value of a key, if not found returns empty. 62 func (kvs KVS) Get(key string) string { 63 v, ok := kvs.Lookup(key) 64 if ok { 65 return v 66 } 67 return "" 68 } 69 70 // Lookup - lookup a key in a list of KVS 71 func (kvs KVS) Lookup(key string) (string, bool) { 72 for _, kv := range kvs { 73 if kv.Key == key { 74 return kv.Value, true 75 } 76 } 77 return "", false 78 } 79 80 // Target signifies an individual target 81 type Target struct { 82 SubSystem string `json:"subSys"` 83 KVS KVS `json:"kvs"` 84 } 85 86 // Standard config keys and values. 87 const ( 88 EnableKey = "enable" 89 CommentKey = "comment" 90 91 // Enable values 92 EnableOn = "on" 93 EnableOff = "off" 94 ) 95 96 // HasSpace - returns if given string has space. 97 func HasSpace(s string) bool { 98 for _, r := range s { 99 if unicode.IsSpace(r) { 100 return true 101 } 102 } 103 return false 104 } 105 106 // Constant separators 107 const ( 108 SubSystemSeparator = `:` 109 KvSeparator = `=` 110 KvComment = `#` 111 KvSpaceSeparator = ` ` 112 KvNewline = "\n" 113 KvDoubleQuote = `"` 114 KvSingleQuote = `'` 115 116 Default = `_` 117 ) 118 119 // SanitizeValue - this function is needed, to trim off single or double quotes, creeping into the values. 120 func SanitizeValue(v string) string { 121 v = strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(v), KvDoubleQuote), KvDoubleQuote) 122 return strings.TrimSuffix(strings.TrimPrefix(v, KvSingleQuote), KvSingleQuote) 123 } 124 125 // KvFields - converts an input string of form "k1=v1 k2=v2" into 126 // fields of ["k1=v1", "k2=v2"], the tokenization of each `k=v` 127 // happens with the right number of input keys, if keys 128 // input is empty returned value is empty slice as well. 129 func KvFields(input string, keys []string) []string { 130 var valueIndexes []int 131 for _, key := range keys { 132 i := strings.Index(input, key+KvSeparator) 133 if i == -1 { 134 continue 135 } 136 valueIndexes = append(valueIndexes, i) 137 } 138 139 sort.Ints(valueIndexes) 140 var fields = make([]string, len(valueIndexes)) 141 for i := range valueIndexes { 142 j := i + 1 143 if j < len(valueIndexes) { 144 fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]]) 145 } else { 146 fields[i] = strings.TrimSpace(input[valueIndexes[i]:]) 147 } 148 } 149 return fields 150 } 151 152 // ParseTarget - adds new targets, by parsing the input string s. 153 func ParseTarget(s string, help Help) (*Target, error) { 154 inputs := strings.SplitN(s, KvSpaceSeparator, 2) 155 if len(inputs) <= 1 { 156 return nil, fmt.Errorf("invalid number of arguments '%s'", s) 157 } 158 159 subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2) 160 if len(subSystemValue) == 0 { 161 return nil, fmt.Errorf("invalid number of arguments %s", s) 162 } 163 164 if help.SubSys != subSystemValue[0] { 165 return nil, fmt.Errorf("unknown sub-system %s", subSystemValue[0]) 166 } 167 168 var kvs = KVS{} 169 var prevK string 170 for _, v := range KvFields(inputs[1], help.Keys()) { 171 kv := strings.SplitN(v, KvSeparator, 2) 172 if len(kv) == 0 { 173 continue 174 } 175 if len(kv) == 1 && prevK != "" { 176 value := strings.Join([]string{ 177 kvs.Get(prevK), 178 SanitizeValue(kv[0]), 179 }, KvSpaceSeparator) 180 kvs.Set(prevK, value) 181 continue 182 } 183 if len(kv) == 2 { 184 prevK = kv[0] 185 kvs.Set(prevK, SanitizeValue(kv[1])) 186 continue 187 } 188 return nil, fmt.Errorf("value for key '%s' cannot be empty", kv[0]) 189 } 190 191 return &Target{ 192 SubSystem: inputs[0], 193 KVS: kvs, 194 }, nil 195 } 196 197 // ParseSubSysTarget - parse a sub-system target 198 func ParseSubSysTarget(buf []byte, help Help) (target *Target, err error) { 199 bio := bufio.NewScanner(bytes.NewReader(buf)) 200 if bio.Scan() { 201 return ParseTarget(bio.Text(), help) 202 } 203 return nil, bio.Err() 204 }