diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md
index 8a1927a39c..518875deb1 100644
--- a/CHANGELOG_PENDING.md
+++ b/CHANGELOG_PENDING.md
@@ -1,5 +1,6 @@
### SDK Features
### SDK Enhancements
+* `private/protocol/xml/xmlutil`: XML encoding has been updated to include encoding line feed character. ([#3881](https://github.com/aws/aws-sdk-go/pull/3881))
### SDK Bugs
diff --git a/private/protocol/xml/xmlutil/build.go b/private/protocol/xml/xmlutil/build.go
index 09ad951595..2fbb93ae76 100644
--- a/private/protocol/xml/xmlutil/build.go
+++ b/private/protocol/xml/xmlutil/build.go
@@ -308,6 +308,8 @@ func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag refl
if tag.Get("xmlAttribute") != "" { // put into current node's attribute list
attr := xml.Attr{Name: xname, Value: str}
current.Attr = append(current.Attr, attr)
+ } else if len(xname.Local) == 0 {
+ current.Text = str
} else { // regular text node
current.AddChild(&XMLNode{Name: xname, Text: str})
}
diff --git a/private/protocol/xml/xmlutil/build_test.go b/private/protocol/xml/xmlutil/build_test.go
index 0ab5b6866e..a50900c857 100644
--- a/private/protocol/xml/xmlutil/build_test.go
+++ b/private/protocol/xml/xmlutil/build_test.go
@@ -153,6 +153,12 @@ func TestBuildXML(t *testing.T) {
Input: &namedEmptyPayload{},
Expect: "",
},
+ "escape line feed and carriage return": {
+ Input: &implicitPayload{
+ StrVal: aws.String("this\nstring\rhas\r\nescapable\n\rcharacters"),
+ },
+ Expect: "this
string
has
escapable
characters",
+ },
}
for name, c := range cases {
diff --git a/private/protocol/xml/xmlutil/xml_to_struct.go b/private/protocol/xml/xmlutil/xml_to_struct.go
index 42f71648ee..c85b79fddd 100644
--- a/private/protocol/xml/xmlutil/xml_to_struct.go
+++ b/private/protocol/xml/xmlutil/xml_to_struct.go
@@ -18,6 +18,14 @@ type XMLNode struct {
parent *XMLNode
}
+// textEncoder is a string type alias that implemnts the TextMarshaler interface.
+// This alias type is used to ensure that the line feed (\n) (U+000A) is escaped.
+type textEncoder string
+
+func (t textEncoder) MarshalText() ([]byte, error) {
+ return []byte(t), nil
+}
+
// NewXMLElement returns a pointer to a new XMLNode initialized to default values.
func NewXMLElement(name xml.Name) *XMLNode {
return &XMLNode{
@@ -130,11 +138,16 @@ func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
attrs = sortedAttrs
}
- e.EncodeToken(xml.StartElement{Name: node.Name, Attr: attrs})
+ startElement := xml.StartElement{Name: node.Name, Attr: attrs}
if node.Text != "" {
- e.EncodeToken(xml.CharData([]byte(node.Text)))
- } else if sorted {
+ e.EncodeElement(textEncoder(node.Text), startElement)
+ return e.Flush()
+ }
+
+ e.EncodeToken(startElement)
+
+ if sorted {
sortedNames := []string{}
for k := range node.Children {
sortedNames = append(sortedNames, k)
@@ -154,6 +167,7 @@ func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
}
}
- e.EncodeToken(xml.EndElement{Name: node.Name})
+ e.EncodeToken(startElement.End())
+
return e.Flush()
}