github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/prop/prop.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package prop
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/xml"
    24  	"unicode/utf8"
    25  )
    26  
    27  // PropertyXML represents a single DAV resource property as defined in RFC 4918.
    28  // http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties
    29  type PropertyXML struct {
    30  	// XMLName is the fully qualified name that identifies this property.
    31  	XMLName xml.Name
    32  
    33  	// Lang is an optional xml:lang attribute.
    34  	Lang string `xml:"xml:lang,attr,omitempty"`
    35  
    36  	// InnerXML contains the XML representation of the property value.
    37  	// See http://www.webdav.org/specs/rfc4918.html#property_values
    38  	//
    39  	// Property values of complex type or mixed-content must have fully
    40  	// expanded XML namespaces or be self-contained with according
    41  	// XML namespace declarations. They must not rely on any XML
    42  	// namespace declarations within the scope of the XML document,
    43  	// even including the DAV: namespace.
    44  	InnerXML []byte `xml:",innerxml"`
    45  }
    46  
    47  func xmlEscaped(val string) []byte {
    48  	buf := new(bytes.Buffer)
    49  	xml.Escape(buf, []byte(val))
    50  	return buf.Bytes()
    51  }
    52  
    53  // EscapedNS returns a new PropertyXML instance while xml-escaping the value
    54  func EscapedNS(namespace string, local string, val string) PropertyXML {
    55  	return PropertyXML{
    56  		XMLName:  xml.Name{Space: namespace, Local: local},
    57  		Lang:     "",
    58  		InnerXML: xmlEscaped(val),
    59  	}
    60  }
    61  
    62  var (
    63  	escAmp  = []byte("&")
    64  	escLT   = []byte("<")
    65  	escGT   = []byte(">")
    66  	escFFFD = []byte(string(utf8.RuneError)) // Unicode replacement character
    67  )
    68  
    69  // Decide whether the given rune is in the XML Character Range, per
    70  // the Char production of https://www.xml.com/axml/testaxml.htm,
    71  // Section 2.2 Characters.
    72  func isInCharacterRange(r rune) (inrange bool) {
    73  	return r == 0x09 ||
    74  		r == 0x0A ||
    75  		r == 0x0D ||
    76  		r >= 0x20 && r <= 0xD7FF ||
    77  		r >= 0xE000 && r <= utf8.RuneError ||
    78  		r >= 0x10000 && r <= 0x10FFFF
    79  }
    80  
    81  // Escaped returns a new PropertyXML instance while replacing only
    82  // * `&` with `&amp;`
    83  // * `<` with `&lt;`
    84  // * `>` with `&gt;`
    85  // as defined in https://www.w3.org/TR/REC-xml/#syntax:
    86  //
    87  // > The ampersand character (&) and the left angle bracket (<) must not appear
    88  // > in their literal form, except when used as markup delimiters, or within a
    89  // > comment, a processing instruction, or a CDATA section. If they are needed
    90  // > elsewhere, they must be escaped using either numeric character references
    91  // > or the strings " &amp; " and " &lt; " respectively. The right angle
    92  // > bracket (>) may be represented using the string " &gt; ", and must, for
    93  // > compatibility, be escaped using either " &gt; " or a character reference
    94  // > when it appears in the string " ]]> " in content, when that string is not
    95  // > marking the end of a CDATA section.
    96  //
    97  // The code ignores errors as the legacy Escaped() does
    98  // TODO properly use the space
    99  func Escaped(key, val string) PropertyXML {
   100  	s := []byte(val)
   101  	w := bytes.NewBuffer(make([]byte, 0, len(s)))
   102  	var esc []byte
   103  	last := 0
   104  	for i := 0; i < len(s); {
   105  		r, width := utf8.DecodeRune(s[i:])
   106  		i += width
   107  		switch r {
   108  		case '&':
   109  			esc = escAmp
   110  		case '<':
   111  			esc = escLT
   112  		case '>':
   113  			esc = escGT
   114  		default:
   115  			if !isInCharacterRange(r) || (r == utf8.RuneError && width == 1) {
   116  				esc = escFFFD
   117  				break
   118  			}
   119  			continue
   120  		}
   121  		if _, err := w.Write(s[last : i-width]); err != nil {
   122  			break
   123  		}
   124  		if _, err := w.Write(esc); err != nil {
   125  			break
   126  		}
   127  		last = i
   128  	}
   129  	_, _ = w.Write(s[last:])
   130  	return PropertyXML{
   131  		XMLName:  xml.Name{Space: "", Local: key},
   132  		Lang:     "",
   133  		InnerXML: w.Bytes(),
   134  	}
   135  }
   136  
   137  // NotFound returns a new PropertyXML instance with an empty value
   138  func NotFound(key string) PropertyXML {
   139  	return PropertyXML{
   140  		XMLName: xml.Name{Space: "", Local: key},
   141  		Lang:    "",
   142  	}
   143  }
   144  
   145  // NotFoundNS returns a new PropertyXML instance with the given namespace and an empty value
   146  func NotFoundNS(namespace, key string) PropertyXML {
   147  	return PropertyXML{
   148  		XMLName: xml.Name{Space: namespace, Local: key},
   149  		Lang:    "",
   150  	}
   151  }
   152  
   153  // Raw returns a new PropertyXML instance for the given key/value pair
   154  // TODO properly use the space
   155  func Raw(key, val string) PropertyXML {
   156  	return PropertyXML{
   157  		XMLName:  xml.Name{Space: "", Local: key},
   158  		Lang:     "",
   159  		InnerXML: []byte(val),
   160  	}
   161  }
   162  
   163  // Next returns the next token, if any, in the XML stream of d.
   164  // RFC 4918 requires to ignore comments, processing instructions
   165  // and directives.
   166  // http://www.webdav.org/specs/rfc4918.html#property_values
   167  // http://www.webdav.org/specs/rfc4918.html#xml-extensibility
   168  func Next(d *xml.Decoder) (xml.Token, error) {
   169  	for {
   170  		t, err := d.Token()
   171  		if err != nil {
   172  			return t, err
   173  		}
   174  		switch t.(type) {
   175  		case xml.Comment, xml.Directive, xml.ProcInst:
   176  			continue
   177  		default:
   178  			return t, nil
   179  		}
   180  	}
   181  }
   182  
   183  // ActiveLock holds active lock xml data
   184  //
   185  //	http://www.webdav.org/specs/rfc4918.html#ELEMENT_activelock
   186  //
   187  // <!ELEMENT activelock (lockscope, locktype, depth, owner?, timeout?,
   188  //
   189  //	locktoken?, lockroot)>
   190  type ActiveLock struct {
   191  	XMLName   xml.Name  `xml:"activelock"`
   192  	Exclusive *struct{} `xml:"lockscope>exclusive,omitempty"`
   193  	Shared    *struct{} `xml:"lockscope>shared,omitempty"`
   194  	Write     *struct{} `xml:"locktype>write,omitempty"`
   195  	Depth     string    `xml:"depth"`
   196  	Owner     Owner     `xml:"owner,omitempty"`
   197  	Timeout   string    `xml:"timeout,omitempty"`
   198  	Locktoken string    `xml:"locktoken>href"`
   199  	Lockroot  string    `xml:"lockroot>href,omitempty"`
   200  }
   201  
   202  // Owner captures the inner UML of a lock owner element http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
   203  type Owner struct {
   204  	InnerXML string `xml:",innerxml"`
   205  }
   206  
   207  // Escape repaces ", &, ', < and > with their xml representation
   208  func Escape(s string) string {
   209  	b := bytes.NewBuffer(nil)
   210  	_ = xml.EscapeText(b, []byte(s))
   211  	return b.String()
   212  }