github.com/Jeffail/benthos/v3@v3.65.0/lib/util/text/function_vars.go (about)

     1  package text
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"os"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/Jeffail/benthos/v3/lib/types"
    14  	"github.com/Jeffail/gabs/v2"
    15  	"github.com/gofrs/uuid"
    16  )
    17  
    18  //------------------------------------------------------------------------------
    19  
    20  // Message is an interface type to be given to a function interpolator, it
    21  // allows the function to resolve fields and metadata from a message.
    22  type Message interface {
    23  	Get(p int) types.Part
    24  	Len() int
    25  }
    26  
    27  //------------------------------------------------------------------------------
    28  
    29  func jsonFieldFunction(msg Message, index int, arg string) []byte {
    30  	part := index
    31  	if argIndex := strings.LastIndex(arg, ","); argIndex > 0 && len(arg) > argIndex {
    32  		partB, err := strconv.ParseInt(arg[argIndex+1:], 10, 64)
    33  		if err == nil {
    34  			part = int(partB)
    35  		}
    36  		arg = arg[:argIndex]
    37  	}
    38  	jPart, err := msg.Get(part).JSON()
    39  	if err != nil {
    40  		return []byte("null")
    41  	}
    42  	gPart := gabs.Wrap(jPart)
    43  	if len(arg) > 0 {
    44  		gPart = gPart.Path(arg)
    45  	}
    46  	switch t := gPart.Data().(type) {
    47  	case string:
    48  		return []byte(t)
    49  	case nil:
    50  		return []byte(`null`)
    51  	}
    52  	return gPart.Bytes()
    53  }
    54  
    55  func metadataFunction(msg Message, index int, arg string) []byte {
    56  	part := index
    57  	if argIndex := strings.LastIndex(arg, ","); argIndex > 0 && len(arg) > argIndex {
    58  		partB, err := strconv.ParseInt(arg[argIndex+1:], 10, 64)
    59  		if err == nil {
    60  			part = int(partB)
    61  		}
    62  		arg = arg[:argIndex]
    63  	}
    64  	if arg == "" {
    65  		return []byte("")
    66  	}
    67  	meta := msg.Get(part).Metadata()
    68  	return []byte(meta.Get(arg))
    69  }
    70  
    71  func metadataMapFunction(msg Message, index int, arg string) []byte {
    72  	part := index
    73  	if len(arg) > 0 {
    74  		partB, err := strconv.ParseInt(arg, 10, 64)
    75  		if err == nil {
    76  			part = int(partB)
    77  		}
    78  	}
    79  	kvs := map[string]string{}
    80  	msg.Get(part).Metadata().Iter(func(k, v string) error {
    81  		kvs[k] = v
    82  		return nil
    83  	})
    84  	result, err := json.Marshal(kvs)
    85  	if err != nil {
    86  		return []byte("")
    87  	}
    88  	return result
    89  }
    90  
    91  func errorFunction(msg Message, index int, arg string) []byte {
    92  	part := index
    93  	if len(arg) > 0 {
    94  		partB, err := strconv.ParseInt(arg, 10, 64)
    95  		if err == nil {
    96  			part = int(partB)
    97  		}
    98  	}
    99  	return []byte(msg.Get(part).Metadata().Get(types.FailFlagKey))
   100  }
   101  
   102  func contentFunction(msg Message, index int, arg string) []byte {
   103  	part := index
   104  	if len(arg) > 0 {
   105  		partB, err := strconv.ParseInt(arg, 10, 64)
   106  		if err == nil {
   107  			part = int(partB)
   108  		}
   109  	}
   110  	return msg.Get(part).Get()
   111  }
   112  
   113  //------------------------------------------------------------------------------
   114  
   115  var (
   116  	functionRegex        = regexp.MustCompile(`\${![a-z0-9_]+(:[^}]+)?}`)
   117  	escapedFunctionRegex = regexp.MustCompile(`\${({![a-z0-9_]+(:[^}]+)?})}`)
   118  
   119  	counters    = map[string]uint64{}
   120  	countersMux = &sync.Mutex{}
   121  
   122  	functionVars = map[string]func(msg Message, index int, arg string) []byte{
   123  		"timestamp_unix_nano": func(_ Message, _ int, arg string) []byte {
   124  			return []byte(strconv.FormatInt(time.Now().UnixNano(), 10))
   125  		},
   126  		"timestamp_unix": func(_ Message, _ int, arg string) []byte {
   127  			tNow := time.Now()
   128  			precision, _ := strconv.ParseInt(arg, 10, 64)
   129  			tStr := strconv.FormatInt(tNow.Unix(), 10)
   130  			if precision > 0 {
   131  				nanoStr := strconv.FormatInt(int64(tNow.Nanosecond()), 10)
   132  				if lNano := int64(len(nanoStr)); precision >= lNano {
   133  					precision = lNano - 1
   134  				}
   135  				tStr = tStr + "." + nanoStr[:precision]
   136  			}
   137  			return []byte(tStr)
   138  		},
   139  		"timestamp": func(_ Message, _ int, arg string) []byte {
   140  			if arg == "" {
   141  				arg = "Mon Jan 2 15:04:05 -0700 MST 2006"
   142  			}
   143  			return []byte(time.Now().Format(arg))
   144  		},
   145  		"timestamp_utc": func(_ Message, _ int, arg string) []byte {
   146  			if arg == "" {
   147  				arg = "Mon Jan 2 15:04:05 -0700 MST 2006"
   148  			}
   149  			return []byte(time.Now().In(time.UTC).Format(arg))
   150  		},
   151  		"hostname": func(_ Message, _ int, arg string) []byte {
   152  			hn, _ := os.Hostname()
   153  			return []byte(hn)
   154  		},
   155  		"echo": func(_ Message, _ int, arg string) []byte {
   156  			return []byte(arg)
   157  		},
   158  		"count": func(_ Message, _ int, arg string) []byte {
   159  			countersMux.Lock()
   160  			defer countersMux.Unlock()
   161  
   162  			var count uint64
   163  			var exists bool
   164  
   165  			if count, exists = counters[arg]; exists {
   166  				count++
   167  			} else {
   168  				count = 1
   169  			}
   170  			counters[arg] = count
   171  
   172  			return []byte(strconv.FormatUint(count, 10))
   173  		},
   174  		"error":                errorFunction,
   175  		"content":              contentFunction,
   176  		"json_field":           jsonFieldFunction,
   177  		"metadata":             metadataFunction,
   178  		"metadata_json_object": metadataMapFunction,
   179  		"batch_size": func(m Message, _ int, _ string) []byte {
   180  			return strconv.AppendInt(nil, int64(m.Len()), 10)
   181  		},
   182  		"uuid_v4": func(_ Message, _ int, _ string) []byte {
   183  			u4, err := uuid.NewV4()
   184  			if err != nil {
   185  				panic(err)
   186  			}
   187  			return []byte(u4.String())
   188  		},
   189  	}
   190  )
   191  
   192  // ContainsFunctionVariables returns true if inBytes contains function variable
   193  // replace patterns.
   194  func ContainsFunctionVariables(inBytes []byte) bool {
   195  	return functionRegex.Find(inBytes) != nil || escapedFunctionRegex.Find(inBytes) != nil
   196  }
   197  
   198  func escapeBytes(in []byte) []byte {
   199  	quoted := strconv.Quote(string(in))
   200  	if len(quoted) < 3 {
   201  		return in
   202  	}
   203  	return []byte(quoted[1 : len(quoted)-1])
   204  }
   205  
   206  // ReplaceFunctionVariables will search a blob of data for the pattern
   207  // `${!foo}`, where `foo` is a function name.
   208  //
   209  // For each aforementioned pattern found in the blob the contents of the
   210  // respective function will be run and will replace the pattern.
   211  //
   212  // Some functions are able to extract contents and metadata from a message, and
   213  // so a message must be supplied.
   214  func ReplaceFunctionVariables(msg Message, inBytes []byte) []byte {
   215  	return ReplaceFunctionVariablesFor(msg, 0, inBytes)
   216  }
   217  
   218  // ReplaceFunctionVariablesFor will search a blob of data for the pattern
   219  // `${!foo}`, where `foo` is a function name.
   220  //
   221  // For each aforementioned pattern found in the blob the contents of the
   222  // respective function will be run and will replace the pattern.
   223  //
   224  // Some functions are able to extract contents and metadata from a message, and
   225  // so a message must be supplied along with the specific index of the message
   226  // part that this function should be resolved for.
   227  func ReplaceFunctionVariablesFor(msg Message, index int, inBytes []byte) []byte {
   228  	return replaceFunctionVariables(msg, index, false, inBytes)
   229  }
   230  
   231  // ReplaceFunctionVariablesEscaped will search a blob of data for the pattern
   232  // `${!foo}`, where `foo` is a function name.
   233  //
   234  // For each aforementioned pattern found in the blob the contents of the
   235  // respective function will be run and will replace the pattern.
   236  //
   237  // The contents of the swapped pattern is escaped such that it can be safely
   238  // injected within the contents of a JSON object.
   239  //
   240  // Some functions are able to extract contents and metadata from a message, and
   241  // so a message must be supplied.
   242  func ReplaceFunctionVariablesEscaped(msg Message, inBytes []byte) []byte {
   243  	return ReplaceFunctionVariablesEscapedFor(msg, 0, inBytes)
   244  }
   245  
   246  // ReplaceFunctionVariablesEscapedFor will search a blob of data for the pattern
   247  // `${!foo}`, where `foo` is a function name.
   248  //
   249  // For each aforementioned pattern found in the blob the contents of the
   250  // respective function will be run and will replace the pattern.
   251  //
   252  // The contents of the swapped pattern is escaped such that it can be safely
   253  // injected within the contents of a JSON object.
   254  //
   255  // Some functions are able to extract contents and metadata from a message, and
   256  // so a message must be supplied along with the specific index of the message
   257  // part that this function should be resolved for.
   258  func ReplaceFunctionVariablesEscapedFor(msg Message, index int, inBytes []byte) []byte {
   259  	return replaceFunctionVariables(msg, index, true, inBytes)
   260  }
   261  
   262  func replaceFunctionVariables(
   263  	msg Message, index int, escape bool, inBytes []byte,
   264  ) []byte {
   265  	replaced := functionRegex.ReplaceAllFunc(inBytes, func(content []byte) []byte {
   266  		if len(content) > 4 {
   267  			if colonIndex := bytes.IndexByte(content, ':'); colonIndex == -1 {
   268  				if ftor, exists := functionVars[string(content[3:len(content)-1])]; exists {
   269  					if escape {
   270  						return escapeBytes(ftor(msg, index, ""))
   271  					}
   272  					return ftor(msg, index, "")
   273  				}
   274  			} else {
   275  				argVal := string(content[colonIndex+1 : len(content)-1])
   276  				if ftor, exists := functionVars[string(content[3:colonIndex])]; exists {
   277  					if escape {
   278  						return escapeBytes(ftor(msg, index, argVal))
   279  					}
   280  					return ftor(msg, index, argVal)
   281  				}
   282  			}
   283  		}
   284  		return content
   285  	})
   286  	replaced = escapedFunctionRegex.ReplaceAll(replaced, []byte(`$$$1`))
   287  	return replaced
   288  }
   289  
   290  //------------------------------------------------------------------------------