Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parsing of environment variables to prevent object parsing. #132 #139

Merged
merged 5 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added
- Add support for HJSON. #131
- Add new parse.Config to adjust parsing of varibles returned by a Resolve. #139

### Changed
- Moved internal/parse to parse module. #139
- Add parse.Config to resolvers return. #139

### Deprecated

Expand All @@ -16,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Call Validate on custom slice types. #133
- Call Validate on custom map types. #136
- Disabled object parsing of environment variables. #139

## [0.7.0]

Expand Down
2 changes: 1 addition & 1 deletion flag/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"strings"

"github.com/elastic/go-ucfg"
"github.com/elastic/go-ucfg/internal/parse"
"github.com/elastic/go-ucfg/parse"
)

// NewFlagKeyValue implements the flag.Value interface for
Expand Down
167 changes: 0 additions & 167 deletions internal/parse/parse_test.go

This file was deleted.

16 changes: 9 additions & 7 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package ucfg

import (
"os"

"github.com/elastic/go-ucfg/parse"
)

// Option type implementing additional options to be passed
Expand All @@ -31,7 +33,7 @@ type options struct {
pathSep string
meta *Meta
env []*Config
resolvers []func(name string) (string, error)
resolvers []func(name string) (string, parse.Config, error)
varexp bool
noParse bool

Expand Down Expand Up @@ -104,7 +106,7 @@ func Env(e *Config) Option {
// Resolve option adds a callback used by variable name expansion. The callback
// will be called if a variable can not be resolved from within the actual configuration
// or any of its environments.
func Resolve(fn func(name string) (string, error)) Option {
func Resolve(fn func(name string) (string, parse.Config, error)) Option {
return func(o *options) {
o.resolvers = append(o.resolvers, fn)
}
Expand All @@ -115,12 +117,12 @@ func Resolve(fn func(name string) (string, error)) Option {
var ResolveEnv Option = doResolveEnv

func doResolveEnv(o *options) {
o.resolvers = append(o.resolvers, func(name string) (string, error) {
o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) {
value := os.Getenv(name)
if value == "" {
return "", ErrMissing
return "", parse.EnvConfig, ErrMissing
}
return value, nil
return value, parse.EnvConfig, nil
})
}

Expand All @@ -132,8 +134,8 @@ func doResolveEnv(o *options) {
var ResolveNOOP Option = doResolveNOOP

func doResolveNOOP(o *options) {
o.resolvers = append(o.resolvers, func(name string) (string, error) {
return "${" + name + "}", nil
o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) {
return "${" + name + "}", parse.NoopConfig, nil
})
}

Expand Down
87 changes: 80 additions & 7 deletions internal/parse/parse.go → parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,41 @@ import (
"unicode"
)

// Config allows enabling and disabling parser features.
type Config struct {
Array bool
Object bool
StringDQuote bool
StringSQuote bool
}

// DefaultConfig is the default config with all parser features enabled.
var DefaultConfig = Config{
Array: true,
Object: true,
StringDQuote: true,
StringSQuote: true,
}

// EnvConfig is configuration for parser when the value comes from environmental variable.
var EnvConfig = Config{
Array: true,
Object: false,
StringDQuote: true,
StringSQuote: true,
}

// NoopConfig is configuration for parser that disables all options.
var NoopConfig = Config{
Array: false,
Object: false,
StringDQuote: false,
StringSQuote: false,
}

type flagParser struct {
input string
cfg Config
}

// stopSet definitions for handling unquoted strings
Expand All @@ -42,24 +75,52 @@ const (
//
// The parser implements a superset of JSON, but only a subset of YAML by
// allowing for arrays and objects having a trailing comma. In addition 3
// strings types are supported:
// string types are supported:
//
// 1. single quoted string (no unescaping of any characters)
// 2. double quoted strings (characters are escaped)
// 3. strings without quotes. String parsing stops in
// 3. strings without quotes. String parsing stops at
// special characters like '[]{},:'
//
// In addition, top-level values can be separated by ',' to build arrays
// without having to use [].
func Value(content string) (interface{}, error) {
p := &flagParser{strings.TrimSpace(content)}
return ValueWithConfig(content, DefaultConfig)
}

// ValueWithConfig parses command line arguments, supporting
// boolean, numbers, strings, arrays, objects when enabled.
//
// The parser implements a superset of JSON, but only a subset of YAML by
// allowing for arrays and objects having a trailing comma. In addition 3
// string types are supported:
//
// 1. single quoted string (no unescaping of any characters)
// 2. double quoted strings (characters are escaped)
// 3. strings without quotes. String parsing stops at
// special characters like '[]{},:'
//
// In addition, top-level values can be separated by ',' to build arrays
// without having to use [].
func ValueWithConfig(content string, cfg Config) (interface{}, error) {
p := &flagParser{strings.TrimSpace(content), cfg}
if err := p.validateConfig(); err != nil {
return nil, err
}
v, err := p.parse()
if err != nil {
return nil, fmt.Errorf("%v when parsing '%v'", err.Error(), content)
}
return v, nil
}

func (p *flagParser) validateConfig() error {
if !p.cfg.Array && p.cfg.Object {
return fmt.Errorf("cfg.Array cannot be disabled when cfg.Object is enabled")
}
return nil
}

func (p *flagParser) parse() (interface{}, error) {
var values []interface{}

Expand Down Expand Up @@ -99,13 +160,25 @@ func (p *flagParser) parseValue(stopSet string) (interface{}, error) {

switch in[0] {
case '[':
return p.parseArray()
if p.cfg.Array {
return p.parseArray()
}
return p.parsePrimitive(stopSet)
case '{':
return p.parseObj()
if p.cfg.Object {
return p.parseObj()
}
return p.parsePrimitive(stopSet)
case '"':
return p.parseStringDQuote()
if p.cfg.StringDQuote {
return p.parseStringDQuote()
}
return p.parsePrimitive(stopSet)
case '\'':
return p.parseStringSQuote()
if p.cfg.StringSQuote {
return p.parseStringSQuote()
}
return p.parsePrimitive(stopSet)
default:
return p.parsePrimitive(stopSet)
}
Expand Down
Loading