Merge pull request #23302 from hashicorp/jbardin/provisioner-utf8
sanitize provisioner output strings
This commit is contained in:
commit
2d9d6d7afe
|
@ -2,6 +2,8 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
||||||
|
@ -90,7 +92,7 @@ type uiOutput struct {
|
||||||
|
|
||||||
func (o uiOutput) Output(s string) {
|
func (o uiOutput) Output(s string) {
|
||||||
err := o.srv.Send(&proto.ProvisionResource_Response{
|
err := o.srv.Send(&proto.ProvisionResource_Response{
|
||||||
Output: s,
|
Output: toValidUTF8(s, string(utf8.RuneError)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] %s", err)
|
log.Printf("[ERROR] %s", err)
|
||||||
|
@ -145,3 +147,55 @@ func (s *GRPCProvisionerServer) Stop(_ context.Context, req *proto.Stop_Request)
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: backported from go1.13 strings package, remove once terraform is
|
||||||
|
// using go >= 1.13
|
||||||
|
// ToValidUTF8 returns a copy of the string s with each run of invalid UTF-8 byte sequences
|
||||||
|
// replaced by the replacement string, which may be empty.
|
||||||
|
func toValidUTF8(s, replacement string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
for i, c := range s {
|
||||||
|
if c != utf8.RuneError {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, wid := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if wid == 1 {
|
||||||
|
b.Grow(len(s) + len(replacement))
|
||||||
|
b.WriteString(s[:i])
|
||||||
|
s = s[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for unchanged input
|
||||||
|
if b.Cap() == 0 { // didn't call b.Grow above
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid := false // previous byte was from an invalid UTF-8 sequence
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
c := s[i]
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
i++
|
||||||
|
invalid = false
|
||||||
|
b.WriteByte(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, wid := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if wid == 1 {
|
||||||
|
i++
|
||||||
|
if !invalid {
|
||||||
|
invalid = true
|
||||||
|
b.WriteString(replacement)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
invalid = false
|
||||||
|
b.WriteString(s[i : i+wid])
|
||||||
|
i += wid
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,82 @@
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
import (
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
||||||
|
mockproto "github.com/hashicorp/terraform/plugin/mock_proto"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
var _ proto.ProvisionerServer = (*GRPCProvisionerServer)(nil)
|
var _ proto.ProvisionerServer = (*GRPCProvisionerServer)(nil)
|
||||||
|
|
||||||
|
type validUTF8Matcher string
|
||||||
|
|
||||||
|
func (m validUTF8Matcher) Matches(x interface{}) bool {
|
||||||
|
resp := x.(*proto.ProvisionResource_Response)
|
||||||
|
return utf8.Valid([]byte(resp.Output))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m validUTF8Matcher) String() string {
|
||||||
|
return string(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockProvisionerServer(t *testing.T, c *gomock.Controller) *mockproto.MockProvisioner_ProvisionResourceServer {
|
||||||
|
server := mockproto.NewMockProvisioner_ProvisionResourceServer(c)
|
||||||
|
|
||||||
|
server.EXPECT().Send(
|
||||||
|
validUTF8Matcher("check for valid utf8"),
|
||||||
|
).Return(nil)
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that a provsioner cannot return invalid utf8 which isn't allowed in
|
||||||
|
// the grpc protocol.
|
||||||
|
func TestProvisionerInvalidUTF8(t *testing.T) {
|
||||||
|
p := &schema.Provisioner{
|
||||||
|
ConnSchema: map[string]*schema.Schema{
|
||||||
|
"foo": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"foo": {
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ApplyFunc: func(ctx context.Context) error {
|
||||||
|
out := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
|
||||||
|
out.Output("invalid \xc3\x28\n")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
srv := mockProvisionerServer(t, ctrl)
|
||||||
|
cfg := &proto.DynamicValue{
|
||||||
|
Msgpack: []byte("\x81\xa3foo\x01"),
|
||||||
|
}
|
||||||
|
conn := &proto.DynamicValue{
|
||||||
|
Msgpack: []byte("\x81\xa3foo\xa4host"),
|
||||||
|
}
|
||||||
|
provisionerServer := NewGRPCProvisionerServerShim(p)
|
||||||
|
req := &proto.ProvisionResource_Request{
|
||||||
|
Config: cfg,
|
||||||
|
Connection: conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := provisionerServer.ProvisionResource(req, srv); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue