diff --git a/webdav/prop.go b/webdav/prop.go index 3446871c..83fbfa88 100644 --- a/webdav/prop.go +++ b/webdav/prop.go @@ -335,9 +335,23 @@ loop: } func escapeXML(s string) string { - var buf bytes.Buffer - xml.EscapeText(&buf, []byte(s)) - return buf.String() + for i := 0; i < len(s); i++ { + // As an optimization, if s contains only ASCII letters, digits or a + // few special characters, the escaped value is s itself and we don't + // need to allocate a buffer and convert between string and []byte. + switch c := s[i]; { + case c == ' ' || c == '_' || + ('+' <= c && c <= '9') || // Digits as well as + , - . and / + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z'): + continue + } + // Otherwise, go through the full escaping process. + var buf bytes.Buffer + xml.EscapeText(&buf, []byte(s)) + return buf.String() + } + return s } func findResourceType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { diff --git a/webdav/webdav_test.go b/webdav/webdav_test.go index 82605cdf..d9f8c8e9 100644 --- a/webdav/webdav_test.go +++ b/webdav/webdav_test.go @@ -202,6 +202,44 @@ func TestPrefix(t *testing.T) { } } +func TestEscapeXML(t *testing.T) { + // These test cases aren't exhaustive, and there is more than one way to + // escape e.g. a quot (as """ or """) or an apos. We presume that + // the encoding/xml package tests xml.EscapeText more thoroughly. This test + // here is just a sanity check for this package's escapeXML function, and + // its attempt to provide a fast path (and avoid a bytes.Buffer allocation) + // when escaping filenames is obviously a no-op. + testCases := map[string]string{ + "": "", + " ": " ", + "&": "&", + "*": "*", + "+": "+", + ",": ",", + "-": "-", + ".": ".", + "/": "/", + "0": "0", + "9": "9", + ":": ":", + "<": "<", + ">": ">", + "A": "A", + "_": "_", + "a": "a", + "~": "~", + "\u0201": "\u0201", + "&": "&amp;", + "foo&baz": "foo&<b/ar>baz", + } + + for in, want := range testCases { + if got := escapeXML(in); got != want { + t.Errorf("in=%q: got %q, want %q", in, got, want) + } + } +} + func TestFilenameEscape(t *testing.T) { hrefRe := regexp.MustCompile(`([^<]*)`) displayNameRe := regexp.MustCompile(`([^<]*)`)