From c4dda89f07ce26c9ace2971777fd447f7b847710 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 19:46:25 +0200 Subject: [PATCH] Add escape/unescape for Logstash strings Based on https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#_escape_sequences --- ast/astutil/escape.go | 69 ++++++++++++++++++++++++++++++++ ast/astutil/escape_test.go | 81 ++++++++++++++++++++++++++++++++++++++ go.sum | 2 + 3 files changed, 152 insertions(+) create mode 100644 ast/astutil/escape.go create mode 100644 ast/astutil/escape_test.go diff --git a/ast/astutil/escape.go b/ast/astutil/escape.go new file mode 100644 index 0000000..1c42b0d --- /dev/null +++ b/ast/astutil/escape.go @@ -0,0 +1,69 @@ +package astutil + +import ( + "regexp" +) + +var unescapeRe = regexp.MustCompile(`\\.`) + +// Unescape converts an input containing escape sequences as understood by +// Logstash (https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#_escape_sequences) +// to an unescaped string. +// +// Unescaped character sequences: +// +// \r : carriage return (ASCII 13) +// \n : new line (ASCII 10) +// \t : tab (ASCII 9) +// \\ : backslash (ASCII 92) +// \" : double quote (ASCII 34) +// \' : single quote (ASCII 39) +// +// Based on: +// https://github.com/elastic/logstash/blob/e9c9865f4066b54048f8d708612a72d25e2fe5d9/logstash-core/lib/logstash/config/string_escape.rb +func Unescape(value string) string { + return unescapeRe.ReplaceAllStringFunc(value, func(s string) string { + switch s[1] { + case '"', '\'', '\\': + return string(s[1]) + case 'n': + return "\n" + case 'r': + return "\r" + case 't': + return "\t" + default: + return s + } + }) +} + +var escapeRe = regexp.MustCompile(`(?s:.)`) + +// Escape converts an input to an escaped representation as understood by +// Logstash (https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#_escape_sequences). +// +// Escaped characters: +// +// \r : carriage return (ASCII 13) +// \n : new line (ASCII 10) +// \t : tab (ASCII 9) +// \ : backslash (ASCII 92) +// " : double quote (ASCII 34) +// ' : single quote (ASCII 39) +func Escape(value string) string { + return escapeRe.ReplaceAllStringFunc(value, func(s string) string { + switch s { + case `"`, `'`, `\`: + return `\` + s + case "\n": + return `\n` + case "\r": + return `\r` + case "\t": + return `\t` + default: + return s + } + }) +} diff --git a/ast/astutil/escape_test.go b/ast/astutil/escape_test.go new file mode 100644 index 0000000..7d19a1c --- /dev/null +++ b/ast/astutil/escape_test.go @@ -0,0 +1,81 @@ +package astutil_test + +import ( + "testing" + + "github.com/breml/logstash-config/ast/astutil" +) + +var tt = []struct { + name string + escaped string + unescaped string +}{ + { + name: "carriage return (ASCII 13)", + escaped: `\r`, + unescaped: "\r", + }, + { + name: "new line (ASCII 10)", + escaped: `\n`, + unescaped: "\n", + }, + { + name: "tab (ASCII 9)", + escaped: `\t`, + unescaped: "\t", + }, + { + name: "backslash (ASCII 92)", + escaped: `\\`, + unescaped: `\`, + }, + { + name: "double quote (ASCII 34)", + escaped: `\"`, + unescaped: `"`, + }, + { + name: "single quote (ASCII 39)", + escaped: `\'`, + unescaped: `'`, + }, + { + name: "value containing all special characters", + escaped: `foo\r\n\t\\\"\'bar`, + unescaped: "foo\r\n\t\\\"'bar", + }, +} + +func TestEscape(t *testing.T) { + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + got := astutil.Escape(tc.unescaped) + if tc.escaped != got { + t.Errorf("Escape(%q) != %q, got: %q", tc.unescaped, tc.escaped, got) + } + }) + } +} + +func TestUnescape(t *testing.T) { + tt := append(tt, struct { + name string + escaped string + unescaped string + }{ + name: "not valid escape", + escaped: `\x`, + unescaped: "\\x", + }) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + got := astutil.Unescape(tc.escaped) + if tc.unescaped != got { + t.Errorf("Unescape(%q) != %q, got: %q", tc.escaped, tc.unescaped, got) + } + }) + } +} diff --git a/go.sum b/go.sum index 4dedeae..b4845ed 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -10,6 +11,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=