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  }