| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- 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 {
- oldNamespaces := e.namespaces
- e.namespaces = copyNamespace(oldNamespaces)
-
- switch child := child.(type) {
- case *etree.Comment:
- if !e.WithComments {
- removeTokenFromElement(etree.Token(child), node)
- }
- case *etree.Element:
- e.processRecursive(child, newPrefixesInScope, newDefaultNS)
- }
-
- e.namespaces = oldNamespaces
- }
- }
- 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) {
- if attr.Space != "xml"{
- 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
- }
- func copyNamespace(namespaces map[string]string) map[string]string {
- newVersion := map[string]string{}
- for index, element := range namespaces {
- newVersion[index] = element
- }
- return newVersion
- }
|