Skip to content

Commit

Permalink
We now only encode &, < and > in PROPFIND PCDATA
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <[email protected]>
  • Loading branch information
butonic committed Sep 14, 2022
1 parent 44dc469 commit 0a85a26
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 4 deletions.
Binary file added admin.propfind.webdav.cached.pprof
Binary file not shown.
3 changes: 3 additions & 0 deletions changelog/unreleased/more-efficient-etag-pcdata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: We now only encode &, < and > in PROPFIND PCDATA

https://github.com/cs3org/reva/pull/3240
70 changes: 68 additions & 2 deletions internal/http/services/owncloud/ocdav/prop/prop.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package prop
import (
"bytes"
"encoding/xml"
"unicode/utf8"
)

// PropertyXML represents a single DAV resource property as defined in RFC 4918.
Expand Down Expand Up @@ -58,13 +59,78 @@ func EscapedNS(namespace string, local string, val string) PropertyXML {
}
}

// Escaped returns a new PropertyXML instance while xml-escaping the value
var (
escAmp = []byte("&amp;")
escLT = []byte("&lt;")
escGT = []byte("&gt;")
escFFFD = []byte("\uFFFD") // Unicode replacement character
)

// Decide whether the given rune is in the XML Character Range, per
// the Char production of https://www.xml.com/axml/testaxml.htm,
// Section 2.2 Characters.
func isInCharacterRange(r rune) (inrange bool) {
return r == 0x09 ||
r == 0x0A ||
r == 0x0D ||
r >= 0x20 && r <= 0xD7FF ||
r >= 0xE000 && r <= 0xFFFD ||
r >= 0x10000 && r <= 0x10FFFF
}

// Escaped returns a new PropertyXML instance while replacing only
// * `&` with `&amp;`
// * `<` with `&lt;`
// * `>` with `&gt;`
// as defined in https://www.w3.org/TR/REC-xml/#syntax:
//
// > The ampersand character (&) and the left angle bracket (<) must not appear
// > in their literal form, except when used as markup delimiters, or within a
// > comment, a processing instruction, or a CDATA section. If they are needed
// > elsewhere, they must be escaped using either numeric character references
// > or the strings " &amp; " and " &lt; " respectively. The right angle
// > bracket (>) may be represented using the string " &gt; ", and must, for
// > compatibility, be escaped using either " &gt; " or a character reference
// > when it appears in the string " ]]> " in content, when that string is not
// > marking the end of a CDATA section.
//
// The code ignores errors as the legacy Escaped() does
// TODO properly use the space
func Escaped(key, val string) PropertyXML {
w := new(bytes.Buffer)
s := []byte(val)
var esc []byte
last := 0
for i := 0; i < len(s); {
r, width := utf8.DecodeRune(s[i:])
i += width
switch r {
case '&':
esc = escAmp
case '<':
esc = escLT
case '>':
esc = escGT
default:
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = escFFFD
break
}
continue
}
if _, err := w.Write(s[last : i-width]); err != nil {
break
}
if _, err := w.Write(esc); err != nil {
break
}
last = i
}
_, _ = w.Write(s[last:])
return PropertyXML{
XMLName: xml.Name{Space: "", Local: key},
Lang: "",
InnerXML: xmlEscaped(val),
InnerXML: w.Bytes(),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ var _ = Describe("Propfind", func() {
Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile"))
Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>"))
Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:01 GMT</d:getlastmodified>"))
Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getetag>&#34;1&#34;</d:getetag>"))
Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"1"</d:getetag>`))
})
})

Expand Down Expand Up @@ -547,7 +547,7 @@ var _ = Describe("Propfind", func() {
Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/"))
Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6000</oc:size>"))
Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:03 GMT</d:getlastmodified>"))
Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getetag>&#34;3&#34;</d:getetag>"))
Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"3"</d:getetag>`))
})

It("stats the embedded space", func() {
Expand Down

0 comments on commit 0a85a26

Please sign in to comment.