Skip to content

Commit

Permalink
Allow mapping imports to be disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffail committed Sep 16, 2021
1 parent 46c86d0 commit 733f957
Show file tree
Hide file tree
Showing 43 changed files with 476 additions and 207 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.

- Fields `cache_control`, `content_disposition`, `content_language` and `website_redirect_location` added to the `aws_s3` output.
- Field `cors.enabled` and `cors.allowed_origins` added to the server wide `http` config.
- Allow mapping imports in Bloblang environments to be disabled.

### Fixed

Expand Down
71 changes: 44 additions & 27 deletions internal/bloblang/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import (
// Environment provides an isolated Bloblang environment where the available
// features, functions and methods can be modified.
type Environment struct {
functions *query.FunctionSet
methods *query.MethodSet
pCtx parser.Context
}

// GlobalEnvironment returns the global default environment. Modifying this
Expand All @@ -20,8 +19,7 @@ type Environment struct {
// changes.
func GlobalEnvironment() *Environment {
return &Environment{
functions: query.AllFunctions,
methods: query.AllMethods,
pCtx: parser.GlobalContext(),
}
}

Expand All @@ -44,8 +42,7 @@ func NewEnvironment() *Environment {
// empty, where no functions or methods are initially available.
func NewEmptyEnvironment() *Environment {
return &Environment{
functions: query.NewFunctionSet(),
methods: query.NewMethodSet(),
pCtx: parser.EmptyContext(),
}
}

Expand All @@ -55,12 +52,7 @@ func NewEmptyEnvironment() *Environment {
// When a parsing error occurs the returned error will be a *parser.Error type,
// which allows you to gain positional and structured error messages.
func (e *Environment) NewField(expr string) (*field.Expression, error) {
pCtx := parser.GlobalContext()
if e != nil {
pCtx.Functions = e.functions
pCtx.Methods = e.methods
}
f, err := parser.ParseField(pCtx, expr)
f, err := parser.ParseField(e.pCtx, expr)
if err != nil {
return nil, err
}
Expand All @@ -73,13 +65,8 @@ func (e *Environment) NewField(expr string) (*field.Expression, error) {
// When a parsing error occurs the error will be the type *parser.Error, which
// gives access to the line and column where the error occurred, as well as a
// method for creating a well formatted error message.
func (e *Environment) NewMapping(path, blobl string) (*mapping.Executor, error) {
pCtx := parser.GlobalContext()
if e != nil {
pCtx.Functions = e.functions
pCtx.Methods = e.methods
}
exec, err := parser.ParseMapping(pCtx, path, blobl)
func (e *Environment) NewMapping(blobl string) (*mapping.Executor, error) {
exec, err := parser.ParseMapping(e.pCtx, blobl)
if err != nil {
return nil, err
}
Expand All @@ -97,37 +84,67 @@ func (e *Environment) NewMapping(path, blobl string) (*mapping.Executor, error)
// that is independent of the source.
func (e *Environment) Deactivated() *Environment {
return &Environment{
functions: e.functions.Deactivated(),
methods: e.methods.Deactivated(),
pCtx: e.pCtx.Deactivated(),
}
}

// RegisterMethod adds a new Bloblang method to the environment.
func (e *Environment) RegisterMethod(spec query.MethodSpec, ctor query.MethodCtor) error {
return e.methods.Add(spec, ctor)
return e.pCtx.Methods.Add(spec, ctor)
}

// RegisterFunction adds a new Bloblang function to the environment.
func (e *Environment) RegisterFunction(spec query.FunctionSpec, ctor query.FunctionCtor) error {
return e.functions.Add(spec, ctor)
return e.pCtx.Functions.Add(spec, ctor)
}

// WithImporter returns a new environment where Bloblang imports are performed
// from a new importer.
func (e *Environment) WithImporter(importer parser.Importer) *Environment {
nextCtx := e.pCtx.WithImporter(importer)
return &Environment{
pCtx: nextCtx,
}
}

// WithImporterRelativeToFile returns a new environment where any relative
// imports will be made from the directory of the provided file path. The
// provided path can itself be relative (to the current importer directory) or
// absolute.
func (e *Environment) WithImporterRelativeToFile(filePath string) *Environment {
nextCtx := e.pCtx.WithImporterRelativeToFile(filePath)
return &Environment{
pCtx: nextCtx,
}
}

// WithDisabledImports returns a version of the environment where imports within
// mappings are disabled entirely. This prevents mappings from accessing files
// from the host disk.
func (e *Environment) WithDisabledImports() *Environment {
return &Environment{
pCtx: e.pCtx.DisabledImports(),
}
}

// WithoutMethods returns a copy of the environment but with a variadic list of
// method names removed. Instantiation of these removed methods within a mapping
// will cause errors at parse time.
func (e *Environment) WithoutMethods(names ...string) *Environment {
nextCtx := e.pCtx
nextCtx.Methods = e.pCtx.Methods.Without(names...)
return &Environment{
functions: e.functions,
methods: e.methods.Without(names...),
pCtx: nextCtx,
}
}

// WithoutFunctions returns a copy of the environment but with a variadic list
// of function names removed. Instantiation of these removed functions within a
// mapping will cause errors at parse time.
func (e *Environment) WithoutFunctions(names ...string) *Environment {
nextCtx := e.pCtx
nextCtx.Functions = e.pCtx.Functions.Without(names...)
return &Environment{
functions: e.functions.Without(names...),
methods: e.methods,
pCtx: nextCtx,
}
}
4 changes: 2 additions & 2 deletions internal/bloblang/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ func NewField(expr string) (*field.Expression, error) {
//
// When a parsing error occurs the returned error may be a *parser.Error type,
// which allows you to gain positional and structured error messages.
func NewMapping(path, expr string) (*mapping.Executor, error) {
return GlobalEnvironment().NewMapping(path, expr)
func NewMapping(expr string) (*mapping.Executor, error) {
return GlobalEnvironment().NewMapping(expr)
}
4 changes: 2 additions & 2 deletions internal/bloblang/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestMappings(t *testing.T) {
for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
m, err := NewMapping("", test.mapping)
m, err := NewMapping(test.mapping)
require.NoError(t, err)

assert.Equal(t, test.assignmentTargets, m.AssignmentTargets())
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestMappingParallelExecution(t *testing.T) {
for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
m, err := NewMapping("", test.mapping)
m, err := NewMapping(test.mapping)
require.NoError(t, err)

startChan := make(chan struct{})
Expand Down
179 changes: 179 additions & 0 deletions internal/bloblang/parser/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package parser

import (
"errors"
"io"
"os"
"path/filepath"

"github.com/Jeffail/benthos/v3/internal/bloblang/query"
)

// Context contains context used throughout a Bloblang parser for
// accessing function and method constructors.
type Context struct {
Functions *query.FunctionSet
Methods *query.MethodSet
namedContext *namedContext
importer Importer
}

// EmptyContext returns a parser context with no functions, methods or import
// capabilities.
func EmptyContext() Context {
return Context{
Functions: query.NewFunctionSet(),
Methods: query.NewMethodSet(),
importer: newOSImporter(),
}
}

// GlobalContext returns a parser context with globally defined functions and
// methods.
func GlobalContext() Context {
return Context{
Functions: query.AllFunctions,
Methods: query.AllMethods,
importer: newOSImporter(),
}
}

type namedContext struct {
name string
next *namedContext
}

// WithNamedContext returns a Context with a named execution context.
func (pCtx Context) WithNamedContext(name string) Context {
next := pCtx.namedContext
pCtx.namedContext = &namedContext{name, next}
return pCtx
}

// HasNamedContext returns true if a given name exists as a named context.
func (pCtx Context) HasNamedContext(name string) bool {
tmp := pCtx.namedContext
for tmp != nil {
if tmp.name == name {
return true
}
tmp = tmp.next
}
return false
}

// InitFunction attempts to initialise a function from the available
// constructors of the parser context.
func (pCtx Context) InitFunction(name string, args *query.ParsedParams) (query.Function, error) {
return pCtx.Functions.Init(name, args)
}

// InitMethod attempts to initialise a method from the available constructors of
// the parser context.
func (pCtx Context) InitMethod(name string, target query.Function, args *query.ParsedParams) (query.Function, error) {
return pCtx.Methods.Init(name, target, args)
}

// WithImporter returns a Context where imports are made from the provided
// Importer implementation.
func (pCtx Context) WithImporter(importer Importer) Context {
pCtx.importer = importer
return pCtx
}

// WithImporterRelativeToFile returns a Context where any relative imports will
// be made from the directory of the provided file path. The provided path can
// itself be relative (to the current importer directory) or absolute.
func (pCtx Context) WithImporterRelativeToFile(pathStr string) Context {
pCtx.importer = pCtx.importer.RelativeToFile(pathStr)
return pCtx
}

// Deactivated returns a version of the parser context where all functions and
// methods exist but can no longer be instantiated. This means it's possible to
// parse and validate mappings but not execute them. If the context also has an
// importer then it will also be replaced with an implementation that always
// returns empty files.
func (pCtx Context) Deactivated() Context {
nextCtx := pCtx
nextCtx.Functions = pCtx.Functions.Deactivated()
nextCtx.Methods = pCtx.Methods.Deactivated()
return nextCtx
}

// DisabledImports returns a version of the parser context where file imports
// are entirely disabled. Any import statement within parsed mappings will
// return parse errors explaining that file imports are disabled.
func (pCtx Context) DisabledImports() Context {
nextCtx := pCtx
nextCtx.importer = disabledImporter{}
return nextCtx
}

//------------------------------------------------------------------------------

// Importer represents a repository of bloblang files that can be imported by
// mappings. It's possible for mappings to import files using relative paths, if
// the import is from a mapping which was itself imported then the path should
// be interpretted as relative to that file.
type Importer interface {
// Import a file from a relative or absolute path.
Import(pathStr string) ([]byte, error)

// Derive a new importer where relative import paths are resolved from the
// directory of the provided file path. The provided path could be absolute,
// or relative itself in which case it should be resolved from the
// pre-existing relative directory.
RelativeToFile(filePath string) Importer
}

//------------------------------------------------------------------------------

type osImporter struct {
relativePath string
}

func newOSImporter() Importer {
pwd, _ := os.Getwd()
return &osImporter{
relativePath: pwd,
}
}

func (i *osImporter) Import(pathStr string) ([]byte, error) {
if !filepath.IsAbs(pathStr) {
pathStr = filepath.Join(i.relativePath, pathStr)
}

f, err := os.Open(pathStr)
if err != nil {
return nil, err
}
return io.ReadAll(f)
}

func (i *osImporter) RelativeToFile(filePath string) Importer {
dir := filepath.Dir(filePath)
if dir == "" || dir == "." {
return i
}

pathStr := filepath.Dir(filePath)
if !filepath.IsAbs(pathStr) && i.relativePath != "" {
pathStr = filepath.Join(i.relativePath, pathStr)
}

newI := *i
newI.relativePath = pathStr
return &newI
}

type disabledImporter struct{}

func (d disabledImporter) Import(pathStr string) ([]byte, error) {
return nil, errors.New("imports are disabled in this context")
}

func (d disabledImporter) RelativeToFile(filePath string) Importer {
return d
}
Loading

0 comments on commit 733f957

Please sign in to comment.