vendor
This commit is contained in:
302
vendor/github.com/ma314smith/signedxml/exclusivecanonicalization.go
generated
vendored
Normal file
302
vendor/github.com/ma314smith/signedxml/exclusivecanonicalization.go
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
package signedxml
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// the attribute and attributes structs are used to implement the sort.Interface
|
||||
type attribute struct {
|
||||
prefix, uri, key, value string
|
||||
}
|
||||
|
||||
type attributes []attribute
|
||||
|
||||
func (a attributes) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
// Less is part of the sort.Interface, and is used to order attributes by their
|
||||
// namespace URIs and then by their keys.
|
||||
func (a attributes) Less(i, j int) bool {
|
||||
if a[i].uri == "" && a[j].uri != "" {
|
||||
return true
|
||||
}
|
||||
if a[j].uri == "" && a[i].uri != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
iQual := a[i].uri + a[i].key
|
||||
jQual := a[j].uri + a[j].key
|
||||
|
||||
return iQual < jQual
|
||||
}
|
||||
|
||||
func (a attributes) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
|
||||
// ExclusiveCanonicalization implements the CanonicalizationAlgorithm
|
||||
// interface and is used for processing the
|
||||
// http://www.w3.org/2001/10/xml-exc-c14n# and
|
||||
// http://www.w3.org/2001/10/xml-exc-c14n#WithComments transform
|
||||
// algorithms
|
||||
type ExclusiveCanonicalization struct {
|
||||
WithComments bool
|
||||
inclusiveNamespacePrefixList []string
|
||||
namespaces map[string]string
|
||||
}
|
||||
|
||||
// Process is called to transfrom the XML using the ExclusiveCanonicalization
|
||||
// algorithm
|
||||
func (e ExclusiveCanonicalization) Process(inputXML string,
|
||||
transformXML string) (outputXML string, err error) {
|
||||
|
||||
e.namespaces = make(map[string]string)
|
||||
|
||||
doc := etree.NewDocument()
|
||||
doc.WriteSettings.CanonicalEndTags = true
|
||||
doc.WriteSettings.CanonicalText = true
|
||||
doc.WriteSettings.CanonicalAttrVal = true
|
||||
|
||||
err = doc.ReadFromString(inputXML)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
e.loadPrefixList(transformXML)
|
||||
e.processDocLevelNodes(doc)
|
||||
e.processRecursive(doc.Root(), nil, "")
|
||||
|
||||
outputXML, err = doc.WriteToString()
|
||||
return outputXML, err
|
||||
}
|
||||
|
||||
func (e *ExclusiveCanonicalization) loadPrefixList(transformXML string) {
|
||||
if transformXML != "" {
|
||||
tDoc := etree.NewDocument()
|
||||
tDoc.ReadFromString(transformXML)
|
||||
inclNSNode := tDoc.Root().SelectElement("InclusiveNamespaces")
|
||||
if inclNSNode != nil {
|
||||
prefixList := inclNSNode.SelectAttrValue("PrefixList", "")
|
||||
if prefixList != "" {
|
||||
e.inclusiveNamespacePrefixList = strings.Split(prefixList, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process nodes outside of the root element
|
||||
func (e ExclusiveCanonicalization) processDocLevelNodes(doc *etree.Document) {
|
||||
// keep track of the previous node action to manage line returns in CharData
|
||||
previousNodeRemoved := false
|
||||
|
||||
for i := 0; i < len(doc.Child); i++ {
|
||||
c := doc.Child[i]
|
||||
|
||||
switch c := c.(type) {
|
||||
case *etree.Comment:
|
||||
if e.WithComments {
|
||||
previousNodeRemoved = false
|
||||
} else {
|
||||
removeTokenFromDocument(c, doc)
|
||||
i--
|
||||
previousNodeRemoved = true
|
||||
}
|
||||
case *etree.CharData:
|
||||
if isWhitespace(c.Data) {
|
||||
if previousNodeRemoved {
|
||||
removeTokenFromDocument(c, doc)
|
||||
i--
|
||||
previousNodeRemoved = true
|
||||
} else {
|
||||
c.Data = "\n"
|
||||
}
|
||||
|
||||
}
|
||||
case *etree.Directive:
|
||||
removeTokenFromDocument(c, doc)
|
||||
i--
|
||||
previousNodeRemoved = true
|
||||
case *etree.ProcInst:
|
||||
// remove declaration, but leave other PI's
|
||||
if c.Target == "xml" {
|
||||
removeTokenFromDocument(c, doc)
|
||||
i--
|
||||
previousNodeRemoved = true
|
||||
} else {
|
||||
previousNodeRemoved = false
|
||||
}
|
||||
default:
|
||||
previousNodeRemoved = false
|
||||
}
|
||||
}
|
||||
|
||||
// if the last line is CharData whitespace, then remove it
|
||||
if c, ok := doc.Child[len(doc.Child)-1].(*etree.CharData); ok {
|
||||
if isWhitespace(c.Data) {
|
||||
removeTokenFromDocument(c, doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e ExclusiveCanonicalization) processRecursive(node *etree.Element,
|
||||
prefixesInScope []string, defaultNS string) {
|
||||
|
||||
newDefaultNS, newPrefixesInScope :=
|
||||
e.renderAttributes(node, prefixesInScope, defaultNS)
|
||||
|
||||
for _, child := range node.Child {
|
||||
switch child := child.(type) {
|
||||
case *etree.Comment:
|
||||
if !e.WithComments {
|
||||
removeTokenFromElement(etree.Token(child), node)
|
||||
}
|
||||
case *etree.Element:
|
||||
e.processRecursive(child, newPrefixesInScope, newDefaultNS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e ExclusiveCanonicalization) renderAttributes(node *etree.Element,
|
||||
prefixesInScope []string, defaultNS string) (newDefaultNS string,
|
||||
newPrefixesInScope []string) {
|
||||
|
||||
currentNS := node.SelectAttrValue("xmlns", defaultNS)
|
||||
elementAttributes := []etree.Attr{}
|
||||
nsListToRender := make(map[string]string)
|
||||
attrListToRender := attributes{}
|
||||
|
||||
// load map with for prefix -> uri lookup
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Space == "xmlns" {
|
||||
e.namespaces[attr.Key] = attr.Value
|
||||
}
|
||||
}
|
||||
|
||||
// handle the namespace of the node itself
|
||||
if node.Space != "" {
|
||||
if !contains(prefixesInScope, node.Space) {
|
||||
nsListToRender["xmlns:"+node.Space] = e.namespaces[node.Space]
|
||||
prefixesInScope = append(prefixesInScope, node.Space)
|
||||
}
|
||||
} else if defaultNS != currentNS {
|
||||
newDefaultNS = currentNS
|
||||
elementAttributes = append(elementAttributes,
|
||||
etree.Attr{Key: "xmlns", Value: currentNS})
|
||||
}
|
||||
|
||||
for _, attr := range node.Attr {
|
||||
// include the namespaces if they are in the inclusiveNamespacePrefixList
|
||||
if attr.Space == "xmlns" {
|
||||
if !contains(prefixesInScope, attr.Key) &&
|
||||
contains(e.inclusiveNamespacePrefixList, attr.Key) {
|
||||
|
||||
nsListToRender["xmlns:"+attr.Key] = attr.Value
|
||||
prefixesInScope = append(prefixesInScope, attr.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// include namespaces for qualfied attributes
|
||||
if attr.Space != "" &&
|
||||
attr.Space != "xmlns" &&
|
||||
!contains(prefixesInScope, attr.Space) {
|
||||
|
||||
nsListToRender["xmlns:"+attr.Space] = e.namespaces[attr.Space]
|
||||
prefixesInScope = append(prefixesInScope, attr.Space)
|
||||
}
|
||||
|
||||
// inclued all non-namespace attributes
|
||||
if attr.Space != "xmlns" && attr.Key != "xmlns" {
|
||||
attrListToRender = append(attrListToRender,
|
||||
attribute{
|
||||
prefix: attr.Space,
|
||||
uri: e.namespaces[attr.Space],
|
||||
key: attr.Key,
|
||||
value: attr.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// sort and add the namespace attributes first
|
||||
sortedNSList := getSortedNamespaces(nsListToRender)
|
||||
elementAttributes = append(elementAttributes, sortedNSList...)
|
||||
// then sort and add the non-namespace attributes
|
||||
sortedAttributes := getSortedAttributes(attrListToRender)
|
||||
elementAttributes = append(elementAttributes, sortedAttributes...)
|
||||
// replace the nodes attributes with the sorted copy
|
||||
node.Attr = elementAttributes
|
||||
return currentNS, prefixesInScope
|
||||
}
|
||||
|
||||
func contains(slice []string, value string) bool {
|
||||
for _, s := range slice {
|
||||
if s == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getSortedNamespaces sorts the namespace attributes by their prefix
|
||||
func getSortedNamespaces(list map[string]string) []etree.Attr {
|
||||
var keys []string
|
||||
for k := range list {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
elem := etree.Element{}
|
||||
for _, k := range keys {
|
||||
elem.CreateAttr(k, list[k])
|
||||
}
|
||||
|
||||
return elem.Attr
|
||||
}
|
||||
|
||||
// getSortedAttributes sorts attributes by their namespace URIs
|
||||
func getSortedAttributes(list attributes) []etree.Attr {
|
||||
sort.Sort(list)
|
||||
attrs := make([]etree.Attr, len(list))
|
||||
for i, a := range list {
|
||||
attrs[i] = etree.Attr{
|
||||
Space: a.prefix,
|
||||
Key: a.key,
|
||||
Value: a.value,
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func removeTokenFromElement(token etree.Token, e *etree.Element) *etree.Token {
|
||||
for i, t := range e.Child {
|
||||
if t == token {
|
||||
e.Child = append(e.Child[0:i], e.Child[i+1:]...)
|
||||
return &t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeTokenFromDocument(token etree.Token, d *etree.Document) *etree.Token {
|
||||
for i, t := range d.Child {
|
||||
if t == token {
|
||||
d.Child = append(d.Child[0:i], d.Child[i+1:]...)
|
||||
return &t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isWhitespace returns true if the byte slice contains only
|
||||
// whitespace characters.
|
||||
func isWhitespace(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user