From fd9092e5ab3b00f33698255deda7a84fb45dee27 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sun, 1 Apr 2018 11:08:20 -0400 Subject: [PATCH] vendoring and caldav --- Gopkg.lock | 61 + Gopkg.toml | 42 + main.go | 22 +- .../github.com/abbot/go-http-auth/.gitignore | 5 + vendor/github.com/abbot/go-http-auth/LICENSE | 178 ++ vendor/github.com/abbot/go-http-auth/Makefile | 12 + .../github.com/abbot/go-http-auth/README.md | 71 + vendor/github.com/abbot/go-http-auth/auth.go | 109 + vendor/github.com/abbot/go-http-auth/basic.go | 163 ++ .../github.com/abbot/go-http-auth/digest.go | 274 +++ .../github.com/abbot/go-http-auth/md5crypt.go | 92 + vendor/github.com/abbot/go-http-auth/misc.go | 141 ++ .../abbot/go-http-auth/test.htdigest | 1 + .../abbot/go-http-auth/test.htpasswd | 4 + vendor/github.com/abbot/go-http-auth/users.go | 154 ++ vendor/github.com/beevik/etree/.travis.yml | 16 + vendor/github.com/beevik/etree/CONTRIBUTORS | 8 + vendor/github.com/beevik/etree/LICENSE | 24 + vendor/github.com/beevik/etree/README.md | 203 ++ vendor/github.com/beevik/etree/etree.go | 943 ++++++++ vendor/github.com/beevik/etree/helpers.go | 188 ++ vendor/github.com/beevik/etree/path.go | 516 +++++ .../laurent22/ical-go/ical/calendar.go | 17 + .../laurent22/ical-go/ical/calendar_event.go | 50 + .../github.com/laurent22/ical-go/ical/lib.go | 18 + .../github.com/laurent22/ical-go/ical/node.go | 168 ++ .../laurent22/ical-go/ical/parsers.go | 106 + .../laurent22/ical-go/ical/properties.go | 9 + .../laurent22/ical-go/ical/serializers.go | 121 + .../github.com/laurent22/ical-go/ical/todo.go | 94 + vendor/github.com/samedi/caldav-go/.gitignore | 2 + .../github.com/samedi/caldav-go/CHANGELOG.md | 69 + vendor/github.com/samedi/caldav-go/LICENSE | 20 + vendor/github.com/samedi/caldav-go/README.md | 140 ++ vendor/github.com/samedi/caldav-go/config.go | 14 + .../samedi/caldav-go/data/filters.go | 362 +++ .../samedi/caldav-go/data/resource.go | 277 +++ .../samedi/caldav-go/data/storage.go | 217 ++ .../github.com/samedi/caldav-go/data/user.go | 5 + .../samedi/caldav-go/errs/errors.go | 12 + .../samedi/caldav-go/files/paths.go | 30 + vendor/github.com/samedi/caldav-go/glide.lock | 10 + vendor/github.com/samedi/caldav-go/glide.yaml | 7 + .../samedi/caldav-go/global/global.go | 12 + vendor/github.com/samedi/caldav-go/handler.go | 28 + .../samedi/caldav-go/handlers/builder.go | 24 + .../samedi/caldav-go/handlers/delete.go | 40 + .../samedi/caldav-go/handlers/get.go | 37 + .../samedi/caldav-go/handlers/headers.go | 27 + .../samedi/caldav-go/handlers/multistatus.go | 207 ++ .../caldav-go/handlers/not_implemented.go | 13 + .../samedi/caldav-go/handlers/options.go | 23 + .../caldav-go/handlers/preconditions.go | 23 + .../samedi/caldav-go/handlers/propfind.go | 49 + .../samedi/caldav-go/handlers/put.go | 65 + .../samedi/caldav-go/handlers/report.go | 168 ++ .../samedi/caldav-go/handlers/response.go | 65 + .../samedi/caldav-go/handlers/shared.go | 22 + .../github.com/samedi/caldav-go/ixml/ixml.go | 93 + .../samedi/caldav-go/lib/components.go | 8 + .../github.com/samedi/caldav-go/lib/paths.go | 10 + .../samedi/caldav-go/lib/strbuff.go | 18 + vendor/github.com/samedi/caldav-go/test.sh | 4 + vendor/github.com/samedi/caldav-go/version.go | 5 + vendor/golang.org/x/crypto/AUTHORS | 3 + vendor/golang.org/x/crypto/CONTRIBUTORS | 3 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/bcrypt/base64.go | 35 + vendor/golang.org/x/crypto/bcrypt/bcrypt.go | 295 +++ vendor/golang.org/x/crypto/blowfish/block.go | 159 ++ vendor/golang.org/x/crypto/blowfish/cipher.go | 91 + vendor/golang.org/x/crypto/blowfish/const.go | 199 ++ vendor/golang.org/x/net/AUTHORS | 3 + vendor/golang.org/x/net/CONTRIBUTORS | 3 + vendor/golang.org/x/net/LICENSE | 27 + vendor/golang.org/x/net/PATENTS | 22 + vendor/golang.org/x/net/context/context.go | 56 + vendor/golang.org/x/net/context/go17.go | 72 + vendor/golang.org/x/net/context/go19.go | 20 + vendor/golang.org/x/net/context/pre_go17.go | 300 +++ vendor/golang.org/x/net/context/pre_go19.go | 109 + vendor/golang.org/x/net/webdav/file.go | 796 +++++++ vendor/golang.org/x/net/webdav/file_go1.6.go | 17 + vendor/golang.org/x/net/webdav/file_go1.7.go | 16 + vendor/golang.org/x/net/webdav/if.go | 173 ++ .../x/net/webdav/internal/xml/README | 11 + .../x/net/webdav/internal/xml/marshal.go | 1223 ++++++++++ .../x/net/webdav/internal/xml/read.go | 692 ++++++ .../x/net/webdav/internal/xml/typeinfo.go | 371 +++ .../x/net/webdav/internal/xml/xml.go | 1998 +++++++++++++++++ .../x/net/webdav/litmus_test_server.go | 94 + vendor/golang.org/x/net/webdav/lock.go | 445 ++++ vendor/golang.org/x/net/webdav/prop.go | 418 ++++ vendor/golang.org/x/net/webdav/webdav.go | 702 ++++++ vendor/golang.org/x/net/webdav/xml.go | 519 +++++ 96 files changed, 14832 insertions(+), 5 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 vendor/github.com/abbot/go-http-auth/.gitignore create mode 100644 vendor/github.com/abbot/go-http-auth/LICENSE create mode 100644 vendor/github.com/abbot/go-http-auth/Makefile create mode 100644 vendor/github.com/abbot/go-http-auth/README.md create mode 100644 vendor/github.com/abbot/go-http-auth/auth.go create mode 100644 vendor/github.com/abbot/go-http-auth/basic.go create mode 100644 vendor/github.com/abbot/go-http-auth/digest.go create mode 100644 vendor/github.com/abbot/go-http-auth/md5crypt.go create mode 100644 vendor/github.com/abbot/go-http-auth/misc.go create mode 100644 vendor/github.com/abbot/go-http-auth/test.htdigest create mode 100644 vendor/github.com/abbot/go-http-auth/test.htpasswd create mode 100644 vendor/github.com/abbot/go-http-auth/users.go create mode 100644 vendor/github.com/beevik/etree/.travis.yml create mode 100644 vendor/github.com/beevik/etree/CONTRIBUTORS create mode 100644 vendor/github.com/beevik/etree/LICENSE create mode 100644 vendor/github.com/beevik/etree/README.md create mode 100644 vendor/github.com/beevik/etree/etree.go create mode 100644 vendor/github.com/beevik/etree/helpers.go create mode 100644 vendor/github.com/beevik/etree/path.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/calendar.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/calendar_event.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/lib.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/node.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/parsers.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/properties.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/serializers.go create mode 100644 vendor/github.com/laurent22/ical-go/ical/todo.go create mode 100644 vendor/github.com/samedi/caldav-go/.gitignore create mode 100644 vendor/github.com/samedi/caldav-go/CHANGELOG.md create mode 100644 vendor/github.com/samedi/caldav-go/LICENSE create mode 100644 vendor/github.com/samedi/caldav-go/README.md create mode 100644 vendor/github.com/samedi/caldav-go/config.go create mode 100644 vendor/github.com/samedi/caldav-go/data/filters.go create mode 100644 vendor/github.com/samedi/caldav-go/data/resource.go create mode 100644 vendor/github.com/samedi/caldav-go/data/storage.go create mode 100644 vendor/github.com/samedi/caldav-go/data/user.go create mode 100644 vendor/github.com/samedi/caldav-go/errs/errors.go create mode 100644 vendor/github.com/samedi/caldav-go/files/paths.go create mode 100644 vendor/github.com/samedi/caldav-go/glide.lock create mode 100644 vendor/github.com/samedi/caldav-go/glide.yaml create mode 100644 vendor/github.com/samedi/caldav-go/global/global.go create mode 100644 vendor/github.com/samedi/caldav-go/handler.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/builder.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/delete.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/get.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/headers.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/multistatus.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/not_implemented.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/options.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/preconditions.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/propfind.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/put.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/report.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/response.go create mode 100644 vendor/github.com/samedi/caldav-go/handlers/shared.go create mode 100644 vendor/github.com/samedi/caldav-go/ixml/ixml.go create mode 100644 vendor/github.com/samedi/caldav-go/lib/components.go create mode 100644 vendor/github.com/samedi/caldav-go/lib/paths.go create mode 100644 vendor/github.com/samedi/caldav-go/lib/strbuff.go create mode 100755 vendor/github.com/samedi/caldav-go/test.sh create mode 100644 vendor/github.com/samedi/caldav-go/version.go create mode 100644 vendor/golang.org/x/crypto/AUTHORS create mode 100644 vendor/golang.org/x/crypto/CONTRIBUTORS create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/bcrypt/base64.go create mode 100644 vendor/golang.org/x/crypto/bcrypt/bcrypt.go create mode 100644 vendor/golang.org/x/crypto/blowfish/block.go create mode 100644 vendor/golang.org/x/crypto/blowfish/cipher.go create mode 100644 vendor/golang.org/x/crypto/blowfish/const.go create mode 100644 vendor/golang.org/x/net/AUTHORS create mode 100644 vendor/golang.org/x/net/CONTRIBUTORS create mode 100644 vendor/golang.org/x/net/LICENSE create mode 100644 vendor/golang.org/x/net/PATENTS create mode 100644 vendor/golang.org/x/net/context/context.go create mode 100644 vendor/golang.org/x/net/context/go17.go create mode 100644 vendor/golang.org/x/net/context/go19.go create mode 100644 vendor/golang.org/x/net/context/pre_go17.go create mode 100644 vendor/golang.org/x/net/context/pre_go19.go create mode 100644 vendor/golang.org/x/net/webdav/file.go create mode 100644 vendor/golang.org/x/net/webdav/file_go1.6.go create mode 100644 vendor/golang.org/x/net/webdav/file_go1.7.go create mode 100644 vendor/golang.org/x/net/webdav/if.go create mode 100644 vendor/golang.org/x/net/webdav/internal/xml/README create mode 100644 vendor/golang.org/x/net/webdav/internal/xml/marshal.go create mode 100644 vendor/golang.org/x/net/webdav/internal/xml/read.go create mode 100644 vendor/golang.org/x/net/webdav/internal/xml/typeinfo.go create mode 100644 vendor/golang.org/x/net/webdav/internal/xml/xml.go create mode 100644 vendor/golang.org/x/net/webdav/litmus_test_server.go create mode 100644 vendor/golang.org/x/net/webdav/lock.go create mode 100644 vendor/golang.org/x/net/webdav/prop.go create mode 100644 vendor/golang.org/x/net/webdav/webdav.go create mode 100644 vendor/golang.org/x/net/webdav/xml.go diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..a973a42 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,61 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/abbot/go-http-auth" + packages = ["."] + revision = "0ddd408d5d60ea76e320503cc7dd091992dee608" + version = "v0.4.0" + +[[projects]] + name = "github.com/beevik/etree" + packages = ["."] + revision = "15a30b44cfd6c5a16a7ddfe271bf146aaf2d3195" + version = "v1.0.0" + +[[projects]] + name = "github.com/laurent22/ical-go" + packages = ["ical"] + revision = "4811ac5553eae5fed7cd5d7a9024727f1311b2a2" + version = "v0.1.0" + +[[projects]] + name = "github.com/samedi/caldav-go" + packages = [ + ".", + "data", + "errs", + "files", + "global", + "handlers", + "ixml", + "lib" + ] + revision = "4d45d84038c6026578b403ceff7d3081dcd84efd" + version = "v3.0.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "bcrypt", + "blowfish" + ] + revision = "12892e8c234f4fe6f6803f052061de9057903bb2" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "context", + "webdav", + "webdav/internal/xml" + ] + revision = "b68f30494add4df6bd8ef5e82803f308e7f7c59c" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "65e0b2109be1138b841139e4bf266a60b62dc0e84d2ab6dc9785ea94b766074c" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..c10ef93 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,42 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/abbot/go-http-auth" + version = "0.4.0" + +[[constraint]] + name = "github.com/samedi/caldav-go" + version = "3.0.0" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[prune] + go-tests = true + unused-packages = true diff --git a/main.go b/main.go index 2693965..30b6fef 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -11,6 +12,7 @@ import ( "time" "github.com/abbot/go-http-auth" + caldav "github.com/samedi/caldav-go" "golang.org/x/net/webdav" ) @@ -19,6 +21,7 @@ var ( flCert = flag.String("cert", "", "server SSL cert (both -cert and -key must be present to use SSL). See `go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -h` to generate development cert/key") flKey = flag.String("key", "", "server SSL key") flHtpasswd = flag.String("htpasswd", "", "htpasswd file for auth (must be present to use auth) See htpasswd(1) to create this file.") + flCalDav = flag.String("caldav", "", "local path to store caldav data ('' means no caldav is served)") ) func main() { @@ -60,10 +63,19 @@ func main() { authHandlerFunc := func(w http.ResponseWriter, r *auth.AuthenticatedRequest) { h.ServeHTTP(w, &r.Request) } + if *flCalDav != "" { + http.HandleFunc("/caldav", authenticator.Wrap(func(w http.ResponseWriter, r *auth.AuthenticatedRequest) { + caldav.RequestHandler(w, &r.Request) + })) + } http.HandleFunc("/", authenticator.Wrap(authHandlerFunc)) } else { log.Println("WARNING: connections are not authenticated. STRONGLY consider using -htpasswd.") + if *flCalDav != "" { + http.HandleFunc("/caldav", caldav.RequestHandler) + } + http.Handle("/", h) } addr := fmt.Sprintf(":%d", *flPort) @@ -85,23 +97,23 @@ type passThroughFS struct { root string } -func (ptfs *passThroughFS) Mkdir(name string, perm os.FileMode) error { +func (ptfs *passThroughFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { // TODO(vbatts) check for escaping the root directory return os.Mkdir(filepath.Join(ptfs.root, name), perm) } -func (ptfs *passThroughFS) OpenFile(name string, flag int, perm os.FileMode) (webdav.File, error) { +func (ptfs *passThroughFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { // TODO(vbatts) check for escaping the root directory return os.OpenFile(filepath.Join(ptfs.root, name), flag, perm) } -func (ptfs *passThroughFS) RemoveAll(name string) error { +func (ptfs *passThroughFS) RemoveAll(ctx context.Context, name string) error { // TODO(vbatts) check for escaping the root directory return os.RemoveAll(filepath.Join(ptfs.root, name)) } -func (ptfs *passThroughFS) Rename(oldName, newName string) error { +func (ptfs *passThroughFS) Rename(ctx context.Context, oldName, newName string) error { // TODO(vbatts) check for escaping the root directory return os.Rename(filepath.Join(ptfs.root, oldName), filepath.Join(ptfs.root, newName)) } -func (ptfs *passThroughFS) Stat(name string) (os.FileInfo, error) { +func (ptfs *passThroughFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { // TODO(vbatts) check for escaping the root directory return os.Stat(filepath.Join(ptfs.root, name)) } diff --git a/vendor/github.com/abbot/go-http-auth/.gitignore b/vendor/github.com/abbot/go-http-auth/.gitignore new file mode 100644 index 0000000..112ea39 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/.gitignore @@ -0,0 +1,5 @@ +*~ +*.a +*.6 +*.out +_testmain.go diff --git a/vendor/github.com/abbot/go-http-auth/LICENSE b/vendor/github.com/abbot/go-http-auth/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/vendor/github.com/abbot/go-http-auth/Makefile b/vendor/github.com/abbot/go-http-auth/Makefile new file mode 100644 index 0000000..25f208d --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/Makefile @@ -0,0 +1,12 @@ +include $(GOROOT)/src/Make.inc + +TARG=auth_digest +GOFILES=\ + auth.go\ + digest.go\ + basic.go\ + misc.go\ + md5crypt.go\ + users.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/vendor/github.com/abbot/go-http-auth/README.md b/vendor/github.com/abbot/go-http-auth/README.md new file mode 100644 index 0000000..73ae985 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/README.md @@ -0,0 +1,71 @@ +HTTP Authentication implementation in Go +======================================== + +This is an implementation of HTTP Basic and HTTP Digest authentication +in Go language. It is designed as a simple wrapper for +http.RequestHandler functions. + +Features +-------- + + * Supports HTTP Basic and HTTP Digest authentication. + * Supports htpasswd and htdigest formatted files. + * Automatic reloading of password files. + * Pluggable interface for user/password storage. + * Supports MD5, SHA1 and BCrypt for Basic authentication password storage. + * Configurable Digest nonce cache size with expiration. + * Wrapper for legacy http handlers (http.HandlerFunc interface) + +Example usage +------------- + +This is a complete working example for Basic auth: + + package main + + import ( + "fmt" + "net/http" + + auth "github.com/abbot/go-http-auth" + ) + + func Secret(user, realm string) string { + if user == "john" { + // password is "hello" + return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" + } + return "" + } + + func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) { + fmt.Fprintf(w, "

Hello, %s!

", r.Username) + } + + func main() { + authenticator := auth.NewBasicAuthenticator("example.com", Secret) + http.HandleFunc("/", authenticator.Wrap(handle)) + http.ListenAndServe(":8080", nil) + } + +See more examples in the "examples" directory. + +Legal +----- + +This module is developed under Apache 2.0 license, and can be used for +open and proprietary projects. + +Copyright 2012-2013 Lev Shamardin + +Licensed under the Apache License, Version 2.0 (the "License"); you +may not use this file or any other part of this project except in +compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. diff --git a/vendor/github.com/abbot/go-http-auth/auth.go b/vendor/github.com/abbot/go-http-auth/auth.go new file mode 100644 index 0000000..05ded16 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/auth.go @@ -0,0 +1,109 @@ +// Package auth is an implementation of HTTP Basic and HTTP Digest authentication. +package auth + +import ( + "net/http" + + "golang.org/x/net/context" +) + +/* + Request handlers must take AuthenticatedRequest instead of http.Request +*/ +type AuthenticatedRequest struct { + http.Request + /* + Authenticated user name. Current API implies that Username is + never empty, which means that authentication is always done + before calling the request handler. + */ + Username string +} + +/* + AuthenticatedHandlerFunc is like http.HandlerFunc, but takes + AuthenticatedRequest instead of http.Request +*/ +type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest) + +/* + Authenticator wraps an AuthenticatedHandlerFunc with + authentication-checking code. + + Typical Authenticator usage is something like: + + authenticator := SomeAuthenticator(...) + http.HandleFunc("/", authenticator(my_handler)) + + Authenticator wrapper checks the user authentication and calls the + wrapped function only after authentication has succeeded. Otherwise, + it returns a handler which initiates the authentication procedure. +*/ +type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc + +// Info contains authentication information for the request. +type Info struct { + // Authenticated is set to true when request was authenticated + // successfully, i.e. username and password passed in request did + // pass the check. + Authenticated bool + + // Username contains a user name passed in the request when + // Authenticated is true. It's value is undefined if Authenticated + // is false. + Username string + + // ResponseHeaders contains extra headers that must be set by server + // when sending back HTTP response. + ResponseHeaders http.Header +} + +// UpdateHeaders updates headers with this Info's ResponseHeaders. It is +// safe to call this function on nil Info. +func (i *Info) UpdateHeaders(headers http.Header) { + if i == nil { + return + } + for k, values := range i.ResponseHeaders { + for _, v := range values { + headers.Add(k, v) + } + } +} + +type key int // used for context keys + +var infoKey key = 0 + +type AuthenticatorInterface interface { + // NewContext returns a new context carrying authentication + // information extracted from the request. + NewContext(ctx context.Context, r *http.Request) context.Context + + // Wrap returns an http.HandlerFunc which wraps + // AuthenticatedHandlerFunc with this authenticator's + // authentication checks. + Wrap(AuthenticatedHandlerFunc) http.HandlerFunc +} + +// FromContext returns authentication information from the context or +// nil if no such information present. +func FromContext(ctx context.Context) *Info { + info, ok := ctx.Value(infoKey).(*Info) + if !ok { + return nil + } + return info +} + +// AuthUsernameHeader is the header set by JustCheck functions. It +// contains an authenticated username (if authentication was +// successful). +const AuthUsernameHeader = "X-Authenticated-Username" + +func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc { + return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { + ar.Header.Set(AuthUsernameHeader, ar.Username) + wrapped(w, &ar.Request) + }) +} diff --git a/vendor/github.com/abbot/go-http-auth/basic.go b/vendor/github.com/abbot/go-http-auth/basic.go new file mode 100644 index 0000000..b03dd58 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/basic.go @@ -0,0 +1,163 @@ +package auth + +import ( + "bytes" + "crypto/sha1" + "crypto/subtle" + "encoding/base64" + "errors" + "net/http" + "strings" + + "golang.org/x/crypto/bcrypt" + "golang.org/x/net/context" +) + +type compareFunc func(hashedPassword, password []byte) error + +var ( + errMismatchedHashAndPassword = errors.New("mismatched hash and password") + + compareFuncs = []struct { + prefix string + compare compareFunc + }{ + {"", compareMD5HashAndPassword}, // default compareFunc + {"{SHA}", compareShaHashAndPassword}, + // Bcrypt is complicated. According to crypt(3) from + // crypt_blowfish version 1.3 (fetched from + // http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz), there + // are three different has prefixes: "$2a$", used by versions up + // to 1.0.4, and "$2x$" and "$2y$", used in all later + // versions. "$2a$" has a known bug, "$2x$" was added as a + // migration path for systems with "$2a$" prefix and still has a + // bug, and only "$2y$" should be used by modern systems. The bug + // has something to do with handling of 8-bit characters. Since + // both "$2a$" and "$2x$" are deprecated, we are handling them the + // same way as "$2y$", which will yield correct results for 7-bit + // character passwords, but is wrong for 8-bit character + // passwords. You have to upgrade to "$2y$" if you want sant 8-bit + // character password support with bcrypt. To add to the mess, + // OpenBSD 5.5. introduced "$2b$" prefix, which behaves exactly + // like "$2y$" according to the same source. + {"$2a$", bcrypt.CompareHashAndPassword}, + {"$2b$", bcrypt.CompareHashAndPassword}, + {"$2x$", bcrypt.CompareHashAndPassword}, + {"$2y$", bcrypt.CompareHashAndPassword}, + } +) + +type BasicAuth struct { + Realm string + Secrets SecretProvider + // Headers used by authenticator. Set to ProxyHeaders to use with + // proxy server. When nil, NormalHeaders are used. + Headers *Headers +} + +// check that BasicAuth implements AuthenticatorInterface +var _ = (AuthenticatorInterface)((*BasicAuth)(nil)) + +/* + Checks the username/password combination from the request. Returns + either an empty string (authentication failed) or the name of the + authenticated user. + + Supports MD5 and SHA1 password entries +*/ +func (a *BasicAuth) CheckAuth(r *http.Request) string { + s := strings.SplitN(r.Header.Get(a.Headers.V().Authorization), " ", 2) + if len(s) != 2 || s[0] != "Basic" { + return "" + } + + b, err := base64.StdEncoding.DecodeString(s[1]) + if err != nil { + return "" + } + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + return "" + } + user, password := pair[0], pair[1] + secret := a.Secrets(user, a.Realm) + if secret == "" { + return "" + } + compare := compareFuncs[0].compare + for _, cmp := range compareFuncs[1:] { + if strings.HasPrefix(secret, cmp.prefix) { + compare = cmp.compare + break + } + } + if compare([]byte(secret), []byte(password)) != nil { + return "" + } + return pair[0] +} + +func compareShaHashAndPassword(hashedPassword, password []byte) error { + d := sha1.New() + d.Write(password) + if subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 { + return errMismatchedHashAndPassword + } + return nil +} + +func compareMD5HashAndPassword(hashedPassword, password []byte) error { + parts := bytes.SplitN(hashedPassword, []byte("$"), 4) + if len(parts) != 4 { + return errMismatchedHashAndPassword + } + magic := []byte("$" + string(parts[1]) + "$") + salt := parts[2] + if subtle.ConstantTimeCompare(hashedPassword, MD5Crypt(password, salt, magic)) != 1 { + return errMismatchedHashAndPassword + } + return nil +} + +/* + http.Handler for BasicAuth which initiates the authentication process + (or requires reauthentication). +*/ +func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { + w.Header().Set(contentType, a.Headers.V().UnauthContentType) + w.Header().Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`) + w.WriteHeader(a.Headers.V().UnauthCode) + w.Write([]byte(a.Headers.V().UnauthResponse)) +} + +/* + BasicAuthenticator returns a function, which wraps an + AuthenticatedHandlerFunc converting it to http.HandlerFunc. This + wrapper function checks the authentication and either sends back + required authentication headers, or calls the wrapped function with + authenticated username in the AuthenticatedRequest. +*/ +func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if username := a.CheckAuth(r); username == "" { + a.RequireAuth(w, r) + } else { + ar := &AuthenticatedRequest{Request: *r, Username: username} + wrapped(w, ar) + } + } +} + +// NewContext returns a context carrying authentication information for the request. +func (a *BasicAuth) NewContext(ctx context.Context, r *http.Request) context.Context { + info := &Info{Username: a.CheckAuth(r), ResponseHeaders: make(http.Header)} + info.Authenticated = (info.Username != "") + if !info.Authenticated { + info.ResponseHeaders.Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`) + } + return context.WithValue(ctx, infoKey, info) +} + +func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth { + return &BasicAuth{Realm: realm, Secrets: secrets} +} diff --git a/vendor/github.com/abbot/go-http-auth/digest.go b/vendor/github.com/abbot/go-http-auth/digest.go new file mode 100644 index 0000000..21b0933 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/digest.go @@ -0,0 +1,274 @@ +package auth + +import ( + "crypto/subtle" + "fmt" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "sync" + "time" + + "golang.org/x/net/context" +) + +type digest_client struct { + nc uint64 + last_seen int64 +} + +type DigestAuth struct { + Realm string + Opaque string + Secrets SecretProvider + PlainTextSecrets bool + IgnoreNonceCount bool + // Headers used by authenticator. Set to ProxyHeaders to use with + // proxy server. When nil, NormalHeaders are used. + Headers *Headers + + /* + Approximate size of Client's Cache. When actual number of + tracked client nonces exceeds + ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2 + older entries are purged. + */ + ClientCacheSize int + ClientCacheTolerance int + + clients map[string]*digest_client + mutex sync.Mutex +} + +// check that DigestAuth implements AuthenticatorInterface +var _ = (AuthenticatorInterface)((*DigestAuth)(nil)) + +type digest_cache_entry struct { + nonce string + last_seen int64 +} + +type digest_cache []digest_cache_entry + +func (c digest_cache) Less(i, j int) bool { + return c[i].last_seen < c[j].last_seen +} + +func (c digest_cache) Len() int { + return len(c) +} + +func (c digest_cache) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +/* + Remove count oldest entries from DigestAuth.clients +*/ +func (a *DigestAuth) Purge(count int) { + entries := make([]digest_cache_entry, 0, len(a.clients)) + for nonce, client := range a.clients { + entries = append(entries, digest_cache_entry{nonce, client.last_seen}) + } + cache := digest_cache(entries) + sort.Sort(cache) + for _, client := range cache[:count] { + delete(a.clients, client.nonce) + } +} + +/* + http.Handler for DigestAuth which initiates the authentication process + (or requires reauthentication). +*/ +func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { + if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance { + a.Purge(a.ClientCacheTolerance * 2) + } + nonce := RandomKey() + a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} + w.Header().Set(contentType, a.Headers.V().UnauthContentType) + w.Header().Set(a.Headers.V().Authenticate, + fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`, + a.Realm, nonce, a.Opaque)) + w.WriteHeader(a.Headers.V().UnauthCode) + w.Write([]byte(a.Headers.V().UnauthResponse)) +} + +/* + Parse Authorization header from the http.Request. Returns a map of + auth parameters or nil if the header is not a valid parsable Digest + auth header. +*/ +func DigestAuthParams(authorization string) map[string]string { + s := strings.SplitN(authorization, " ", 2) + if len(s) != 2 || s[0] != "Digest" { + return nil + } + + return ParsePairs(s[1]) +} + +/* + Check if request contains valid authentication data. Returns a pair + of username, authinfo where username is the name of the authenticated + user or an empty string and authinfo is the contents for the optional + Authentication-Info response header. +*/ +func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) { + da.mutex.Lock() + defer da.mutex.Unlock() + username = "" + authinfo = nil + auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization)) + if auth == nil { + return "", nil + } + // RFC2617 Section 3.2.1 specifies that unset value of algorithm in + // WWW-Authenticate Response header should be treated as + // "MD5". According to section 3.2.2 the "algorithm" value in + // subsequent Request Authorization header must be set to whatever + // was supplied in the WWW-Authenticate Response header. This + // implementation always returns an algorithm in WWW-Authenticate + // header, however there seems to be broken clients in the wild + // which do not set the algorithm. Assume the unset algorithm in + // Authorization header to be equal to MD5. + if _, ok := auth["algorithm"]; !ok { + auth["algorithm"] = "MD5" + } + if da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" { + return "", nil + } + + // Check if the requested URI matches auth header + if r.RequestURI != auth["uri"] { + // We allow auth["uri"] to be a full path prefix of request-uri + // for some reason lost in history, which is probably wrong, but + // used to be like that for quite some time + // (https://tools.ietf.org/html/rfc2617#section-3.2.2 explicitly + // says that auth["uri"] is the request-uri). + // + // TODO: make an option to allow only strict checking. + switch u, err := url.Parse(auth["uri"]); { + case err != nil: + return "", nil + case r.URL == nil: + return "", nil + case len(u.Path) > len(r.URL.Path): + return "", nil + case !strings.HasPrefix(r.URL.Path, u.Path): + return "", nil + } + } + + HA1 := da.Secrets(auth["username"], da.Realm) + if da.PlainTextSecrets { + HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1) + } + HA2 := H(r.Method + ":" + auth["uri"]) + KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":")) + + if subtle.ConstantTimeCompare([]byte(KD), []byte(auth["response"])) != 1 { + return "", nil + } + + // At this point crypto checks are completed and validated. + // Now check if the session is valid. + + nc, err := strconv.ParseUint(auth["nc"], 16, 64) + if err != nil { + return "", nil + } + + if client, ok := da.clients[auth["nonce"]]; !ok { + return "", nil + } else { + if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount { + return "", nil + } + client.nc = nc + client.last_seen = time.Now().UnixNano() + } + + resp_HA2 := H(":" + auth["uri"]) + rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":")) + + info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"]) + return auth["username"], &info +} + +/* + Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth +*/ +const DefaultClientCacheSize = 1000 +const DefaultClientCacheTolerance = 100 + +/* + Wrap returns an Authenticator which uses HTTP Digest + authentication. Arguments: + + realm: The authentication realm. + + secrets: SecretProvider which must return HA1 digests for the same + realm as above. +*/ +func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if username, authinfo := a.CheckAuth(r); username == "" { + a.RequireAuth(w, r) + } else { + ar := &AuthenticatedRequest{Request: *r, Username: username} + if authinfo != nil { + w.Header().Set(a.Headers.V().AuthInfo, *authinfo) + } + wrapped(w, ar) + } + } +} + +/* + JustCheck returns function which converts an http.HandlerFunc into a + http.HandlerFunc which requires authentication. Username is passed as + an extra X-Authenticated-Username header. +*/ +func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc { + return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) { + ar.Header.Set(AuthUsernameHeader, ar.Username) + wrapped(w, &ar.Request) + }) +} + +// NewContext returns a context carrying authentication information for the request. +func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context { + username, authinfo := a.CheckAuth(r) + info := &Info{Username: username, ResponseHeaders: make(http.Header)} + if username != "" { + info.Authenticated = true + info.ResponseHeaders.Set(a.Headers.V().AuthInfo, *authinfo) + } else { + // return back digest WWW-Authenticate header + if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance { + a.Purge(a.ClientCacheTolerance * 2) + } + nonce := RandomKey() + a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()} + info.ResponseHeaders.Set(a.Headers.V().Authenticate, + fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`, + a.Realm, nonce, a.Opaque)) + } + return context.WithValue(ctx, infoKey, info) +} + +func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth { + da := &DigestAuth{ + Opaque: RandomKey(), + Realm: realm, + Secrets: secrets, + PlainTextSecrets: false, + ClientCacheSize: DefaultClientCacheSize, + ClientCacheTolerance: DefaultClientCacheTolerance, + clients: map[string]*digest_client{}} + return da +} diff --git a/vendor/github.com/abbot/go-http-auth/md5crypt.go b/vendor/github.com/abbot/go-http-auth/md5crypt.go new file mode 100644 index 0000000..a7a031c --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/md5crypt.go @@ -0,0 +1,92 @@ +package auth + +import "crypto/md5" +import "strings" + +const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + +var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11} + +type MD5Entry struct { + Magic, Salt, Hash []byte +} + +func NewMD5Entry(e string) *MD5Entry { + parts := strings.SplitN(e, "$", 4) + if len(parts) != 4 { + return nil + } + return &MD5Entry{ + Magic: []byte("$" + parts[1] + "$"), + Salt: []byte(parts[2]), + Hash: []byte(parts[3]), + } +} + +/* + MD5 password crypt implementation +*/ +func MD5Crypt(password, salt, magic []byte) []byte { + d := md5.New() + + d.Write(password) + d.Write(magic) + d.Write(salt) + + d2 := md5.New() + d2.Write(password) + d2.Write(salt) + d2.Write(password) + + for i, mixin := 0, d2.Sum(nil); i < len(password); i++ { + d.Write([]byte{mixin[i%16]}) + } + + for i := len(password); i != 0; i >>= 1 { + if i&1 == 0 { + d.Write([]byte{password[0]}) + } else { + d.Write([]byte{0}) + } + } + + final := d.Sum(nil) + + for i := 0; i < 1000; i++ { + d2 := md5.New() + if i&1 == 0 { + d2.Write(final) + } else { + d2.Write(password) + } + + if i%3 != 0 { + d2.Write(salt) + } + + if i%7 != 0 { + d2.Write(password) + } + + if i&1 == 0 { + d2.Write(password) + } else { + d2.Write(final) + } + final = d2.Sum(nil) + } + + result := make([]byte, 0, 22) + v := uint(0) + bits := uint(0) + for _, i := range md5_crypt_swaps { + v |= (uint(final[i]) << bits) + for bits = bits + 8; bits > 6; bits -= 6 { + result = append(result, itoa64[v&0x3f]) + v >>= 6 + } + } + result = append(result, itoa64[v&0x3f]) + + return append(append(append(magic, salt...), '$'), result...) +} diff --git a/vendor/github.com/abbot/go-http-auth/misc.go b/vendor/github.com/abbot/go-http-auth/misc.go new file mode 100644 index 0000000..4536ce6 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/misc.go @@ -0,0 +1,141 @@ +package auth + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "strings" +) + +// RandomKey returns a random 16-byte base64 alphabet string +func RandomKey() string { + k := make([]byte, 12) + for bytes := 0; bytes < len(k); { + n, err := rand.Read(k[bytes:]) + if err != nil { + panic("rand.Read() failed") + } + bytes += n + } + return base64.StdEncoding.EncodeToString(k) +} + +// H function for MD5 algorithm (returns a lower-case hex MD5 digest) +func H(data string) string { + digest := md5.New() + digest.Write([]byte(data)) + return fmt.Sprintf("%x", digest.Sum(nil)) +} + +// ParseList parses a comma-separated list of values as described by +// RFC 2068 and returns list elements. +// +// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go +// which was ported from urllib2.parse_http_list, from the Python +// standard library. +func ParseList(value string) []string { + var list []string + var escape, quote bool + b := new(bytes.Buffer) + for _, r := range value { + switch { + case escape: + b.WriteRune(r) + escape = false + case quote: + if r == '\\' { + escape = true + } else { + if r == '"' { + quote = false + } + b.WriteRune(r) + } + case r == ',': + list = append(list, strings.TrimSpace(b.String())) + b.Reset() + case r == '"': + quote = true + b.WriteRune(r) + default: + b.WriteRune(r) + } + } + // Append last part. + if s := b.String(); s != "" { + list = append(list, strings.TrimSpace(s)) + } + return list +} + +// ParsePairs extracts key/value pairs from a comma-separated list of +// values as described by RFC 2068 and returns a map[key]value. The +// resulting values are unquoted. If a list element doesn't contain a +// "=", the key is the element itself and the value is an empty +// string. +// +// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go +func ParsePairs(value string) map[string]string { + m := make(map[string]string) + for _, pair := range ParseList(strings.TrimSpace(value)) { + if i := strings.Index(pair, "="); i < 0 { + m[pair] = "" + } else { + v := pair[i+1:] + if v[0] == '"' && v[len(v)-1] == '"' { + // Unquote it. + v = v[1 : len(v)-1] + } + m[pair[:i]] = v + } + } + return m +} + +// Headers contains header and error codes used by authenticator. +type Headers struct { + Authenticate string // WWW-Authenticate + Authorization string // Authorization + AuthInfo string // Authentication-Info + UnauthCode int // 401 + UnauthContentType string // text/plain + UnauthResponse string // Unauthorized. +} + +// V returns NormalHeaders when h is nil, or h otherwise. Allows to +// use uninitialized *Headers values in structs. +func (h *Headers) V() *Headers { + if h == nil { + return NormalHeaders + } + return h +} + +var ( + // NormalHeaders are the regular Headers used by an HTTP Server for + // request authentication. + NormalHeaders = &Headers{ + Authenticate: "WWW-Authenticate", + Authorization: "Authorization", + AuthInfo: "Authentication-Info", + UnauthCode: http.StatusUnauthorized, + UnauthContentType: "text/plain", + UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)), + } + + // ProxyHeaders are Headers used by an HTTP Proxy server for proxy + // access authentication. + ProxyHeaders = &Headers{ + Authenticate: "Proxy-Authenticate", + Authorization: "Proxy-Authorization", + AuthInfo: "Proxy-Authentication-Info", + UnauthCode: http.StatusProxyAuthRequired, + UnauthContentType: "text/plain", + UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusProxyAuthRequired, http.StatusText(http.StatusProxyAuthRequired)), + } +) + +const contentType = "Content-Type" diff --git a/vendor/github.com/abbot/go-http-auth/test.htdigest b/vendor/github.com/abbot/go-http-auth/test.htdigest new file mode 100644 index 0000000..6c8c75b --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/test.htdigest @@ -0,0 +1 @@ +test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9 diff --git a/vendor/github.com/abbot/go-http-auth/test.htpasswd b/vendor/github.com/abbot/go-http-auth/test.htpasswd new file mode 100644 index 0000000..4844a3c --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/test.htpasswd @@ -0,0 +1,4 @@ +test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00= +test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0 +test16:$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0 +test3:$2y$05$ih3C91zUBSTFcAh2mQnZYuob0UOZVEf16wl/ukgjDhjvj.xgM1WwS diff --git a/vendor/github.com/abbot/go-http-auth/users.go b/vendor/github.com/abbot/go-http-auth/users.go new file mode 100644 index 0000000..3771812 --- /dev/null +++ b/vendor/github.com/abbot/go-http-auth/users.go @@ -0,0 +1,154 @@ +package auth + +import ( + "encoding/csv" + "os" + "sync" +) + +/* + SecretProvider is used by authenticators. Takes user name and realm + as an argument, returns secret required for authentication (HA1 for + digest authentication, properly encrypted password for basic). + + Returning an empty string means failing the authentication. +*/ +type SecretProvider func(user, realm string) string + +/* + Common functions for file auto-reloading +*/ +type File struct { + Path string + Info os.FileInfo + /* must be set in inherited types during initialization */ + Reload func() + mu sync.Mutex +} + +func (f *File) ReloadIfNeeded() { + info, err := os.Stat(f.Path) + if err != nil { + panic(err) + } + f.mu.Lock() + defer f.mu.Unlock() + if f.Info == nil || f.Info.ModTime() != info.ModTime() { + f.Info = info + f.Reload() + } +} + +/* + Structure used for htdigest file authentication. Users map realms to + maps of users to their HA1 digests. +*/ +type HtdigestFile struct { + File + Users map[string]map[string]string + mu sync.RWMutex +} + +func reload_htdigest(hf *HtdigestFile) { + r, err := os.Open(hf.Path) + if err != nil { + panic(err) + } + csv_reader := csv.NewReader(r) + csv_reader.Comma = ':' + csv_reader.Comment = '#' + csv_reader.TrimLeadingSpace = true + + records, err := csv_reader.ReadAll() + if err != nil { + panic(err) + } + + hf.mu.Lock() + defer hf.mu.Unlock() + hf.Users = make(map[string]map[string]string) + for _, record := range records { + _, exists := hf.Users[record[1]] + if !exists { + hf.Users[record[1]] = make(map[string]string) + } + hf.Users[record[1]][record[0]] = record[2] + } +} + +/* + SecretProvider implementation based on htdigest-formated files. Will + reload htdigest file on changes. Will panic on syntax errors in + htdigest files. +*/ +func HtdigestFileProvider(filename string) SecretProvider { + hf := &HtdigestFile{File: File{Path: filename}} + hf.Reload = func() { reload_htdigest(hf) } + return func(user, realm string) string { + hf.ReloadIfNeeded() + hf.mu.RLock() + defer hf.mu.RUnlock() + _, exists := hf.Users[realm] + if !exists { + return "" + } + digest, exists := hf.Users[realm][user] + if !exists { + return "" + } + return digest + } +} + +/* + Structure used for htdigest file authentication. Users map users to + their salted encrypted password +*/ +type HtpasswdFile struct { + File + Users map[string]string + mu sync.RWMutex +} + +func reload_htpasswd(h *HtpasswdFile) { + r, err := os.Open(h.Path) + if err != nil { + panic(err) + } + csv_reader := csv.NewReader(r) + csv_reader.Comma = ':' + csv_reader.Comment = '#' + csv_reader.TrimLeadingSpace = true + + records, err := csv_reader.ReadAll() + if err != nil { + panic(err) + } + + h.mu.Lock() + defer h.mu.Unlock() + h.Users = make(map[string]string) + for _, record := range records { + h.Users[record[0]] = record[1] + } +} + +/* + SecretProvider implementation based on htpasswd-formated files. Will + reload htpasswd file on changes. Will panic on syntax errors in + htpasswd files. Realm argument of the SecretProvider is ignored. +*/ +func HtpasswdFileProvider(filename string) SecretProvider { + h := &HtpasswdFile{File: File{Path: filename}} + h.Reload = func() { reload_htpasswd(h) } + return func(user, realm string) string { + h.ReloadIfNeeded() + h.mu.RLock() + password, exists := h.Users[user] + h.mu.RUnlock() + if !exists { + return "" + } + return password + } +} diff --git a/vendor/github.com/beevik/etree/.travis.yml b/vendor/github.com/beevik/etree/.travis.yml new file mode 100644 index 0000000..7caa247 --- /dev/null +++ b/vendor/github.com/beevik/etree/.travis.yml @@ -0,0 +1,16 @@ +language: go +sudo: false + +go: + - 1.4.2 + - 1.5.1 + - 1.6 + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go vet ./... + - go test -v ./... diff --git a/vendor/github.com/beevik/etree/CONTRIBUTORS b/vendor/github.com/beevik/etree/CONTRIBUTORS new file mode 100644 index 0000000..084662c --- /dev/null +++ b/vendor/github.com/beevik/etree/CONTRIBUTORS @@ -0,0 +1,8 @@ +Brett Vickers (beevik) +Felix Geisendörfer (felixge) +Kamil Kisiel (kisielk) +Graham King (grahamking) +Matt Smith (ma314smith) +Michal Jemala (michaljemala) +Nicolas Piganeau (npiganeau) +Chris Brown (ccbrown) diff --git a/vendor/github.com/beevik/etree/LICENSE b/vendor/github.com/beevik/etree/LICENSE new file mode 100644 index 0000000..e14ad68 --- /dev/null +++ b/vendor/github.com/beevik/etree/LICENSE @@ -0,0 +1,24 @@ +Copyright 2015 Brett Vickers. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/beevik/etree/README.md b/vendor/github.com/beevik/etree/README.md new file mode 100644 index 0000000..2855843 --- /dev/null +++ b/vendor/github.com/beevik/etree/README.md @@ -0,0 +1,203 @@ +[![Build Status](https://travis-ci.org/beevik/etree.svg?branch=master)](https://travis-ci.org/beevik/etree) +[![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree) + +etree +===== + +The etree package is a lightweight, pure go package that expresses XML in +the form of an element tree. Its design was inspired by the Python +[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html) +module. Some of the package's features include: + +* Represents XML documents as trees of elements for easy traversal. +* Imports, serializes, modifies or creates XML documents from scratch. +* Writes and reads XML to/from files, byte slices, strings and io interfaces. +* Performs simple or complex searches with lightweight XPath-like query APIs. +* Auto-indents XML using spaces or tabs for better readability. +* Implemented in pure go; depends only on standard go libraries. +* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml) + package. + +### Creating an XML document + +The following example creates an XML document from scratch using the etree +package and outputs its indented contents to stdout. +```go +doc := etree.NewDocument() +doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) +doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`) + +people := doc.CreateElement("People") +people.CreateComment("These are all known people") + +jon := people.CreateElement("Person") +jon.CreateAttr("name", "Jon") + +sally := people.CreateElement("Person") +sally.CreateAttr("name", "Sally") + +doc.Indent(2) +doc.WriteTo(os.Stdout) +``` + +Output: +```xml + + + + + + + +``` + +### Reading an XML file + +Suppose you have a file on disk called `bookstore.xml` containing the +following data: + +```xml + + + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + + Harry Potter + J K. Rowling + 2005 + 29.99 + + + + XQuery Kick Start + James McGovern + Per Bothner + Kurt Cagle + James Linn + Vaidyanathan Nagarajan + 2003 + 49.99 + + + + Learning XML + Erik T. Ray + 2003 + 39.95 + + + +``` + +This code reads the file's contents into an etree document. +```go +doc := etree.NewDocument() +if err := doc.ReadFromFile("bookstore.xml"); err != nil { + panic(err) +} +``` + +You can also read XML from a string, a byte slice, or an `io.Reader`. + +### Processing elements and attributes + +This example illustrates several ways to access elements and attributes using +etree selection queries. +```go +root := doc.SelectElement("bookstore") +fmt.Println("ROOT element:", root.Tag) + +for _, book := range root.SelectElements("book") { + fmt.Println("CHILD element:", book.Tag) + if title := book.SelectElement("title"); title != nil { + lang := title.SelectAttrValue("lang", "unknown") + fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang) + } + for _, attr := range book.Attr { + fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value) + } +} +``` +Output: +``` +ROOT element: bookstore +CHILD element: book + TITLE: Everyday Italian (en) + ATTR: category=COOKING +CHILD element: book + TITLE: Harry Potter (en) + ATTR: category=CHILDREN +CHILD element: book + TITLE: XQuery Kick Start (en) + ATTR: category=WEB +CHILD element: book + TITLE: Learning XML (en) + ATTR: category=WEB +``` + +### Path queries + +This example uses etree's path functions to select all book titles that fall +into the category of 'WEB'. The double-slash prefix in the path causes the +search for book elements to occur recursively; book elements may appear at any +level of the XML hierarchy. +```go +for _, t := range doc.FindElements("//book[@category='WEB']/title") { + fmt.Println("Title:", t.Text()) +} +``` + +Output: +``` +Title: XQuery Kick Start +Title: Learning XML +``` + +This example finds the first book element under the root bookstore element and +outputs the tag and text of each of its child elements. +```go +for _, e := range doc.FindElements("./bookstore/book[1]/*") { + fmt.Printf("%s: %s\n", e.Tag, e.Text()) +} +``` + +Output: +``` +title: Everyday Italian +author: Giada De Laurentiis +year: 2005 +price: 30.00 +``` + +This example finds all books with a price of 49.99 and outputs their titles. +```go +path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title") +for _, e := range doc.FindElementsPath(path) { + fmt.Println(e.Text()) +} +``` + +Output: +``` +XQuery Kick Start +``` + +Note that this example uses the FindElementsPath function, which takes as an +argument a pre-compiled path object. Use precompiled paths when you plan to +search with the same path more than once. + +### Other features + +These are just a few examples of the things the etree package can do. See the +[documentation](http://godoc.org/github.com/beevik/etree) for a complete +description of its capabilities. + +### Contributing + +This project accepts contributions. Just fork the repo and submit a pull +request! diff --git a/vendor/github.com/beevik/etree/etree.go b/vendor/github.com/beevik/etree/etree.go new file mode 100644 index 0000000..36b279f --- /dev/null +++ b/vendor/github.com/beevik/etree/etree.go @@ -0,0 +1,943 @@ +// Copyright 2015 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package etree provides XML services through an Element Tree +// abstraction. +package etree + +import ( + "bufio" + "bytes" + "encoding/xml" + "errors" + "io" + "os" + "strings" +) + +const ( + // NoIndent is used with Indent to disable all indenting. + NoIndent = -1 +) + +// ErrXML is returned when XML parsing fails due to incorrect formatting. +var ErrXML = errors.New("etree: invalid XML format") + +// ReadSettings allow for changing the default behavior of the ReadFrom* +// methods. +type ReadSettings struct { + // CharsetReader to be passed to standard xml.Decoder. Default: nil. + CharsetReader func(charset string, input io.Reader) (io.Reader, error) + + // Permissive allows input containing common mistakes such as missing tags + // or attribute values. Default: false. + Permissive bool +} + +// newReadSettings creates a default ReadSettings record. +func newReadSettings() ReadSettings { + return ReadSettings{} +} + +// WriteSettings allow for changing the serialization behavior of the WriteTo* +// methods. +type WriteSettings struct { + // CanonicalEndTags forces the production of XML end tags, even for + // elements that have no child elements. Default: false. + CanonicalEndTags bool + + // CanonicalText forces the production of XML character references for + // text data characters &, <, and >. If false, XML character references + // are also produced for " and '. Default: false. + CanonicalText bool + + // CanonicalAttrVal forces the production of XML character references for + // attribute value characters &, < and ". If false, XML character + // references are also produced for > and '. Default: false. + CanonicalAttrVal bool +} + +// newWriteSettings creates a default WriteSettings record. +func newWriteSettings() WriteSettings { + return WriteSettings{ + CanonicalEndTags: false, + CanonicalText: false, + CanonicalAttrVal: false, + } +} + +// A Token is an empty interface that represents an Element, CharData, +// Comment, Directive, or ProcInst. +type Token interface { + Parent() *Element + dup(parent *Element) Token + setParent(parent *Element) + writeTo(w *bufio.Writer, s *WriteSettings) +} + +// A Document is a container holding a complete XML hierarchy. Its embedded +// element contains zero or more children, one of which is usually the root +// element. The embedded element may include other children such as +// processing instructions or BOM CharData tokens. +type Document struct { + Element + ReadSettings ReadSettings + WriteSettings WriteSettings +} + +// An Element represents an XML element, its attributes, and its child tokens. +type Element struct { + Space, Tag string // namespace and tag + Attr []Attr // key-value attribute pairs + Child []Token // child tokens (elements, comments, etc.) + parent *Element // parent element +} + +// An Attr represents a key-value attribute of an XML element. +type Attr struct { + Space, Key string // The attribute's namespace and key + Value string // The attribute value string +} + +// CharData represents character data within XML. +type CharData struct { + Data string + parent *Element + whitespace bool +} + +// A Comment represents an XML comment. +type Comment struct { + Data string + parent *Element +} + +// A Directive represents an XML directive. +type Directive struct { + Data string + parent *Element +} + +// A ProcInst represents an XML processing instruction. +type ProcInst struct { + Target string + Inst string + parent *Element +} + +// NewDocument creates an XML document without a root element. +func NewDocument() *Document { + return &Document{ + Element{Child: make([]Token, 0)}, + newReadSettings(), + newWriteSettings(), + } +} + +// Copy returns a recursive, deep copy of the document. +func (d *Document) Copy() *Document { + return &Document{*(d.dup(nil).(*Element)), d.ReadSettings, d.WriteSettings} +} + +// Root returns the root element of the document, or nil if there is no root +// element. +func (d *Document) Root() *Element { + for _, t := range d.Child { + if c, ok := t.(*Element); ok { + return c + } + } + return nil +} + +// SetRoot replaces the document's root element with e. If the document +// already has a root when this function is called, then the document's +// original root is unbound first. If the element e is bound to another +// document (or to another element within a document), then it is unbound +// first. +func (d *Document) SetRoot(e *Element) { + if e.parent != nil { + e.parent.RemoveChild(e) + } + e.setParent(&d.Element) + + for i, t := range d.Child { + if _, ok := t.(*Element); ok { + t.setParent(nil) + d.Child[i] = e + return + } + } + d.Child = append(d.Child, e) +} + +// ReadFrom reads XML from the reader r into the document d. It returns the +// number of bytes read and any error encountered. +func (d *Document) ReadFrom(r io.Reader) (n int64, err error) { + return d.Element.readFrom(r, d.ReadSettings) +} + +// ReadFromFile reads XML from the string s into the document d. +func (d *Document) ReadFromFile(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + _, err = d.ReadFrom(f) + return err +} + +// ReadFromBytes reads XML from the byte slice b into the document d. +func (d *Document) ReadFromBytes(b []byte) error { + _, err := d.ReadFrom(bytes.NewReader(b)) + return err +} + +// ReadFromString reads XML from the string s into the document d. +func (d *Document) ReadFromString(s string) error { + _, err := d.ReadFrom(strings.NewReader(s)) + return err +} + +// WriteTo serializes an XML document into the writer w. It +// returns the number of bytes written and any error encountered. +func (d *Document) WriteTo(w io.Writer) (n int64, err error) { + cw := newCountWriter(w) + b := bufio.NewWriter(cw) + for _, c := range d.Child { + c.writeTo(b, &d.WriteSettings) + } + err, n = b.Flush(), cw.bytes + return +} + +// WriteToFile serializes an XML document into the file named +// filename. +func (d *Document) WriteToFile(filename string) error { + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + _, err = d.WriteTo(f) + return err +} + +// WriteToBytes serializes the XML document into a slice of +// bytes. +func (d *Document) WriteToBytes() (b []byte, err error) { + var buf bytes.Buffer + if _, err = d.WriteTo(&buf); err != nil { + return + } + return buf.Bytes(), nil +} + +// WriteToString serializes the XML document into a string. +func (d *Document) WriteToString() (s string, err error) { + var b []byte + if b, err = d.WriteToBytes(); err != nil { + return + } + return string(b), nil +} + +type indentFunc func(depth int) string + +// Indent modifies the document's element tree by inserting CharData entities +// containing carriage returns and indentation. The amount of indentation per +// depth level is given as spaces. Pass etree.NoIndent for spaces if you want +// no indentation at all. +func (d *Document) Indent(spaces int) { + var indent indentFunc + switch { + case spaces < 0: + indent = func(depth int) string { return "" } + default: + indent = func(depth int) string { return crIndent(depth*spaces, crsp) } + } + d.Element.indent(0, indent) +} + +// IndentTabs modifies the document's element tree by inserting CharData +// entities containing carriage returns and tabs for indentation. One tab is +// used per indentation level. +func (d *Document) IndentTabs() { + indent := func(depth int) string { return crIndent(depth, crtab) } + d.Element.indent(0, indent) +} + +// NewElement creates an unparented element with the specified tag. The tag +// may be prefixed by a namespace and a colon. +func NewElement(tag string) *Element { + space, stag := spaceDecompose(tag) + return newElement(space, stag, nil) +} + +// newElement is a helper function that creates an element and binds it to +// a parent element if possible. +func newElement(space, tag string, parent *Element) *Element { + e := &Element{ + Space: space, + Tag: tag, + Attr: make([]Attr, 0), + Child: make([]Token, 0), + parent: parent, + } + if parent != nil { + parent.addChild(e) + } + return e +} + +// Copy creates a recursive, deep copy of the element and all its attributes +// and children. The returned element has no parent but can be parented to a +// another element using AddElement, or to a document using SetRoot. +func (e *Element) Copy() *Element { + var parent *Element + return e.dup(parent).(*Element) +} + +// Text returns the characters immediately following the element's +// opening tag. +func (e *Element) Text() string { + if len(e.Child) == 0 { + return "" + } + if cd, ok := e.Child[0].(*CharData); ok { + return cd.Data + } + return "" +} + +// SetText replaces an element's subsidiary CharData text with a new string. +func (e *Element) SetText(text string) { + if len(e.Child) > 0 { + if cd, ok := e.Child[0].(*CharData); ok { + cd.Data = text + return + } + } + cd := newCharData(text, false, e) + copy(e.Child[1:], e.Child[0:]) + e.Child[0] = cd +} + +// CreateElement creates an element with the specified tag and adds it as the +// last child element of the element e. The tag may be prefixed by a namespace +// and a colon. +func (e *Element) CreateElement(tag string) *Element { + space, stag := spaceDecompose(tag) + return newElement(space, stag, e) +} + +// AddChild adds the token t as the last child of element e. If token t was +// already the child of another element, it is first removed from its current +// parent element. +func (e *Element) AddChild(t Token) { + if t.Parent() != nil { + t.Parent().RemoveChild(t) + } + t.setParent(e) + e.addChild(t) +} + +// InsertChild inserts the token t before e's existing child token ex. If ex +// is nil (or if ex is not a child of e), then t is added to the end of e's +// child token list. If token t was already the child of another element, it +// is first removed from its current parent element. +func (e *Element) InsertChild(ex Token, t Token) { + if t.Parent() != nil { + t.Parent().RemoveChild(t) + } + t.setParent(e) + + for i, c := range e.Child { + if c == ex { + e.Child = append(e.Child, nil) + copy(e.Child[i+1:], e.Child[i:]) + e.Child[i] = t + return + } + } + e.addChild(t) +} + +// RemoveChild attempts to remove the token t from element e's list of +// children. If the token t is a child of e, then it is returned. Otherwise, +// nil is returned. +func (e *Element) RemoveChild(t Token) Token { + for i, c := range e.Child { + if c == t { + e.Child = append(e.Child[:i], e.Child[i+1:]...) + c.setParent(nil) + return t + } + } + return nil +} + +// ReadFrom reads XML from the reader r and stores the result as a new child +// of element e. +func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) { + r := newCountReader(ri) + dec := xml.NewDecoder(r) + dec.CharsetReader = settings.CharsetReader + dec.Strict = !settings.Permissive + var stack stack + stack.push(e) + for { + t, err := dec.RawToken() + switch { + case err == io.EOF: + return r.bytes, nil + case err != nil: + return r.bytes, err + case stack.empty(): + return r.bytes, ErrXML + } + + top := stack.peek().(*Element) + + switch t := t.(type) { + case xml.StartElement: + e := newElement(t.Name.Space, t.Name.Local, top) + for _, a := range t.Attr { + e.createAttr(a.Name.Space, a.Name.Local, a.Value) + } + stack.push(e) + case xml.EndElement: + stack.pop() + case xml.CharData: + data := string(t) + newCharData(data, isWhitespace(data), top) + case xml.Comment: + newComment(string(t), top) + case xml.Directive: + newDirective(string(t), top) + case xml.ProcInst: + newProcInst(t.Target, string(t.Inst), top) + } + } +} + +// SelectAttr finds an element attribute matching the requested key and +// returns it if found. The key may be prefixed by a namespace and a colon. +func (e *Element) SelectAttr(key string) *Attr { + space, skey := spaceDecompose(key) + for i, a := range e.Attr { + if spaceMatch(space, a.Space) && skey == a.Key { + return &e.Attr[i] + } + } + return nil +} + +// SelectAttrValue finds an element attribute matching the requested key and +// returns its value if found. The key may be prefixed by a namespace and a +// colon. If the key is not found, the dflt value is returned instead. +func (e *Element) SelectAttrValue(key, dflt string) string { + space, skey := spaceDecompose(key) + for _, a := range e.Attr { + if spaceMatch(space, a.Space) && skey == a.Key { + return a.Value + } + } + return dflt +} + +// ChildElements returns all elements that are children of element e. +func (e *Element) ChildElements() []*Element { + var elements []*Element + for _, t := range e.Child { + if c, ok := t.(*Element); ok { + elements = append(elements, c) + } + } + return elements +} + +// SelectElement returns the first child element with the given tag. The tag +// may be prefixed by a namespace and a colon. +func (e *Element) SelectElement(tag string) *Element { + space, stag := spaceDecompose(tag) + for _, t := range e.Child { + if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag { + return c + } + } + return nil +} + +// SelectElements returns a slice of all child elements with the given tag. +// The tag may be prefixed by a namespace and a colon. +func (e *Element) SelectElements(tag string) []*Element { + space, stag := spaceDecompose(tag) + var elements []*Element + for _, t := range e.Child { + if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag { + elements = append(elements, c) + } + } + return elements +} + +// FindElement returns the first element matched by the XPath-like path +// string. Panics if an invalid path string is supplied. +func (e *Element) FindElement(path string) *Element { + return e.FindElementPath(MustCompilePath(path)) +} + +// FindElementPath returns the first element matched by the XPath-like path +// string. +func (e *Element) FindElementPath(path Path) *Element { + p := newPather() + elements := p.traverse(e, path) + switch { + case len(elements) > 0: + return elements[0] + default: + return nil + } +} + +// FindElements returns a slice of elements matched by the XPath-like path +// string. Panics if an invalid path string is supplied. +func (e *Element) FindElements(path string) []*Element { + return e.FindElementsPath(MustCompilePath(path)) +} + +// FindElementsPath returns a slice of elements matched by the Path object. +func (e *Element) FindElementsPath(path Path) []*Element { + p := newPather() + return p.traverse(e, path) +} + +// indent recursively inserts proper indentation between an +// XML element's child tokens. +func (e *Element) indent(depth int, indent indentFunc) { + e.stripIndent() + n := len(e.Child) + if n == 0 { + return + } + + oldChild := e.Child + e.Child = make([]Token, 0, n*2+1) + isCharData, firstNonCharData := false, true + for _, c := range oldChild { + + // Insert CR+indent before child if it's not character data. + // Exceptions: when it's the first non-character-data child, or when + // the child is at root depth. + _, isCharData = c.(*CharData) + if !isCharData { + if !firstNonCharData || depth > 0 { + newCharData(indent(depth), true, e) + } + firstNonCharData = false + } + + e.addChild(c) + + // Recursively process child elements. + if ce, ok := c.(*Element); ok { + ce.indent(depth+1, indent) + } + } + + // Insert CR+indent before the last child. + if !isCharData { + if !firstNonCharData || depth > 0 { + newCharData(indent(depth-1), true, e) + } + } +} + +// stripIndent removes any previously inserted indentation. +func (e *Element) stripIndent() { + // Count the number of non-indent child tokens + n := len(e.Child) + for _, c := range e.Child { + if cd, ok := c.(*CharData); ok && cd.whitespace { + n-- + } + } + if n == len(e.Child) { + return + } + + // Strip out indent CharData + newChild := make([]Token, n) + j := 0 + for _, c := range e.Child { + if cd, ok := c.(*CharData); ok && cd.whitespace { + continue + } + newChild[j] = c + j++ + } + e.Child = newChild +} + +// dup duplicates the element. +func (e *Element) dup(parent *Element) Token { + ne := &Element{ + Space: e.Space, + Tag: e.Tag, + Attr: make([]Attr, len(e.Attr)), + Child: make([]Token, len(e.Child)), + parent: parent, + } + for i, t := range e.Child { + ne.Child[i] = t.dup(ne) + } + for i, a := range e.Attr { + ne.Attr[i] = a + } + return ne +} + +// Parent returns the element token's parent element, or nil if it has no +// parent. +func (e *Element) Parent() *Element { + return e.parent +} + +// setParent replaces the element token's parent. +func (e *Element) setParent(parent *Element) { + e.parent = parent +} + +// writeTo serializes the element to the writer w. +func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteByte('<') + if e.Space != "" { + w.WriteString(e.Space) + w.WriteByte(':') + } + w.WriteString(e.Tag) + for _, a := range e.Attr { + w.WriteByte(' ') + a.writeTo(w, s) + } + if len(e.Child) > 0 { + w.WriteString(">") + for _, c := range e.Child { + c.writeTo(w, s) + } + w.Write([]byte{'<', '/'}) + if e.Space != "" { + w.WriteString(e.Space) + w.WriteByte(':') + } + w.WriteString(e.Tag) + w.WriteByte('>') + } else { + if s.CanonicalEndTags { + w.Write([]byte{'>', '<', '/'}) + if e.Space != "" { + w.WriteString(e.Space) + w.WriteByte(':') + } + w.WriteString(e.Tag) + w.WriteByte('>') + } else { + w.Write([]byte{'/', '>'}) + } + } +} + +// addChild adds a child token to the element e. +func (e *Element) addChild(t Token) { + e.Child = append(e.Child, t) +} + +// CreateAttr creates an attribute and adds it to element e. The key may be +// prefixed by a namespace and a colon. If an attribute with the key already +// exists, its value is replaced. +func (e *Element) CreateAttr(key, value string) *Attr { + space, skey := spaceDecompose(key) + return e.createAttr(space, skey, value) +} + +// createAttr is a helper function that creates attributes. +func (e *Element) createAttr(space, key, value string) *Attr { + for i, a := range e.Attr { + if space == a.Space && key == a.Key { + e.Attr[i].Value = value + return &e.Attr[i] + } + } + a := Attr{space, key, value} + e.Attr = append(e.Attr, a) + return &e.Attr[len(e.Attr)-1] +} + +// RemoveAttr removes and returns the first attribute of the element whose key +// matches the given key. The key may be prefixed by a namespace and a colon. +// If an equal attribute does not exist, nil is returned. +func (e *Element) RemoveAttr(key string) *Attr { + space, skey := spaceDecompose(key) + for i, a := range e.Attr { + if space == a.Space && skey == a.Key { + e.Attr = append(e.Attr[0:i], e.Attr[i+1:]...) + return &a + } + } + return nil +} + +var xmlReplacerNormal = strings.NewReplacer( + "&", "&", + "<", "<", + ">", ">", + "'", "'", + `"`, """, +) + +var xmlReplacerCanonicalText = strings.NewReplacer( + "&", "&", + "<", "<", + ">", ">", + "\r", " ", +) + +var xmlReplacerCanonicalAttrVal = strings.NewReplacer( + "&", "&", + "<", "<", + `"`, """, + "\t", " ", + "\n", " ", + "\r", " ", +) + +// writeTo serializes the attribute to the writer. +func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) { + if a.Space != "" { + w.WriteString(a.Space) + w.WriteByte(':') + } + w.WriteString(a.Key) + w.WriteString(`="`) + var r *strings.Replacer + if s.CanonicalAttrVal { + r = xmlReplacerCanonicalAttrVal + } else { + r = xmlReplacerNormal + } + w.WriteString(r.Replace(a.Value)) + w.WriteByte('"') +} + +// NewCharData creates a parentless XML character data entity. +func NewCharData(data string) *CharData { + return newCharData(data, false, nil) +} + +// newCharData creates an XML character data entity and binds it to a parent +// element. If parent is nil, the CharData token remains unbound. +func newCharData(data string, whitespace bool, parent *Element) *CharData { + c := &CharData{ + Data: data, + whitespace: whitespace, + parent: parent, + } + if parent != nil { + parent.addChild(c) + } + return c +} + +// CreateCharData creates an XML character data entity and adds it as a child +// of element e. +func (e *Element) CreateCharData(data string) *CharData { + return newCharData(data, false, e) +} + +// dup duplicates the character data. +func (c *CharData) dup(parent *Element) Token { + return &CharData{ + Data: c.Data, + whitespace: c.whitespace, + parent: parent, + } +} + +// Parent returns the character data token's parent element, or nil if it has +// no parent. +func (c *CharData) Parent() *Element { + return c.parent +} + +// setParent replaces the character data token's parent. +func (c *CharData) setParent(parent *Element) { + c.parent = parent +} + +// writeTo serializes the character data entity to the writer. +func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) { + var r *strings.Replacer + if s.CanonicalText { + r = xmlReplacerCanonicalText + } else { + r = xmlReplacerNormal + } + w.WriteString(r.Replace(c.Data)) +} + +// NewComment creates a parentless XML comment. +func NewComment(comment string) *Comment { + return newComment(comment, nil) +} + +// NewComment creates an XML comment and binds it to a parent element. If +// parent is nil, the Comment remains unbound. +func newComment(comment string, parent *Element) *Comment { + c := &Comment{ + Data: comment, + parent: parent, + } + if parent != nil { + parent.addChild(c) + } + return c +} + +// CreateComment creates an XML comment and adds it as a child of element e. +func (e *Element) CreateComment(comment string) *Comment { + return newComment(comment, e) +} + +// dup duplicates the comment. +func (c *Comment) dup(parent *Element) Token { + return &Comment{ + Data: c.Data, + parent: parent, + } +} + +// Parent returns comment token's parent element, or nil if it has no parent. +func (c *Comment) Parent() *Element { + return c.parent +} + +// setParent replaces the comment token's parent. +func (c *Comment) setParent(parent *Element) { + c.parent = parent +} + +// writeTo serialies the comment to the writer. +func (c *Comment) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} + +// NewDirective creates a parentless XML directive. +func NewDirective(data string) *Directive { + return newDirective(data, nil) +} + +// newDirective creates an XML directive and binds it to a parent element. If +// parent is nil, the Directive remains unbound. +func newDirective(data string, parent *Element) *Directive { + d := &Directive{ + Data: data, + parent: parent, + } + if parent != nil { + parent.addChild(d) + } + return d +} + +// CreateDirective creates an XML directive and adds it as the last child of +// element e. +func (e *Element) CreateDirective(data string) *Directive { + return newDirective(data, e) +} + +// dup duplicates the directive. +func (d *Directive) dup(parent *Element) Token { + return &Directive{ + Data: d.Data, + parent: parent, + } +} + +// Parent returns directive token's parent element, or nil if it has no +// parent. +func (d *Directive) Parent() *Element { + return d.parent +} + +// setParent replaces the directive token's parent. +func (d *Directive) setParent(parent *Element) { + d.parent = parent +} + +// writeTo serializes the XML directive to the writer. +func (d *Directive) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} + +// NewProcInst creates a parentless XML processing instruction. +func NewProcInst(target, inst string) *ProcInst { + return newProcInst(target, inst, nil) +} + +// newProcInst creates an XML processing instruction and binds it to a parent +// element. If parent is nil, the ProcInst remains unbound. +func newProcInst(target, inst string, parent *Element) *ProcInst { + p := &ProcInst{ + Target: target, + Inst: inst, + parent: parent, + } + if parent != nil { + parent.addChild(p) + } + return p +} + +// CreateProcInst creates a processing instruction and adds it as a child of +// element e. +func (e *Element) CreateProcInst(target, inst string) *ProcInst { + return newProcInst(target, inst, e) +} + +// dup duplicates the procinst. +func (p *ProcInst) dup(parent *Element) Token { + return &ProcInst{ + Target: p.Target, + Inst: p.Inst, + parent: parent, + } +} + +// Parent returns processing instruction token's parent element, or nil if it +// has no parent. +func (p *ProcInst) Parent() *Element { + return p.parent +} + +// setParent replaces the processing instruction token's parent. +func (p *ProcInst) setParent(parent *Element) { + p.parent = parent +} + +// writeTo serializes the processing instruction to the writer. +func (p *ProcInst) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} diff --git a/vendor/github.com/beevik/etree/helpers.go b/vendor/github.com/beevik/etree/helpers.go new file mode 100644 index 0000000..4f8350e --- /dev/null +++ b/vendor/github.com/beevik/etree/helpers.go @@ -0,0 +1,188 @@ +// Copyright 2015 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package etree + +import ( + "io" + "strings" +) + +// A simple stack +type stack struct { + data []interface{} +} + +func (s *stack) empty() bool { + return len(s.data) == 0 +} + +func (s *stack) push(value interface{}) { + s.data = append(s.data, value) +} + +func (s *stack) pop() interface{} { + value := s.data[len(s.data)-1] + s.data[len(s.data)-1] = nil + s.data = s.data[:len(s.data)-1] + return value +} + +func (s *stack) peek() interface{} { + return s.data[len(s.data)-1] +} + +// A fifo is a simple first-in-first-out queue. +type fifo struct { + data []interface{} + head, tail int +} + +func (f *fifo) add(value interface{}) { + if f.len()+1 >= len(f.data) { + f.grow() + } + f.data[f.tail] = value + if f.tail++; f.tail == len(f.data) { + f.tail = 0 + } +} + +func (f *fifo) remove() interface{} { + value := f.data[f.head] + f.data[f.head] = nil + if f.head++; f.head == len(f.data) { + f.head = 0 + } + return value +} + +func (f *fifo) len() int { + if f.tail >= f.head { + return f.tail - f.head + } + return len(f.data) - f.head + f.tail +} + +func (f *fifo) grow() { + c := len(f.data) * 2 + if c == 0 { + c = 4 + } + buf, count := make([]interface{}, c), f.len() + if f.tail >= f.head { + copy(buf[0:count], f.data[f.head:f.tail]) + } else { + hindex := len(f.data) - f.head + copy(buf[0:hindex], f.data[f.head:]) + copy(buf[hindex:count], f.data[:f.tail]) + } + f.data, f.head, f.tail = buf, 0, count +} + +// countReader implements a proxy reader that counts the number of +// bytes read from its encapsulated reader. +type countReader struct { + r io.Reader + bytes int64 +} + +func newCountReader(r io.Reader) *countReader { + return &countReader{r: r} +} + +func (cr *countReader) Read(p []byte) (n int, err error) { + b, err := cr.r.Read(p) + cr.bytes += int64(b) + return b, err +} + +// countWriter implements a proxy writer that counts the number of +// bytes written by its encapsulated writer. +type countWriter struct { + w io.Writer + bytes int64 +} + +func newCountWriter(w io.Writer) *countWriter { + return &countWriter{w: w} +} + +func (cw *countWriter) Write(p []byte) (n int, err error) { + b, err := cw.w.Write(p) + cw.bytes += int64(b) + return b, err +} + +// 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 +} + +// spaceMatch returns true if namespace a is the empty string +// or if namespace a equals namespace b. +func spaceMatch(a, b string) bool { + switch { + case a == "": + return true + default: + return a == b + } +} + +// spaceDecompose breaks a namespace:tag identifier at the ':' +// and returns the two parts. +func spaceDecompose(str string) (space, key string) { + colon := strings.IndexByte(str, ':') + if colon == -1 { + return "", str + } + return str[:colon], str[colon+1:] +} + +// Strings used by crIndent +const ( + crsp = "\n " + crtab = "\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" +) + +// crIndent returns a carriage return followed by n copies of the +// first non-CR character in the source string. +func crIndent(n int, source string) string { + switch { + case n < 0: + return source[:1] + case n < len(source): + return source[:n+1] + default: + return source + strings.Repeat(source[1:2], n-len(source)+1) + } +} + +// nextIndex returns the index of the next occurrence of sep in s, +// starting from offset. It returns -1 if the sep string is not found. +func nextIndex(s, sep string, offset int) int { + switch i := strings.Index(s[offset:], sep); i { + case -1: + return -1 + default: + return offset + i + } +} + +// isInteger returns true if the string s contains an integer. +func isInteger(s string) bool { + for i := 0; i < len(s); i++ { + if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') { + return false + } + } + return true +} diff --git a/vendor/github.com/beevik/etree/path.go b/vendor/github.com/beevik/etree/path.go new file mode 100644 index 0000000..9cf245e --- /dev/null +++ b/vendor/github.com/beevik/etree/path.go @@ -0,0 +1,516 @@ +// Copyright 2015 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package etree + +import ( + "strconv" + "strings" +) + +/* +A Path is an object that represents an optimized version of an +XPath-like search string. Although path strings are XPath-like, +only the following limited syntax is supported: + + . Selects the current element + .. Selects the parent of the current element + * Selects all child elements + // Selects all descendants of the current element + tag Selects all child elements with the given tag + [#] Selects the element of the given index (1-based, + negative starts from the end) + [@attrib] Selects all elements with the given attribute + [@attrib='val'] Selects all elements with the given attribute set to val + [tag] Selects all elements with a child element named tag + [tag='val'] Selects all elements with a child element named tag + and text matching val + [text()] Selects all elements with non-empty text + [text()='val'] Selects all elements whose text matches val + +Examples: + +Select the title elements of all descendant book elements having a +'category' attribute of 'WEB': + //book[@category='WEB']/title + +Select the first book element with a title child containing the text +'Great Expectations': + .//book[title='Great Expectations'][1] + +Starting from the current element, select all children of book elements +with an attribute 'language' set to 'english': + ./book/*[@language='english'] + +Starting from the current element, select all children of book elements +containing the text 'special': + ./book/*[text()='special'] + +Select all descendant book elements whose title element has an attribute +'language' set to 'french': + //book/title[@language='french']/.. + +*/ +type Path struct { + segments []segment +} + +// ErrPath is returned by path functions when an invalid etree path is provided. +type ErrPath string + +// Error returns the string describing a path error. +func (err ErrPath) Error() string { + return "etree: " + string(err) +} + +// CompilePath creates an optimized version of an XPath-like string that +// can be used to query elements in an element tree. +func CompilePath(path string) (Path, error) { + var comp compiler + segments := comp.parsePath(path) + if comp.err != ErrPath("") { + return Path{nil}, comp.err + } + return Path{segments}, nil +} + +// MustCompilePath creates an optimized version of an XPath-like string that +// can be used to query elements in an element tree. Panics if an error +// occurs. Use this function to create Paths when you know the path is +// valid (i.e., if it's hard-coded). +func MustCompilePath(path string) Path { + p, err := CompilePath(path) + if err != nil { + panic(err) + } + return p +} + +// A segment is a portion of a path between "/" characters. +// It contains one selector and zero or more [filters]. +type segment struct { + sel selector + filters []filter +} + +func (seg *segment) apply(e *Element, p *pather) { + seg.sel.apply(e, p) + for _, f := range seg.filters { + f.apply(p) + } +} + +// A selector selects XML elements for consideration by the +// path traversal. +type selector interface { + apply(e *Element, p *pather) +} + +// A filter pares down a list of candidate XML elements based +// on a path filter in [brackets]. +type filter interface { + apply(p *pather) +} + +// A pather is helper object that traverses an element tree using +// a Path object. It collects and deduplicates all elements matching +// the path query. +type pather struct { + queue fifo + results []*Element + inResults map[*Element]bool + candidates []*Element + scratch []*Element // used by filters +} + +// A node represents an element and the remaining path segments that +// should be applied against it by the pather. +type node struct { + e *Element + segments []segment +} + +func newPather() *pather { + return &pather{ + results: make([]*Element, 0), + inResults: make(map[*Element]bool), + candidates: make([]*Element, 0), + scratch: make([]*Element, 0), + } +} + +// traverse follows the path from the element e, collecting +// and then returning all elements that match the path's selectors +// and filters. +func (p *pather) traverse(e *Element, path Path) []*Element { + for p.queue.add(node{e, path.segments}); p.queue.len() > 0; { + p.eval(p.queue.remove().(node)) + } + return p.results +} + +// eval evalutes the current path node by applying the remaining +// path's selector rules against the node's element. +func (p *pather) eval(n node) { + p.candidates = p.candidates[0:0] + seg, remain := n.segments[0], n.segments[1:] + seg.apply(n.e, p) + + if len(remain) == 0 { + for _, c := range p.candidates { + if in := p.inResults[c]; !in { + p.inResults[c] = true + p.results = append(p.results, c) + } + } + } else { + for _, c := range p.candidates { + p.queue.add(node{c, remain}) + } + } +} + +// A compiler generates a compiled path from a path string. +type compiler struct { + err ErrPath +} + +// parsePath parses an XPath-like string describing a path +// through an element tree and returns a slice of segment +// descriptors. +func (c *compiler) parsePath(path string) []segment { + // If path starts or ends with //, fix it + if strings.HasPrefix(path, "//") { + path = "." + path + } + if strings.HasSuffix(path, "//") { + path = path + "*" + } + + // Paths cannot be absolute + if strings.HasPrefix(path, "/") { + c.err = ErrPath("paths cannot be absolute.") + return nil + } + + // Split path into segment objects + var segments []segment + for _, s := range splitPath(path) { + segments = append(segments, c.parseSegment(s)) + if c.err != ErrPath("") { + break + } + } + return segments +} + +func splitPath(path string) []string { + pieces := make([]string, 0) + start := 0 + inquote := false + for i := 0; i+1 <= len(path); i++ { + if path[i] == '\'' { + inquote = !inquote + } else if path[i] == '/' && !inquote { + pieces = append(pieces, path[start:i]) + start = i + 1 + } + } + return append(pieces, path[start:]) +} + +// parseSegment parses a path segment between / characters. +func (c *compiler) parseSegment(path string) segment { + pieces := strings.Split(path, "[") + seg := segment{ + sel: c.parseSelector(pieces[0]), + filters: make([]filter, 0), + } + for i := 1; i < len(pieces); i++ { + fpath := pieces[i] + if fpath[len(fpath)-1] != ']' { + c.err = ErrPath("path has invalid filter [brackets].") + break + } + seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1])) + } + return seg +} + +// parseSelector parses a selector at the start of a path segment. +func (c *compiler) parseSelector(path string) selector { + switch path { + case ".": + return new(selectSelf) + case "..": + return new(selectParent) + case "*": + return new(selectChildren) + case "": + return new(selectDescendants) + default: + return newSelectChildrenByTag(path) + } +} + +// parseFilter parses a path filter contained within [brackets]. +func (c *compiler) parseFilter(path string) filter { + if len(path) == 0 { + c.err = ErrPath("path contains an empty filter expression.") + return nil + } + + // Filter contains [@attr='val'], [text()='val'], or [tag='val']? + eqindex := strings.Index(path, "='") + if eqindex >= 0 { + rindex := nextIndex(path, "'", eqindex+2) + if rindex != len(path)-1 { + c.err = ErrPath("path has mismatched filter quotes.") + return nil + } + switch { + case path[0] == '@': + return newFilterAttrVal(path[1:eqindex], path[eqindex+2:rindex]) + case strings.HasPrefix(path, "text()"): + return newFilterTextVal(path[eqindex+2 : rindex]) + default: + return newFilterChildText(path[:eqindex], path[eqindex+2:rindex]) + } + } + + // Filter contains [@attr], [N], [tag] or [text()] + switch { + case path[0] == '@': + return newFilterAttr(path[1:]) + case path == "text()": + return newFilterText() + case isInteger(path): + pos, _ := strconv.Atoi(path) + switch { + case pos > 0: + return newFilterPos(pos - 1) + default: + return newFilterPos(pos) + } + default: + return newFilterChild(path) + } +} + +// selectSelf selects the current element into the candidate list. +type selectSelf struct{} + +func (s *selectSelf) apply(e *Element, p *pather) { + p.candidates = append(p.candidates, e) +} + +// selectParent selects the element's parent into the candidate list. +type selectParent struct{} + +func (s *selectParent) apply(e *Element, p *pather) { + if e.parent != nil { + p.candidates = append(p.candidates, e.parent) + } +} + +// selectChildren selects the element's child elements into the +// candidate list. +type selectChildren struct{} + +func (s *selectChildren) apply(e *Element, p *pather) { + for _, c := range e.Child { + if c, ok := c.(*Element); ok { + p.candidates = append(p.candidates, c) + } + } +} + +// selectDescendants selects all descendant child elements +// of the element into the candidate list. +type selectDescendants struct{} + +func (s *selectDescendants) apply(e *Element, p *pather) { + var queue fifo + for queue.add(e); queue.len() > 0; { + e := queue.remove().(*Element) + p.candidates = append(p.candidates, e) + for _, c := range e.Child { + if c, ok := c.(*Element); ok { + queue.add(c) + } + } + } +} + +// selectChildrenByTag selects into the candidate list all child +// elements of the element having the specified tag. +type selectChildrenByTag struct { + space, tag string +} + +func newSelectChildrenByTag(path string) *selectChildrenByTag { + s, l := spaceDecompose(path) + return &selectChildrenByTag{s, l} +} + +func (s *selectChildrenByTag) apply(e *Element, p *pather) { + for _, c := range e.Child { + if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag { + p.candidates = append(p.candidates, c) + } + } +} + +// filterPos filters the candidate list, keeping only the +// candidate at the specified index. +type filterPos struct { + index int +} + +func newFilterPos(pos int) *filterPos { + return &filterPos{pos} +} + +func (f *filterPos) apply(p *pather) { + if f.index >= 0 { + if f.index < len(p.candidates) { + p.scratch = append(p.scratch, p.candidates[f.index]) + } + } else { + if -f.index <= len(p.candidates) { + p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index]) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterAttr filters the candidate list for elements having +// the specified attribute. +type filterAttr struct { + space, key string +} + +func newFilterAttr(str string) *filterAttr { + s, l := spaceDecompose(str) + return &filterAttr{s, l} +} + +func (f *filterAttr) apply(p *pather) { + for _, c := range p.candidates { + for _, a := range c.Attr { + if spaceMatch(f.space, a.Space) && f.key == a.Key { + p.scratch = append(p.scratch, c) + break + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterAttrVal filters the candidate list for elements having +// the specified attribute with the specified value. +type filterAttrVal struct { + space, key, val string +} + +func newFilterAttrVal(str, value string) *filterAttrVal { + s, l := spaceDecompose(str) + return &filterAttrVal{s, l, value} +} + +func (f *filterAttrVal) apply(p *pather) { + for _, c := range p.candidates { + for _, a := range c.Attr { + if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value { + p.scratch = append(p.scratch, c) + break + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterText filters the candidate list for elements having text. +type filterText struct{} + +func newFilterText() *filterText { + return &filterText{} +} + +func (f *filterText) apply(p *pather) { + for _, c := range p.candidates { + if c.Text() != "" { + p.scratch = append(p.scratch, c) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterTextVal filters the candidate list for elements having +// text equal to the specified value. +type filterTextVal struct { + val string +} + +func newFilterTextVal(value string) *filterTextVal { + return &filterTextVal{value} +} + +func (f *filterTextVal) apply(p *pather) { + for _, c := range p.candidates { + if c.Text() == f.val { + p.scratch = append(p.scratch, c) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterChild filters the candidate list for elements having +// a child element with the specified tag. +type filterChild struct { + space, tag string +} + +func newFilterChild(str string) *filterChild { + s, l := spaceDecompose(str) + return &filterChild{s, l} +} + +func (f *filterChild) apply(p *pather) { + for _, c := range p.candidates { + for _, cc := range c.Child { + if cc, ok := cc.(*Element); ok && + spaceMatch(f.space, cc.Space) && + f.tag == cc.Tag { + p.scratch = append(p.scratch, c) + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterChildText filters the candidate list for elements having +// a child element with the specified tag and text. +type filterChildText struct { + space, tag, text string +} + +func newFilterChildText(str, text string) *filterChildText { + s, l := spaceDecompose(str) + return &filterChildText{s, l, text} +} + +func (f *filterChildText) apply(p *pather) { + for _, c := range p.candidates { + for _, cc := range c.Child { + if cc, ok := cc.(*Element); ok && + spaceMatch(f.space, cc.Space) && + f.tag == cc.Tag && + f.text == cc.Text() { + p.scratch = append(p.scratch, c) + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} diff --git a/vendor/github.com/laurent22/ical-go/ical/calendar.go b/vendor/github.com/laurent22/ical-go/ical/calendar.go new file mode 100644 index 0000000..b857f58 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/calendar.go @@ -0,0 +1,17 @@ +package ical + +type Calendar struct { + Items []CalendarEvent +} + +func (this *Calendar) Serialize() string { + serializer := calSerializer{ + calendar: this, + buffer: new(strBuffer), + } + return serializer.serialize() +} + +func (this *Calendar) ToICS() string { + return this.Serialize() +} diff --git a/vendor/github.com/laurent22/ical-go/ical/calendar_event.go b/vendor/github.com/laurent22/ical-go/ical/calendar_event.go new file mode 100644 index 0000000..52c9580 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/calendar_event.go @@ -0,0 +1,50 @@ +package ical + +import ( + "time" +) + +type CalendarEvent struct { + Id string + Summary string + Description string + Location string + CreatedAtUTC *time.Time + ModifiedAtUTC *time.Time + StartAt *time.Time + EndAt *time.Time +} + +func (this *CalendarEvent) StartAtUTC() *time.Time { + return inUTC(this.StartAt) +} + +func (this *CalendarEvent) EndAtUTC() *time.Time { + return inUTC(this.EndAt) +} + +func (this *CalendarEvent) Serialize() string { + buffer := new(strBuffer) + return this.serializeWithBuffer(buffer) +} + +func (this *CalendarEvent) ToICS() string { + return this.Serialize() +} + +func (this *CalendarEvent) serializeWithBuffer(buffer *strBuffer) string { + serializer := calEventSerializer{ + event: this, + buffer: buffer, + } + return serializer.serialize() +} + +func inUTC(t *time.Time) *time.Time { + if t == nil { + return nil + } + + tUTC := t.UTC() + return &tUTC +} diff --git a/vendor/github.com/laurent22/ical-go/ical/lib.go b/vendor/github.com/laurent22/ical-go/ical/lib.go new file mode 100644 index 0000000..4230c09 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/lib.go @@ -0,0 +1,18 @@ +package ical + +import ( + "fmt" + "bytes" +) + +type strBuffer struct { + buffer bytes.Buffer +} + +func (b *strBuffer) Write(format string, elem ...interface{}) { + b.buffer.WriteString(fmt.Sprintf(format, elem...)) +} + +func (b *strBuffer) String() string { + return b.buffer.String() +} diff --git a/vendor/github.com/laurent22/ical-go/ical/node.go b/vendor/github.com/laurent22/ical-go/ical/node.go new file mode 100644 index 0000000..252fe52 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/node.go @@ -0,0 +1,168 @@ +package ical + +import ( + "time" + "regexp" + "strconv" +) + +type Node struct { + Name string + Value string + Type int // 1 = Object, 0 = Name/Value + Parameters map[string]string + Children []*Node +} + +func (this *Node) ChildrenByName(name string) []*Node { + var output []*Node + for _, child := range this.Children { + if child.Name == name { + output = append(output, child) + } + } + return output +} + +func (this *Node) ChildByName(name string) *Node { + for _, child := range this.Children { + if child.Name == name { + return child + } + } + return nil +} + +func (this *Node) PropString(name string, defaultValue string) string { + for _, child := range this.Children { + if child.Name == name { + return child.Value + } + } + return defaultValue +} + +func (this *Node) PropDate(name string, defaultValue time.Time) time.Time { + node := this.ChildByName(name) + if node == nil { return defaultValue } + tzid := node.Parameter("TZID", "") + var output time.Time + var err error + if tzid != "" { + loc, err := time.LoadLocation(tzid) + if err != nil { panic(err) } + output, err = time.ParseInLocation("20060102T150405", node.Value, loc) + } else { + output, err = time.Parse("20060102T150405Z", node.Value) + } + + if err != nil { panic(err) } + return output +} + +func (this *Node) PropDuration(name string) time.Duration { + durStr := this.PropString(name, "") + + if durStr == "" { + return time.Duration(0) + } + + durRgx := regexp.MustCompile("PT(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)S)?") + matches := durRgx.FindStringSubmatch(durStr) + + if len(matches) != 4 { + return time.Duration(0) + } + + strToDuration := func(value string) time.Duration { + d := 0 + if value != "" { + d, _ = strconv.Atoi(value) + } + return time.Duration(d) + } + + hours := strToDuration(matches[1]) + min := strToDuration(matches[2]) + sec := strToDuration(matches[3]) + + return hours * time.Hour + min * time.Minute + sec * time.Second +} + +func (this *Node) PropInt(name string, defaultValue int) int { + n := this.PropString(name, "") + if n == "" { return defaultValue } + output, err := strconv.Atoi(n) + if err != nil { panic(err) } + return output +} + +func (this *Node) DigProperty(propPath... string) (string, bool) { + return this.dig("prop", propPath...) +} + +func (this *Node) Parameter(name string, defaultValue string) string { + if len(this.Parameters) <= 0 { return defaultValue } + v, ok := this.Parameters[name] + if !ok { return defaultValue } + return v +} + +func (this *Node) DigParameter(paramPath... string) (string, bool) { + return this.dig("param", paramPath...) +} + +// Digs a value based on a given value path. +// valueType: can be "param" or "prop". +// valuePath: the path to access the value. +// Returns ("", false) when not found or (value, true) when found. +// +// Example: +// dig("param", "VCALENDAR", "VEVENT", "DTEND", "TYPE") -> It will search for "VCALENDAR" node, +// then a "VEVENT" node, then a "DTEND" note, then finally the "TYPE" param. +func (this *Node) dig(valueType string, valuePath... string) (string, bool) { + current := this + lastIndex := len(valuePath) - 1 + for _, v := range valuePath[:lastIndex] { + current = current.ChildByName(v) + + if current == nil { + return "", false + } + } + + target := valuePath[lastIndex] + + value := "" + if valueType == "param" { + value = current.Parameter(target, "") + } else if valueType == "prop" { + value = current.PropString(target, "") + } + + if value == "" { + return "", false + } + + return value, true +} + +func (this *Node) String() string { + s := "" + if this.Type == 1 { + s += "===== " + this.Name + s += "\n" + } else { + s += this.Name + s += ":" + this.Value + s += "\n" + } + for _, child := range this.Children { + s += child.String() + } + if this.Type == 1 { + s += "===== /" + this.Name + s += "\n" + } + return s +} diff --git a/vendor/github.com/laurent22/ical-go/ical/parsers.go b/vendor/github.com/laurent22/ical-go/ical/parsers.go new file mode 100644 index 0000000..5ad3c7b --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/parsers.go @@ -0,0 +1,106 @@ +package ical + +import ( + "log" + "errors" + "regexp" + "strings" +) + +func ParseCalendar(data string) (*Node, error) { + r := regexp.MustCompile("([\r|\t| ]*\n[\r|\t| ]*)+") + lines := r.Split(strings.TrimSpace(data), -1) + node, _, err, _ := parseCalendarNode(lines, 0) + + return node, err +} + +func parseCalendarNode(lines []string, lineIndex int) (*Node, bool, error, int) { + line := strings.TrimSpace(lines[lineIndex]) + _ = log.Println + colonIndex := strings.Index(line, ":") + if colonIndex <= 0 { + return nil, false, errors.New("Invalid value/pair: " + line), lineIndex + 1 + } + name := line[0:colonIndex] + splitted := strings.Split(name, ";") + var parameters map[string]string + if len(splitted) >= 2 { + name = splitted[0] + parameters = make(map[string]string) + for i := 1; i < len(splitted); i++ { + p := strings.Split(splitted[i], "=") + if len(p) != 2 { panic("Invalid parameter format: " + name) } + parameters[p[0]] = p[1] + } + } + value := line[colonIndex+1:len(line)] + + if name == "BEGIN" { + node := new(Node) + node.Name = value + node.Type = 1 + lineIndex = lineIndex + 1 + for { + child, finished, _, newLineIndex := parseCalendarNode(lines, lineIndex) + if finished { + return node, false, nil, newLineIndex + } else { + if child != nil { + node.Children = append(node.Children, child) + } + lineIndex = newLineIndex + } + } + } else if name == "END" { + return nil, true, nil, lineIndex + 1 + } else { + node := new(Node) + node.Name = name + if name == "DESCRIPTION" || name == "SUMMARY" { + text, newLineIndex := parseTextType(lines, lineIndex) + node.Value = text + node.Parameters = parameters + return node, false, nil, newLineIndex + } else { + node.Value = value + node.Parameters = parameters + return node, false, nil, lineIndex + 1 + } + } + + panic("Unreachable") + return nil, false, nil, lineIndex + 1 +} + +func parseTextType(lines []string, lineIndex int) (string, int) { + line := lines[lineIndex] + colonIndex := strings.Index(line, ":") + output := strings.TrimSpace(line[colonIndex+1:len(line)]) + lineIndex++ + for { + line := lines[lineIndex] + if line == "" || line[0] != ' ' { + return unescapeTextType(output), lineIndex + } + output += line[1:len(line)] + lineIndex++ + } + return unescapeTextType(output), lineIndex +} + +func escapeTextType(input string) string { + output := strings.Replace(input, "\\", "\\\\", -1) + output = strings.Replace(output, ";", "\\;", -1) + output = strings.Replace(output, ",", "\\,", -1) + output = strings.Replace(output, "\n", "\\n", -1) + return output +} + +func unescapeTextType(s string) string { + s = strings.Replace(s, "\\;", ";", -1) + s = strings.Replace(s, "\\,", ",", -1) + s = strings.Replace(s, "\\n", "\n", -1) + s = strings.Replace(s, "\\\\", "\\", -1) + return s +} diff --git a/vendor/github.com/laurent22/ical-go/ical/properties.go b/vendor/github.com/laurent22/ical-go/ical/properties.go new file mode 100644 index 0000000..44acf9a --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/properties.go @@ -0,0 +1,9 @@ +package ical + +const ( + VCALENDAR = "VCALENDAR" + VEVENT = "VEVENT" + DTSTART = "DTSTART" + DTEND = "DTEND" + DURATION = "DURATION" +) diff --git a/vendor/github.com/laurent22/ical-go/ical/serializers.go b/vendor/github.com/laurent22/ical-go/ical/serializers.go new file mode 100644 index 0000000..12c0070 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/serializers.go @@ -0,0 +1,121 @@ +package ical + +import ( + "time" + "strings" +) + +type calSerializer struct { + calendar *Calendar + buffer *strBuffer +} + +func (this *calSerializer) serialize() string { + this.serializeCalendar() + return strings.TrimSpace(this.buffer.String()) +} + +func (this *calSerializer) serializeCalendar() { + this.begin() + this.version() + this.items() + this.end() +} + +func (this *calSerializer) begin() { + this.buffer.Write("BEGIN:VCALENDAR\n") +} + +func (this *calSerializer) end() { + this.buffer.Write("END:VCALENDAR\n") +} + +func (this *calSerializer) version() { + this.buffer.Write("VERSION:2.0\n") +} + +func (this *calSerializer) items() { + for _, item := range this.calendar.Items { + item.serializeWithBuffer(this.buffer) + } +} + +type calEventSerializer struct { + event *CalendarEvent + buffer *strBuffer +} + +const ( + eventSerializerTimeFormat = "20060102T150405Z" +) + +func (this *calEventSerializer) serialize() string { + this.serializeEvent() + return strings.TrimSpace(this.buffer.String()) +} + +func (this *calEventSerializer) serializeEvent() { + this.begin() + this.uid() + this.created() + this.lastModified() + this.dtstart() + this.dtend() + this.summary() + this.description() + this.location() + this.end() +} + +func (this *calEventSerializer) begin() { + this.buffer.Write("BEGIN:VEVENT\n") +} + +func (this *calEventSerializer) end() { + this.buffer.Write("END:VEVENT\n") +} + +func (this *calEventSerializer) uid() { + this.serializeStringProp("UID", this.event.Id) +} + +func (this *calEventSerializer) summary() { + this.serializeStringProp("SUMMARY", this.event.Summary) +} + +func (this *calEventSerializer) description() { + this.serializeStringProp("DESCRIPTION", this.event.Description) +} + +func (this *calEventSerializer) location() { + this.serializeStringProp("LOCATION", this.event.Location) +} + +func (this *calEventSerializer) dtstart() { + this.serializeTimeProp("DTSTART", this.event.StartAtUTC()) +} + +func (this *calEventSerializer) dtend() { + this.serializeTimeProp("DTEND", this.event.EndAtUTC()) +} + +func (this *calEventSerializer) created() { + this.serializeTimeProp("CREATED", this.event.CreatedAtUTC) +} + +func (this *calEventSerializer) lastModified() { + this.serializeTimeProp("LAST-MODIFIED", this.event.ModifiedAtUTC) +} + +func (this *calEventSerializer) serializeStringProp(name, value string) { + if value != "" { + escapedValue := escapeTextType(value) + this.buffer.Write("%s:%s\n", name, escapedValue) + } +} + +func (this *calEventSerializer) serializeTimeProp(name string, value *time.Time) { + if value != nil { + this.buffer.Write("%s:%s\n", name, value.Format(eventSerializerTimeFormat)) + } +} diff --git a/vendor/github.com/laurent22/ical-go/ical/todo.go b/vendor/github.com/laurent22/ical-go/ical/todo.go new file mode 100644 index 0000000..4818900 --- /dev/null +++ b/vendor/github.com/laurent22/ical-go/ical/todo.go @@ -0,0 +1,94 @@ +package ical + +// import ( +// "time" +// "strconv" +// "strings" +// ) +// +// func TodoFromNode(node *Node) Todo { +// if node.Name != "VTODO" { panic("Node is not a VTODO") } +// +// var todo Todo +// todo.SetId(node.PropString("UID", "")) +// todo.SetSummary(node.PropString("SUMMARY", "")) +// todo.SetDescription(node.PropString("DESCRIPTION", "")) +// todo.SetDueDate(node.PropDate("DUE", time.Time{})) +// //todo.SetAlarmDate(this.TimestampBytesToTime(reminderDate)) +// todo.SetCreatedDate(node.PropDate("CREATED", time.Time{})) +// todo.SetModifiedDate(node.PropDate("DTSTAMP", time.Time{})) +// todo.SetPriority(node.PropInt("PRIORITY", 0)) +// todo.SetPercentComplete(node.PropInt("PERCENT-COMPLETE", 0)) +// return todo +// } +// +// type Todo struct { +// CalendarItem +// dueDate time.Time +// } +// +// func (this *Todo) SetDueDate(v time.Time) { this.dueDate = v } +// func (this *Todo) DueDate() time.Time { return this.dueDate } +// +// func (this *Todo) ICalString(target string) string { +// s := "BEGIN:VTODO\n" +// +// if target == "macTodo" { +// status := "NEEDS-ACTION" +// if this.PercentComplete() == 100 { +// status = "COMPLETED" +// } +// s += "STATUS:" + status + "\n" +// } +// +// s += encodeDateProperty("CREATED", this.CreatedDate()) + "\n" +// s += "UID:" + this.Id() + "\n" +// s += "SUMMARY:" + escapeTextType(this.Summary()) + "\n" +// if this.PercentComplete() == 100 && !this.CompletedDate().IsZero() { +// s += encodeDateProperty("COMPLETED", this.CompletedDate()) + "\n" +// } +// s += encodeDateProperty("DTSTAMP", this.ModifiedDate()) + "\n" +// if this.Priority() != 0 { +// s += "PRIORITY:" + strconv.Itoa(this.Priority()) + "\n" +// } +// if this.PercentComplete() != 0 { +// s += "PERCENT-COMPLETE:" + strconv.Itoa(this.PercentComplete()) + "\n" +// } +// if target == "macTodo" { +// s += "SEQUENCE:" + strconv.Itoa(this.Sequence()) + "\n" +// } +// if this.Description() != "" { +// s += "DESCRIPTION:" + encodeTextType(this.Description()) + "\n" +// } +// +// s += "END:VTODO\n" +// +// return s +// } +// +// func encodeDateProperty(name string, t time.Time) string { +// var output string +// zone, _ := t.Zone() +// if zone != "UTC" && zone != "" { +// output = ";TZID=" + zone + ":" + t.Format("20060102T150405") +// } else { +// output = ":" + t.Format("20060102T150405") + "Z" +// } +// return name + output +// } +// +// +// func encodeTextType(s string) string { +// output := "" +// s = escapeTextType(s) +// lineLength := 0 +// for _, c := range s { +// if lineLength + len(string(c)) > 75 { +// output += "\n " +// lineLength = 1 +// } +// output += string(c) +// lineLength += len(string(c)) +// } +// return output +// } diff --git a/vendor/github.com/samedi/caldav-go/.gitignore b/vendor/github.com/samedi/caldav-go/.gitignore new file mode 100644 index 0000000..7812586 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/.gitignore @@ -0,0 +1,2 @@ +test-data/ +vendor diff --git a/vendor/github.com/samedi/caldav-go/CHANGELOG.md b/vendor/github.com/samedi/caldav-go/CHANGELOG.md new file mode 100644 index 0000000..d336f6b --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/CHANGELOG.md @@ -0,0 +1,69 @@ +# CHANGELOG + +v3.0.0 +----------- +2017-08-01 Daniel Ferraz + +Main change: + +Add two ways to get resource from the storage: shallow or not. + +`data.GetShallowResource`: means that, if it's collection resource, it will not include its child VEVENTs in the ICS data. +This is used throughout the palces where the children dont matter. + +`data.GetResource`: means that the child VEVENTs will be included in the returned ICS content data for collection resources. +This is used then sending a GET request to fetch a specific resource and expecting its full ICS data in response. + +Other changes: + +* Removed the need to pass the useless `writer http.ResponseWriter` parameter when calling the `caldav.HandleRequest` function. +* Added a `caldav.HandleRequestWithStorage` function that makes it easy to pass a custom storage to be used and handle the request with a single function call. + + +v2.0.0 +----------- +2017-05-10 Daniel Ferraz + +All commits squashed and LICENSE updated to release as OSS in github. +Feature-wise it remains the same. + + +v1.0.1 +----------- +2017-01-25 Daniel Ferraz + +Escape the contents in `` and `` in the `multistatus` XML responses. Fixing possible bugs +related to having special characters (e.g. &) in the XML multistatus responses that would possible break the encoding. + +v1.0.0 +----------- +2017-01-18 Daniel Ferraz + +Main feature: + +* Handles the `Prefer` header on PROPFIND and REPORT requests (defined in this [draft/proposal](https://tools.ietf.org/html/draft-murchison-webdav-prefer-05)). Useful to shrink down possible big and verbose responses when the client demands. Ex: current iOS calendar client uses this feature on its PROPFIND requests. + +Other changes: + +* Added the `handlers.Response` to allow clients of the lib to interact with the generated response before being written/sent back to the client. +* Added `GetResourcesByFilters` to the storage interface to allow filtering of resources in the storage level. Useful to provide an already filtered and smaller resource collection to a the REPORT handler when dealing with a filtered REPORT request. +* Added `GetResourcesByList` to the storage interface to fetch a set a of resources based on a set of paths. Useful to provide, in one call, the correct resource collection to the REPORT handler when dealing with a REPORT request for specific `hrefs`. +* Remove useless `IsResourcePresent` from the storage interface. + + +v0.1.0 +----------- +2016-09-23 Daniel Ferraz + +This version implements: + +* Allow: "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, REPORT" +* DAV: "1, 3, calendar-access" +* Also only handles the following components: `VCALENDAR`, `VEVENT` + +Currently unsupported: + +* Components `VTODO`, `VJOURNAL`, `VFREEBUSY` +* `VEVENT` recurrences +* Resource locking +* User authentication diff --git a/vendor/github.com/samedi/caldav-go/LICENSE b/vendor/github.com/samedi/caldav-go/LICENSE new file mode 100644 index 0000000..3213aa0 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/LICENSE @@ -0,0 +1,20 @@ +Copyright 2017 samedi GmbH + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/samedi/caldav-go/README.md b/vendor/github.com/samedi/caldav-go/README.md new file mode 100644 index 0000000..973aad0 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/README.md @@ -0,0 +1,140 @@ +# go CalDAV + +This is a Go lib that aims to implement the CalDAV specification ([RFC4791]). It allows the quick implementation of a CalDAV server in Go. Basically, it provides the request handlers that will handle the several CalDAV HTTP requests, fetch the appropriate resources, build and return the responses. + +### How to install + +``` +go get github.com/samedi/caldav-go +``` + +### Dependencies + +For dependency management, `glide` is used. + +```bash +# install glide (once!) +curl https://glide.sh/get | sh + +# install dependencies +glide install +``` + +### How to use it + +The easiest way to quickly implement a CalDAV server is by just using the lib's request handler. Example: + +```go +package mycaldav + +import ( + "net/http" + "github.com/samedi/caldav-go" +) + +func runServer() { + http.HandleFunc(PATH, caldav.RequestHandler) + http.ListenAndServe(PORT, nil) +} +``` + +With that, all the HTTP requests (GET, PUT, REPORT, PROPFIND, etc) will be handled and responded by the `caldav` handler. In case of any HTTP methods not supported by the lib, a `501 Not Implemented` response will be returned. + +In case you want more flexibility to handle the requests, e.g., if you wanted to access the generated response before being sent back to the caller, you could do like: + +```go +package mycaldav + +import ( + "net/http" + "github.com/samedi/caldav-go" +) + +func runServer() { + http.HandleFunc(PATH, myHandler) + http.ListenAndServe(PORT, nil) +} + +func myHandler(writer http.ResponseWriter, request *http.Request) { + response := caldav.HandleRequest(writer, request) + // ... do something with the `response` ... + // the response is written with the current `http.ResponseWriter` and ready to be sent back + response.Write(writer) +} +``` + +### Storage & Resources + +The storage is where the caldav resources are stored. To interact with that, the caldav lib needs only a type that conforms with the `data.Storage` interface to operate on top of the storage. Basically, this interface defines all the CRUD functions to work on top of the resources. With that, resources can be stored anywhere: in the filesystem, in the cloud, database, etc. As long as the used storage implements all the required storage interface functions, the caldav lib will work fine. + +For example, we could use the following dummy storage implementation: + +```go +type DummyStorage struct{ +} + +func (d *DummyStorage) GetResources(rpath string, withChildren bool) ([]Resource, error) { + return []Resource{}, nil +} + +func (d *DummyStorage) GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error) { + return []Resource{}, nil +} + +func (d *DummyStorage) GetResourcesByList(rpaths []string) ([]Resource, error) { + return []Resource{}, nil +} + +func (d *DummyStorage) GetResource(rpath string) (*Resource, bool, error) { + return nil, false, nil +} + +func (d *DummyStorage) GetShallowResource(rpath string) (*Resource, bool, error) { + return nil, false, nil +} + +func (d *DummyStorage) CreateResource(rpath, content string) (*Resource, error) { + return nil, nil +} + +func (d *DummyStorage) UpdateResource(rpath, content string) (*Resource, error) { + return nil, nil +} + +func (d *DummyStorage) DeleteResource(rpath string) error { + return nil +} +``` + +Then we just need to tell the caldav lib to use our dummy storage: + +```go +dummyStg := new(DummyStorage) +caldav.SetupStorage(dummyStg) +``` + +All the CRUD operations on resources will then be forwarded to our dummy storage. + +The default storage used (if none is explicitly set) is the `data.FileStorage` which deals with resources as files in the File System. + +The resources can be of two types: collection and non-collection. A collection resource is basically a resource that has children resources, but does not have any data content. A non-collection resource is a resource that does not have children, but has data. In the case of a file storage, collections correspond to directories and non-collection to plain files. The data of a caldav resource is all the info that shows up in the calendar client, in the [iCalendar](https://en.wikipedia.org/wiki/ICalendar) format. + +### Features + +Please check the **CHANGELOG** to see specific features that are currently implemented. + +### Contributing and testing + +Everyone is welcome to contribute. Please raise an issue or pull request accordingly. + +To run the tests: + +``` +./test.sh +``` + +### License + +MIT License. + +[RFC4791]: https://tools.ietf.org/html/rfc4791 diff --git a/vendor/github.com/samedi/caldav-go/config.go b/vendor/github.com/samedi/caldav-go/config.go new file mode 100644 index 0000000..8a2d7bc --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/config.go @@ -0,0 +1,14 @@ +package caldav + +import ( + "github.com/samedi/caldav-go/data" + "github.com/samedi/caldav-go/global" +) + +func SetupStorage(stg data.Storage) { + global.Storage = stg +} + +func SetupUser(username string) { + global.User = &data.CalUser{username} +} diff --git a/vendor/github.com/samedi/caldav-go/data/filters.go b/vendor/github.com/samedi/caldav-go/data/filters.go new file mode 100644 index 0000000..cf0ec62 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/data/filters.go @@ -0,0 +1,362 @@ +package data + +import ( + "log" + "time" + "strings" + "errors" + "github.com/beevik/etree" + + "github.com/samedi/caldav-go/lib" +) + +// ================ FILTERS ================== +// Filters are a set of rules used to retrieve a range of resources. It is used primarily +// on REPORT requests and is described in details here (RFC4791#7.8). + +const ( + TAG_FILTER = "filter" + TAG_COMP_FILTER = "comp-filter" + TAG_PROP_FILTER = "prop-filter" + TAG_PARAM_FILTER = "param-filter" + TAG_TIME_RANGE = "time-range" + TAG_TEXT_MATCH = "text-match" + TAG_IS_NOT_DEFINED = "is-not-defined" + + // from the RFC, the time range `start` and `end` attributes MUST be in UTC and in this specific format + FILTER_TIME_FORMAT = "20060102T150405Z" +) + +type ResourceFilter struct { + name string + text string + attrs map[string]string + children []ResourceFilter // collection of child filters. + etreeElem *etree.Element // holds the parsed XML node/tag as an `etree` element. +} + +// This function creates a new filter object from a piece of XML string. +func ParseResourceFilters(xml string) (*ResourceFilter, error) { + doc := etree.NewDocument() + if err := doc.ReadFromString(xml); err != nil { + log.Printf("ERROR: Could not parse filter from XML string. XML:\n%s", xml) + return new(ResourceFilter), err + } + + // Right now we're searching for a tag to initialize the filter struct from it. + // It SHOULD be a valid XML CALDAV:filter tag (RFC4791#9.7). We're not checking namespaces yet. + // TODO: check for XML namespaces and restrict it to accept only CALDAV:filter tag. + elem := doc.FindElement("//" + TAG_FILTER) + if elem == nil { + log.Printf("WARNING: The filter XML should contain a <%s> element. XML:\n%s", TAG_FILTER, xml) + return new(ResourceFilter), errors.New("invalid XML filter") + } + + filter := newFilterFromEtreeElem(elem) + return &filter, nil +} + +func newFilterFromEtreeElem(elem *etree.Element) ResourceFilter { + // init filter from etree element + filter := ResourceFilter{ + name: elem.Tag, + text: strings.TrimSpace(elem.Text()), + etreeElem: elem, + attrs: make(map[string]string), + } + + // set attributes + for _, attr := range elem.Attr { + filter.attrs[attr.Key] = attr.Value + } + + return filter +} + +func (f *ResourceFilter) Attr(attrName string) string { + return f.attrs[attrName] +} + +func (f *ResourceFilter) TimeAttr(attrName string) *time.Time { + + + t, err := time.Parse(FILTER_TIME_FORMAT, f.attrs[attrName]) + if err != nil { + return nil + } + + return &t +} + +// GetTimeRangeFilter checks if the current filter has a child "time-range" filter and +// returns it (wrapped in a `ResourceFilter` type). It returns nil if the current filter does +// not contain any "time-range" filter. +func (f *ResourceFilter) GetTimeRangeFilter() *ResourceFilter { + return f.findChild(TAG_TIME_RANGE, true) +} + +func (f *ResourceFilter) Match(target ResourceInterface) bool { + if f.name == TAG_FILTER { + return f.rootFilterMatch(target) + } + + return false +} + +func (f *ResourceFilter) rootFilterMatch(target ResourceInterface) bool { + if f.isEmpty() { + return false + } + + return f.rootChildrenMatch(target) +} + +// checks if all the root's child filters match the target resource +func (f *ResourceFilter) rootChildrenMatch(target ResourceInterface) bool { + scope := []string{} + + for _, child := range f.getChildren() { + // root filters only accept comp filters as children + if child.name != TAG_COMP_FILTER || !child.compMatch(target, scope) { + return false + } + } + + return true +} + +// See RFC4791-9.7.1. +func (f *ResourceFilter) compMatch(target ResourceInterface, scope []string) bool { + targetComp := target.ComponentName() + compName := f.attrs["name"] + + if f.isEmpty() { + // Point #1 of RFC4791#9.7.1 + return compName == targetComp + } else if f.contains(TAG_IS_NOT_DEFINED) { + // Point #2 of RFC4791#9.7.1 + return compName != targetComp + } else { + // check each child of the current filter if they all match. + childrenScope := append(scope, compName) + return f.compChildrenMatch(target, childrenScope) + } +} + +// checks if all the comp's child filters match the target resource +func (f *ResourceFilter) compChildrenMatch(target ResourceInterface, scope []string) bool { + for _, child := range f.getChildren() { + var match bool + + switch child.name { + case TAG_TIME_RANGE: + // Point #3 of RFC4791#9.7.1 + match = child.timeRangeMatch(target) + case TAG_PROP_FILTER: + // Point #4 of RFC4791#9.7.1 + match = child.propMatch(target, scope) + case TAG_COMP_FILTER: + // Point #4 of RFC4791#9.7.1 + match = child.compMatch(target, scope) + } + + if !match { + return false + } + } + + return true +} + +// See RFC4791-9.9 +func (f *ResourceFilter) timeRangeMatch(target ResourceInterface) bool { + startAttr := f.attrs["start"] + endAttr := f.attrs["end"] + + // at least one of the two MUST be present + if startAttr == "" && endAttr == "" { + // if both of them are missing, return false + return false + } else if startAttr == "" { + // if missing only the `start`, set it open ended to the left + startAttr = "00010101T000000Z" + } else if endAttr == "" { + // if missing only the `end`, set it open ended to the right + endAttr = "99991231T235959Z" + } + + // The logic below is only applicable for VEVENT components. So + // we return false if the resource is not a VEVENT component. + if target.ComponentName() != lib.VEVENT { + return false + } + + rangeStart, err := time.Parse(FILTER_TIME_FORMAT, startAttr) + if err != nil { + log.Printf("ERROR: Could not parse start time in time-range filter.\nError: %s.\nStart attr: %s", err, startAttr) + return false + } + + rangeEnd, err := time.Parse(FILTER_TIME_FORMAT, endAttr) + if err != nil { + log.Printf("ERROR: Could not parse end time in time-range filter.\nError: %s.\nEnd attr: %s", err, endAttr) + return false + } + + // the following logic is inferred from the rules table for VEVENT components, + // described in RFC4791-9.9. + overlapRange := func(dtStart, dtEnd, rangeStart, rangeEnd time.Time) bool { + if dtStart.Equal(dtEnd) { + // Lines 3 and 4 of the table deal when the DTSTART and DTEND dates are equals. + // In this case we use the rule: (start <= DTSTART && end > DTSTART) + return (rangeStart.Before(dtStart) || rangeStart.Equal(dtStart)) && rangeEnd.After(dtStart) + } else { + // Lines 1, 2 and 6 of the table deal when the DTSTART and DTEND dates are different. + // In this case we use the rule: (start < DTEND && end > DTSTART) + return rangeStart.Before(dtEnd) && rangeEnd.After(dtStart) + } + } + + // first we check each of the target recurrences (if any). + for _, recurrence := range target.Recurrences() { + // if any of them overlap the filter range, we return true right away + if overlapRange(recurrence.StartTime, recurrence.EndTime, rangeStart, rangeEnd) { + return true + } + } + + // if none of the recurrences match, we just return if the actual + // resource's `start` and `end` times match the filter range + return overlapRange(target.StartTimeUTC(), target.EndTimeUTC(), rangeStart, rangeEnd) +} + +// See RFC4791-9.7.2. +func (f *ResourceFilter) propMatch(target ResourceInterface, scope []string) bool { + propName := f.attrs["name"] + propPath := append(scope, propName) + + if f.isEmpty() { + // Point #1 of RFC4791#9.7.2 + return target.HasProperty(propPath...) + } else if f.contains(TAG_IS_NOT_DEFINED) { + // Point #2 of RFC4791#9.7.2 + return !target.HasProperty(propPath...) + } else { + // check each child of the current filter if they all match. + return f.propChildrenMatch(target, propPath) + } +} + +// checks if all the prop's child filters match the target resource +func (f *ResourceFilter) propChildrenMatch(target ResourceInterface, propPath []string) bool { + for _, child := range f.getChildren() { + var match bool + + switch child.name { + case TAG_TIME_RANGE: + // Point #3 of RFC4791#9.7.2 + // TODO: this point is not very clear on how to match time range against properties. + // So we're returning `false` in the meantime. + match = false + case TAG_TEXT_MATCH: + // Point #4 of RFC4791#9.7.2 + propText := target.GetPropertyValue(propPath...) + match = child.textMatch(propText) + case TAG_PARAM_FILTER: + // Point #4 of RFC4791#9.7.2 + match = child.paramMatch(target, propPath) + } + + if !match { + return false + } + } + + return true +} + +// See RFC4791-9.7.3 +func (f *ResourceFilter) paramMatch(target ResourceInterface, parentPropPath []string) bool { + paramName := f.attrs["name"] + paramPath := append(parentPropPath, paramName) + + if f.isEmpty() { + // Point #1 of RFC4791#9.7.3 + return target.HasPropertyParam(paramPath...) + } else if f.contains(TAG_IS_NOT_DEFINED) { + // Point #2 of RFC4791#9.7.3 + return !target.HasPropertyParam(paramPath...) + } else { + child := f.getChildren()[0] + // param filters can also have (only-one) nested text-match filter + if child.name == TAG_TEXT_MATCH { + paramValue := target.GetPropertyParamValue(paramPath...) + return child.textMatch(paramValue) + } + } + + return false +} + +// See RFC4791-9.7.5 +func (f *ResourceFilter) textMatch(targetText string) bool { + // TODO: collations are not being considered/supported yet. + // Texts are lowered to be case-insensitive, almost as the "i;ascii-casemap" value. + + targetText = strings.ToLower(targetText) + expectedSubstr := strings.ToLower(f.text) + + match := strings.Contains(targetText, expectedSubstr) + + if f.attrs["negate-condition"] == "yes" { + return !match + } + + return match +} + +func (f *ResourceFilter) isEmpty() bool { + return len(f.getChildren()) == 0 && f.text == "" +} + +func (f *ResourceFilter) contains(filterName string) bool { + if f.findChild(filterName, false) != nil { + return true + } + + return false +} + +func (f *ResourceFilter) findChild(filterName string, dig bool) *ResourceFilter { + for _, child := range f.getChildren() { + if child.name == filterName { + return &child + } + + if !dig { + continue + } + + dugChild := child.findChild(filterName, true) + + if dugChild != nil { + return dugChild + } + } + + return nil +} + +// lazy evaluation of the child filters +func (f *ResourceFilter) getChildren() []ResourceFilter { + if f.children == nil { + f.children = []ResourceFilter{} + + for _, childElem := range f.etreeElem.ChildElements() { + childFilter := newFilterFromEtreeElem(childElem) + f.children = append(f.children, childFilter) + } + } + + return f.children +} diff --git a/vendor/github.com/samedi/caldav-go/data/resource.go b/vendor/github.com/samedi/caldav-go/data/resource.go new file mode 100644 index 0000000..541bb23 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/data/resource.go @@ -0,0 +1,277 @@ +package data + +import ( + "os" + "fmt" + "log" + "time" + "strings" + "strconv" + "io/ioutil" + + "github.com/laurent22/ical-go/ical" + + "github.com/samedi/caldav-go/lib" + "github.com/samedi/caldav-go/files" +) + +type ResourceInterface interface { + ComponentName() string + StartTimeUTC() time.Time + EndTimeUTC() time.Time + Recurrences() []ResourceRecurrence + HasProperty(propPath... string) bool + GetPropertyValue(propPath... string) string + HasPropertyParam(paramName... string) bool + GetPropertyParamValue(paramName... string) string +} + +type ResourceAdapter interface { + IsCollection() bool + CalculateEtag() string + GetContent() string + GetContentSize() int64 + GetModTime() time.Time +} + +type ResourceRecurrence struct { + StartTime time.Time + EndTime time.Time +} + +type Resource struct { + Name string + Path string + + pathSplit []string + adapter ResourceAdapter + + emptyTime time.Time +} + +func NewResource(resPath string, adp ResourceAdapter) Resource { + pClean := lib.ToSlashPath(resPath) + pSplit := strings.Split(strings.Trim(pClean, "/"), "/") + + return Resource { + Name: pSplit[len(pSplit) - 1], + Path: pClean, + pathSplit: pSplit, + adapter: adp, + } +} + +func (r *Resource) IsCollection() bool { + return r.adapter.IsCollection() +} + +func (r *Resource) IsPrincipal() bool { + return len(r.pathSplit) <= 1 +} + +func (r *Resource) ComponentName() string { + if r.IsCollection() { + return lib.VCALENDAR + } else { + return lib.VEVENT + } +} + +func (r *Resource) StartTimeUTC() time.Time { + vevent := r.icalVEVENT() + dtstart := vevent.PropDate(ical.DTSTART, r.emptyTime) + + if dtstart == r.emptyTime { + log.Printf("WARNING: The property DTSTART was not found in the resource's ical data.\nResource path: %s", r.Path) + return r.emptyTime + } + + return dtstart.UTC() +} + +func (r *Resource) EndTimeUTC() time.Time { + vevent := r.icalVEVENT() + dtend := vevent.PropDate(ical.DTEND, r.emptyTime) + + // when the DTEND property is not present, we just add the DURATION (if any) to the DTSTART + if dtend == r.emptyTime { + duration := vevent.PropDuration(ical.DURATION) + dtend = r.StartTimeUTC().Add(duration) + } + + return dtend.UTC() +} + +func (r *Resource) Recurrences() []ResourceRecurrence { + // TODO: Implement. This server does not support ical recurrences yet. We just return an empty array. + return []ResourceRecurrence{} +} + +func (r *Resource) HasProperty(propPath... string) bool { + return r.GetPropertyValue(propPath...) != "" +} + +func (r *Resource) GetPropertyValue(propPath... string) string { + if propPath[0] == ical.VCALENDAR { + propPath = propPath[1:] + } + + prop, _ := r.icalendar().DigProperty(propPath...) + return prop +} + +func (r *Resource) HasPropertyParam(paramPath... string) bool { + return r.GetPropertyParamValue(paramPath...) != "" +} + +func (r *Resource) GetPropertyParamValue(paramPath... string) string { + if paramPath[0] == ical.VCALENDAR { + paramPath = paramPath[1:] + } + + param, _ := r.icalendar().DigParameter(paramPath...) + return param +} + +func (r *Resource) GetEtag() (string, bool) { + if r.IsCollection() { + return "", false + } + + return r.adapter.CalculateEtag(), true +} + +func (r *Resource) GetContentType() (string, bool) { + if r.IsCollection() { + return "text/calendar", true + } else { + return "text/calendar; component=vcalendar", true + } +} + +func (r *Resource) GetDisplayName() (string, bool) { + return r.Name, true +} + +func (r *Resource) GetContentData() (string, bool) { + data := r.adapter.GetContent() + found := data != "" + + return data, found +} + +func (r *Resource) GetContentLength() (string, bool) { + // If its collection, it does not have any content, so mark it as not found + if r.IsCollection() { + return "", false + } + + contentSize := r.adapter.GetContentSize() + return strconv.FormatInt(contentSize, 10), true +} + +func (r *Resource) GetLastModified(format string) (string, bool) { + return r.adapter.GetModTime().Format(format), true +} + +func (r *Resource) GetOwner() (string, bool) { + var owner string + if len(r.pathSplit) > 1 { + owner = r.pathSplit[0] + } else { + owner = "" + } + + return owner, true +} + +func (r *Resource) GetOwnerPath() (string, bool) { + owner, _ := r.GetOwner() + + if owner != "" { + return fmt.Sprintf("/%s/", owner), true + } else { + return "", false + } +} + +// TODO: memoize +func (r *Resource) icalVEVENT() *ical.Node { + vevent := r.icalendar().ChildByName(ical.VEVENT) + + // if nil, log it and return an empty vevent + if vevent == nil { + log.Printf("WARNING: The resource's ical data is missing the VEVENT property.\nResource path: %s", r.Path) + + return &ical.Node{ + Name: ical.VEVENT, + } + } + + return vevent +} + +// TODO: memoize +func (r *Resource) icalendar() *ical.Node { + data, found := r.GetContentData() + + if !found { + log.Printf("WARNING: The resource's ical data does not have any data.\nResource path: %s", r.Path) + return &ical.Node{ + Name: ical.VCALENDAR, + } + } + + icalNode, err := ical.ParseCalendar(data) + if err != nil { + log.Printf("ERROR: Could not parse the resource's ical data.\nError: %s.\nResource path: %s", err, r.Path) + return &ical.Node{ + Name: ical.VCALENDAR, + } + } + + return icalNode +} + +type FileResourceAdapter struct { + finfo os.FileInfo + resourcePath string +} + +func (adp *FileResourceAdapter) IsCollection() bool { + return adp.finfo.IsDir() +} + +func (adp *FileResourceAdapter) GetContent() string { + if adp.IsCollection() { + return "" + } + + data, err := ioutil.ReadFile(files.AbsPath(adp.resourcePath)) + if err != nil { + log.Printf("ERROR: Could not read file content for the resource.\nError: %s.\nResource path: %s.", err, adp.resourcePath) + return "" + } + + return string(data) +} + +func (adp *FileResourceAdapter) GetContentSize() int64 { + return adp.finfo.Size() +} + +func (adp *FileResourceAdapter) CalculateEtag() string { + // returns ETag as the concatenated hex values of a file's + // modification time and size. This is not a reliable synchronization + // mechanism for directories, so for collections we return empty. + if adp.IsCollection() { + return "" + } + + fi := adp.finfo + return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()) +} + +func (adp *FileResourceAdapter) GetModTime() time.Time { + return adp.finfo.ModTime() +} diff --git a/vendor/github.com/samedi/caldav-go/data/storage.go b/vendor/github.com/samedi/caldav-go/data/storage.go new file mode 100644 index 0000000..c4e40d8 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/data/storage.go @@ -0,0 +1,217 @@ +package data + +import ( + "os" + "log" + "io/ioutil" + "github.com/samedi/caldav-go/errs" + "github.com/samedi/caldav-go/files" +) + +// The Storage is the responsible for the CRUD operations on the caldav resources. +type Storage interface { + // GetResources gets a list of resources based on a given `rpath`. The + // `rpath` is the path to the original resource that's being requested. The resultant list + // will/must contain that original resource in it, apart from any additional resources. It also receives + // `withChildren` flag to say if the result must also include all the original resource`s + // children (if original is a collection resource). If `true`, the result will have the requested resource + children. + // If `false`, it will have only the requested original resource (from the `rpath` path). + // It returns errors if anything went wrong or if it could not find any resource on `rpath` path. + GetResources(rpath string, withChildren bool) ([]Resource, error) + // GetResourcesByList fetches a list of resources by path from the storage. + // This method fetches all the `rpaths` and return an array of the reosurces found. + // No error 404 will be returned if one of the resources cannot be found. + // Errors are returned if any errors other than "not found" happens. + GetResourcesByList(rpaths []string) ([]Resource, error) + // GetResourcesByFilters returns the filtered children of a target collection resource. + // The target collection resource is the one pointed by the `rpath` parameter. All of its children + // will be checked against a set of `filters` and the matching ones are returned. The results + // contains only the filtered children and does NOT include the target resource. If the target resource + // is not a collection, an empty array is returned as the result. + GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error) + // GetResource gets the requested resource based on a given `rpath` path. It returns the resource (if found) or + // nil (if not found). Also returns a flag specifying if the resource was found or not. + GetResource(rpath string) (*Resource, bool, error) + // GetShallowResource has the same behaviour of `storage.GetResource`. The only difference is that, for collection resources, + // it does not return its children in the collection `storage.Resource` struct (hence the name shallow). The motive is + // for optimizations reasons, as this function is used on places where the collection's children are not important. + GetShallowResource(rpath string) (*Resource, bool, error) + // CreateResource creates a new resource on the `rpath` path with a given `content`. + CreateResource(rpath, content string) (*Resource, error) + // UpdateResource udpates a resource on the `rpath` path with a given `content`. + UpdateResource(rpath, content string) (*Resource, error) + // DeleteResource deletes a resource on the `rpath` path. + DeleteResource(rpath string) error +} + +// FileStorage is the storage that deals with resources as files in the file system. So, a collection resource +// is treated as a folder/directory and its children resources are the files it contains. On the other hand, non-collection +// resources are just plain files. +type FileStorage struct { +} + +func (fs *FileStorage) GetResources(rpath string, withChildren bool) ([]Resource, error) { + result := []Resource{} + + // tries to open the file by the given path + f, e := fs.openResourceFile(rpath, os.O_RDONLY) + if e != nil { + return nil, e + } + + // add it as a resource to the result list + finfo, _ := f.Stat() + resource := NewResource(rpath, &FileResourceAdapter{finfo, rpath}) + result = append(result, resource) + + // if the file is a dir, add its children to the result list + if withChildren && finfo.IsDir() { + dirFiles, _ := f.Readdir(0) + for _, finfo := range dirFiles { + childPath := files.JoinPaths(rpath, finfo.Name()) + resource = NewResource(childPath, &FileResourceAdapter{finfo, childPath}) + result = append(result, resource) + } + } + + return result, nil +} + +func (fs *FileStorage) GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error) { + result := []Resource{} + + childPaths := fs.getDirectoryChildPaths(rpath) + for _, path := range childPaths { + resource, _, err := fs.GetShallowResource(path) + + if err != nil { + // if we can't find this resource, something weird went wrong, but not that serious, so we log it and continue + log.Printf("WARNING: returned error when trying to get resource with path %s from collection with path %s. Error: %s", path, rpath, err) + continue + } + + // only add it if the resource matches the filters + if filters == nil || filters.Match(resource) { + result = append(result, *resource) + } + } + + return result, nil +} + +func (fs *FileStorage) GetResourcesByList(rpaths []string) ([]Resource, error) { + results := []Resource{} + + for _, rpath := range rpaths { + resource, found, err := fs.GetShallowResource(rpath) + + if err != nil && err != errs.ResourceNotFoundError { + return nil, err + } + + if found { + results = append(results, *resource) + } + } + + return results, nil +} + +func (fs *FileStorage) GetResource(rpath string) (*Resource, bool, error) { + // For simplicity we just return the shallow resource. + return fs.GetShallowResource(rpath) +} + +func (fs *FileStorage) GetShallowResource(rpath string) (*Resource, bool, error) { + resources, err := fs.GetResources(rpath, false) + + if err != nil { + return nil, false, err + } + + if resources == nil || len(resources) == 0 { + return nil, false, errs.ResourceNotFoundError + } + + res := resources[0] + return &res, true, nil +} + +func (fs *FileStorage) CreateResource(rpath, content string) (*Resource, error) { + rAbsPath := files.AbsPath(rpath) + + if fs.isResourcePresent(rAbsPath) { + return nil, errs.ResourceAlreadyExistsError + } + + // create parent directories (if needed) + if err := os.MkdirAll(files.DirPath(rAbsPath), os.ModePerm); err != nil { + return nil, err + } + + // create file/resource and write content + f, err := os.Create(rAbsPath) + if err != nil { + return nil, err + } + f.WriteString(content) + + finfo, _ := f.Stat() + res := NewResource(rpath, &FileResourceAdapter{finfo, rpath}) + return &res, nil +} + +func (fs *FileStorage) UpdateResource(rpath, content string) (*Resource, error) { + f, e := fs.openResourceFile(rpath, os.O_RDWR) + if e != nil { + return nil, e + } + + // update content + f.Truncate(0) + f.WriteString(content) + + finfo, _ := f.Stat() + res := NewResource(rpath, &FileResourceAdapter{finfo, rpath}) + return &res, nil +} + +func (fs *FileStorage) DeleteResource(rpath string) error { + err := os.Remove(files.AbsPath(rpath)) + + return err +} + +func (fs *FileStorage) isResourcePresent(rpath string) bool { + _, found, _ := fs.GetShallowResource(rpath) + + return found +} + +func (fs *FileStorage) openResourceFile(filepath string, mode int) (*os.File, error) { + f, e := os.OpenFile(files.AbsPath(filepath), mode, 0666) + if e != nil { + if os.IsNotExist(e) { + return nil, errs.ResourceNotFoundError + } + return nil, e + } + + return f, nil +} + +func (fs *FileStorage) getDirectoryChildPaths(dirpath string) []string { + content, err := ioutil.ReadDir(files.AbsPath(dirpath)) + if err != nil { + log.Printf("ERROR: Could not read resource as file directory.\nError: %s.\nResource path: %s.", err, dirpath) + return nil + } + + result := []string{} + for _, file := range content { + fpath := files.JoinPaths(dirpath, file.Name()) + result = append(result, fpath) + } + + return result +} diff --git a/vendor/github.com/samedi/caldav-go/data/user.go b/vendor/github.com/samedi/caldav-go/data/user.go new file mode 100644 index 0000000..5ceba9a --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/data/user.go @@ -0,0 +1,5 @@ +package data + +type CalUser struct { + Name string +} diff --git a/vendor/github.com/samedi/caldav-go/errs/errors.go b/vendor/github.com/samedi/caldav-go/errs/errors.go new file mode 100644 index 0000000..bc1fa96 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/errs/errors.go @@ -0,0 +1,12 @@ +package errs + +import ( + "errors" +) + +var ( + ResourceNotFoundError = errors.New("caldav: resource not found") + ResourceAlreadyExistsError = errors.New("caldav: resource already exists") + UnauthorizedError = errors.New("caldav: unauthorized. credentials needed.") + ForbiddenError = errors.New("caldav: forbidden operation.") +) diff --git a/vendor/github.com/samedi/caldav-go/files/paths.go b/vendor/github.com/samedi/caldav-go/files/paths.go new file mode 100644 index 0000000..4e41c53 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/files/paths.go @@ -0,0 +1,30 @@ +package files + +import ( + "strings" + "path/filepath" + "github.com/samedi/caldav-go/lib" +) + +const ( + Separator = string(filepath.Separator) +) + +func AbsPath(path string) string { + path = strings.Trim(path, "/") + absPath, _ := filepath.Abs(path) + + return absPath +} + +func DirPath(path string) string { + return filepath.Dir(path) +} + +func JoinPaths(paths ...string) string { + return filepath.Join(paths...) +} + +func ToSlashPath(path string) string { + return lib.ToSlashPath(path) +} diff --git a/vendor/github.com/samedi/caldav-go/glide.lock b/vendor/github.com/samedi/caldav-go/glide.lock new file mode 100644 index 0000000..7243eb5 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/glide.lock @@ -0,0 +1,10 @@ +hash: 2796726e69757f4af1a13f6ebd056ebc626d712051aa213875bb03f5bdc1ebfd +updated: 2017-01-18T11:43:23.127761353+01:00 +imports: +- name: github.com/beevik/etree + version: 4cd0dd976db869f817248477718071a28e978df0 +- name: github.com/laurent22/ical-go + version: 4811ac5553eae5fed7cd5d7a9024727f1311b2a2 + subpackages: + - ical +testImports: [] diff --git a/vendor/github.com/samedi/caldav-go/glide.yaml b/vendor/github.com/samedi/caldav-go/glide.yaml new file mode 100644 index 0000000..18b4ab1 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/glide.yaml @@ -0,0 +1,7 @@ +package: github.com/samedi/caldav-go +import: +- package: github.com/beevik/etree +- package: github.com/laurent22/ical-go + version: ~0.1.0 + subpackages: + - ical diff --git a/vendor/github.com/samedi/caldav-go/global/global.go b/vendor/github.com/samedi/caldav-go/global/global.go new file mode 100644 index 0000000..9e1d930 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/global/global.go @@ -0,0 +1,12 @@ +package global + +// This file defines accessible variables used to setup the caldav server. + +import ( + "github.com/samedi/caldav-go/data" +) + +// The global storage used in the CRUD operations of resources. Default storage is the `FileStorage`. +var Storage data.Storage = new(data.FileStorage) +// Current caldav user. It is used to keep the info of the current user that is interacting with the calendar. +var User *data.CalUser diff --git a/vendor/github.com/samedi/caldav-go/handler.go b/vendor/github.com/samedi/caldav-go/handler.go new file mode 100644 index 0000000..a1945b5 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handler.go @@ -0,0 +1,28 @@ +package caldav + +import ( + "net/http" + "github.com/samedi/caldav-go/data" + "github.com/samedi/caldav-go/handlers" +) + +// RequestHandler handles the given CALDAV request and writes the reponse righ away. This function is to be +// used by passing it directly as the handle func to the `http` lib. Example: http.HandleFunc("/", caldav.RequestHandler). +func RequestHandler(writer http.ResponseWriter, request *http.Request) { + response := HandleRequest(request) + response.Write(writer) +} + +// HandleRequest handles the given CALDAV request and returns the response. Useful when the caller +// wants to do something else with the response before writing it to the response stream. +func HandleRequest(request *http.Request) *handlers.Response { + handler := handlers.NewHandler(request) + return handler.Handle() +} + +// HandleRequestWithStorage handles the request the same way as `HandleRequest` does, but before, +// it sets the given storage that will be used throughout the request handling flow. +func HandleRequestWithStorage(request *http.Request, stg data.Storage) *handlers.Response { + SetupStorage(stg) + return HandleRequest(request) +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/builder.go b/vendor/github.com/samedi/caldav-go/handlers/builder.go new file mode 100644 index 0000000..0d99f97 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/builder.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "net/http" +) + +type handlerInterface interface { + Handle() *Response +} + +func NewHandler(request *http.Request) handlerInterface { + response := NewResponse() + + switch request.Method { + case "GET": return getHandler{request, response, false} + case "HEAD": return getHandler{request, response, true} + case "PUT": return putHandler{request, response} + case "DELETE": return deleteHandler{request, response} + case "PROPFIND": return propfindHandler{request, response} + case "OPTIONS": return optionsHandler{response} + case "REPORT": return reportHandler{request, response} + default: return notImplementedHandler{response} + } +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/delete.go b/vendor/github.com/samedi/caldav-go/handlers/delete.go new file mode 100644 index 0000000..d66b27a --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/delete.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "net/http" + "github.com/samedi/caldav-go/global" +) + +type deleteHandler struct { + request *http.Request + response *Response +} + +func (dh deleteHandler) Handle() *Response { + precond := requestPreconditions{dh.request} + + // get the event from the storage + resource, _, err := global.Storage.GetShallowResource(dh.request.URL.Path) + if err != nil { + return dh.response.SetError(err) + } + + // TODO: Handle delete on collections + if resource.IsCollection() { + return dh.response.Set(http.StatusMethodNotAllowed, "") + } + + // check ETag pre-condition + resourceEtag, _ := resource.GetEtag() + if !precond.IfMatch(resourceEtag) { + return dh.response.Set(http.StatusPreconditionFailed, "") + } + + // delete event after pre-condition passed + err = global.Storage.DeleteResource(resource.Path) + if err != nil { + return dh.response.SetError(err) + } + + return dh.response.Set(http.StatusNoContent, "") +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/get.go b/vendor/github.com/samedi/caldav-go/handlers/get.go new file mode 100644 index 0000000..2b42a47 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/get.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "net/http" + "github.com/samedi/caldav-go/global" +) + +type getHandler struct { + request *http.Request + response *Response + onlyHeaders bool +} + +func (gh getHandler) Handle() *Response { + resource, _, err := global.Storage.GetResource(gh.request.URL.Path) + if err != nil { + return gh.response.SetError(err) + } + + var response string + if gh.onlyHeaders { + response = "" + } else { + response, _ = resource.GetContentData() + } + + etag, _ := resource.GetEtag() + lastm, _ := resource.GetLastModified(http.TimeFormat) + ctype, _ := resource.GetContentType() + + gh.response.SetHeader("ETag", etag). + SetHeader("Last-Modified", lastm). + SetHeader("Content-Type", ctype). + Set(http.StatusOK, response) + + return gh.response +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/headers.go b/vendor/github.com/samedi/caldav-go/handlers/headers.go new file mode 100644 index 0000000..d7d692a --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/headers.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "net/http" +) + +const ( + HD_DEPTH = "Depth" + HD_DEPTH_DEEP = "1" + HD_PREFER = "Prefer" + HD_PREFER_MINIMAL = "return=minimal" + HD_PREFERENCE_APPLIED = "Preference-Applied" +) + +type headers struct { + http.Header +} + +func (this headers) IsDeep() bool { + depth := this.Get(HD_DEPTH) + return (depth == HD_DEPTH_DEEP) +} + +func (this headers) IsMinimal() bool { + prefer := this.Get(HD_PREFER) + return (prefer == HD_PREFER_MINIMAL) +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/multistatus.go b/vendor/github.com/samedi/caldav-go/handlers/multistatus.go new file mode 100644 index 0000000..038d139 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/multistatus.go @@ -0,0 +1,207 @@ +package handlers + +import ( + "fmt" + "net/http" + "encoding/xml" + "github.com/samedi/caldav-go/lib" + "github.com/samedi/caldav-go/global" + "github.com/samedi/caldav-go/data" + "github.com/samedi/caldav-go/ixml" +) + +// Wraps a multistatus response. It contains the set of `Responses` +// that will serve to build the final XML. Multistatus responses are +// used by the REPORT and PROPFIND methods. +type multistatusResp struct { + // The set of multistatus responses used to build each of the nodes. + Responses []msResponse + // Flag that XML should be minimal or not + // [defined in the draft https://tools.ietf.org/html/draft-murchison-webdav-prefer-05] + Minimal bool +} + +type msResponse struct { + Href string + Found bool + Propstats msPropstats +} + +type msPropstats map[int]msProps + +// Adds a msProp to the map with the key being the prop status. +func (stats msPropstats) Add(prop msProp) { + stats[prop.Status] = append(stats[prop.Status], prop) +} + +func (stats msPropstats) Clone() msPropstats { + clone := make(msPropstats) + + for k, v := range stats { + clone[k] = v + } + + return clone +} + +type msProps []msProp + +type msProp struct { + Tag xml.Name + Content string + Contents []string + Status int +} + +// Function that processes all the required props for a given resource. +// ## Params +// resource: the target calendar resource. +// reqprops: set of required props that must be processed for the resource. +// ## Returns +// The set of props (msProp) processed. Each prop is mapped to a HTTP status code. +// So if a prop is found and processed ok, it'll be mapped to 200. If it's not found, +// it'll be mapped to 404, and so on. +func (ms *multistatusResp) Propstats(resource *data.Resource, reqprops []xml.Name) msPropstats { + if resource == nil { + return nil + } + + result := make(msPropstats) + + for _, ptag := range reqprops { + pvalue := msProp{ + Tag: ptag, + Status: http.StatusOK, + } + + pfound := false + switch ptag { + case ixml.CALENDAR_DATA_TG: + pvalue.Content, pfound = resource.GetContentData() + if pfound { + pvalue.Content = ixml.EscapeText(pvalue.Content) + } + case ixml.GET_ETAG_TG: + pvalue.Content, pfound = resource.GetEtag() + case ixml.GET_CONTENT_TYPE_TG: + pvalue.Content, pfound = resource.GetContentType() + case ixml.GET_CONTENT_LENGTH_TG: + pvalue.Content, pfound = resource.GetContentLength() + case ixml.DISPLAY_NAME_TG: + pvalue.Content, pfound = resource.GetDisplayName() + if pfound { + pvalue.Content = ixml.EscapeText(pvalue.Content) + } + case ixml.GET_LAST_MODIFIED_TG: + pvalue.Content, pfound = resource.GetLastModified(http.TimeFormat) + case ixml.OWNER_TG: + pvalue.Content, pfound = resource.GetOwnerPath() + case ixml.GET_CTAG_TG: + pvalue.Content, pfound = resource.GetEtag() + case ixml.PRINCIPAL_URL_TG, + ixml.PRINCIPAL_COLLECTION_SET_TG, + ixml.CALENDAR_USER_ADDRESS_SET_TG, + ixml.CALENDAR_HOME_SET_TG: + pvalue.Content, pfound = ixml.HrefTag(resource.Path), true + case ixml.RESOURCE_TYPE_TG: + if resource.IsCollection() { + pvalue.Content, pfound = ixml.Tag(ixml.COLLECTION_TG, "") + ixml.Tag(ixml.CALENDAR_TG, ""), true + + if resource.IsPrincipal() { + pvalue.Content += ixml.Tag(ixml.PRINCIPAL_TG, "") + } + } else { + // resourcetype must be returned empty for non-collection elements + pvalue.Content, pfound = "", true + } + case ixml.CURRENT_USER_PRINCIPAL_TG: + if global.User != nil { + path := fmt.Sprintf("/%s/", global.User.Name) + pvalue.Content, pfound = ixml.HrefTag(path), true + } + case ixml.SUPPORTED_CALENDAR_COMPONENT_SET_TG: + if resource.IsCollection() { + for _, component := range supportedComponents { + // TODO: use ixml somehow to build the below tag + compTag := fmt.Sprintf(``, component) + pvalue.Contents = append(pvalue.Contents, compTag) + } + pfound = true + } + } + + if !pfound { + pvalue.Status = http.StatusNotFound + } + + result.Add(pvalue) + } + + return result +} + +// Adds a new `msResponse` to the `Responses` array. +func (ms *multistatusResp) AddResponse(href string, found bool, propstats msPropstats) { + ms.Responses = append(ms.Responses, msResponse{ + Href: href, + Found: found, + Propstats: propstats, + }) +} + +func (ms *multistatusResp) ToXML() string { + // init multistatus + var bf lib.StringBuffer + bf.Write(``) + bf.Write(``, ixml.Namespaces()) + + // iterate over event hrefs and build multistatus XML on the fly + for _, response := range ms.Responses { + bf.Write("") + bf.Write(ixml.HrefTag(response.Href)) + + if response.Found { + propstats := response.Propstats.Clone() + + if ms.Minimal { + delete(propstats, http.StatusNotFound) + + if len(propstats) == 0 { + bf.Write("") + bf.Write("") + bf.Write(ixml.StatusTag(http.StatusOK)) + bf.Write("") + bf.Write("") + + continue + } + } + + for status, props := range propstats { + bf.Write("") + bf.Write("") + for _, prop := range props { + bf.Write(ms.propToXML(prop)) + } + bf.Write("") + bf.Write(ixml.StatusTag(status)) + bf.Write("") + } + } else { + // if does not find the resource set 404 + bf.Write(ixml.StatusTag(http.StatusNotFound)) + } + bf.Write("") + } + bf.Write("") + + return bf.String() +} + +func (ms *multistatusResp) propToXML(prop msProp) string { + for _, content := range prop.Contents { + prop.Content += content + } + xmlString := ixml.Tag(prop.Tag, prop.Content) + return xmlString +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/not_implemented.go b/vendor/github.com/samedi/caldav-go/handlers/not_implemented.go new file mode 100644 index 0000000..8fb4f46 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/not_implemented.go @@ -0,0 +1,13 @@ +package handlers + +import ( + "net/http" +) + +type notImplementedHandler struct { + response *Response +} + +func (h notImplementedHandler) Handle() *Response { + return h.response.Set(http.StatusNotImplemented, "") +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/options.go b/vendor/github.com/samedi/caldav-go/handlers/options.go new file mode 100644 index 0000000..7c4c749 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/options.go @@ -0,0 +1,23 @@ +package handlers + +import ( + "net/http" +) + +type optionsHandler struct { + response *Response +} + +// Returns the allowed methods and the DAV features implemented by the current server. +// For more information about the values and format read RFC4918 Sections 10.1 and 18. +func (oh optionsHandler) Handle() *Response { + // Set the DAV compliance header: + // 1: Server supports all the requirements specified in RFC2518 + // 3: Server supports all the revisions specified in RFC4918 + // calendar-access: Server supports all the extensions specified in RFC4791 + oh.response.SetHeader("DAV", "1, 3, calendar-access"). + SetHeader("Allow", "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, REPORT"). + Set(http.StatusOK, "") + + return oh.response +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/preconditions.go b/vendor/github.com/samedi/caldav-go/handlers/preconditions.go new file mode 100644 index 0000000..d104b0a --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/preconditions.go @@ -0,0 +1,23 @@ +package handlers + +import ( + "net/http" +) + +type requestPreconditions struct { + request *http.Request +} + +func (p *requestPreconditions) IfMatch(etag string) bool { + etagMatch := p.request.Header["If-Match"] + return len(etagMatch) == 0 || etagMatch[0] == "*" || etagMatch[0] == etag +} + +func (p *requestPreconditions) IfMatchPresent() bool { + return len(p.request.Header["If-Match"]) != 0 +} + +func (p *requestPreconditions) IfNoneMatch(value string) bool { + valueMatch := p.request.Header["If-None-Match"] + return len(valueMatch) == 1 && valueMatch[0] == value +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/propfind.go b/vendor/github.com/samedi/caldav-go/handlers/propfind.go new file mode 100644 index 0000000..35fc807 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/propfind.go @@ -0,0 +1,49 @@ +package handlers + +import ( + "net/http" + "encoding/xml" + "github.com/samedi/caldav-go/global" +) + +type propfindHandler struct { + request *http.Request + response *Response +} + +func (ph propfindHandler) Handle() *Response { + requestBody := readRequestBody(ph.request) + header := headers{ph.request.Header} + + // get the target resources based on the request URL + resources, err := global.Storage.GetResources(ph.request.URL.Path, header.IsDeep()) + if err != nil { + return ph.response.SetError(err) + } + + // read body string to xml struct + type XMLProp2 struct { + Tags []xml.Name `xml:",any"` + } + type XMLRoot2 struct { + XMLName xml.Name + Prop XMLProp2 `xml:"DAV: prop"` + } + var requestXML XMLRoot2 + xml.Unmarshal([]byte(requestBody), &requestXML) + + multistatus := &multistatusResp{ + Minimal: header.IsMinimal(), + } + // for each href, build the multistatus responses + for _, resource := range resources { + propstats := multistatus.Propstats(&resource, requestXML.Prop.Tags) + multistatus.AddResponse(resource.Path, true, propstats) + } + + if multistatus.Minimal { + ph.response.SetHeader(HD_PREFERENCE_APPLIED, HD_PREFER_MINIMAL) + } + + return ph.response.Set(207, multistatus.ToXML()) +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/put.go b/vendor/github.com/samedi/caldav-go/handlers/put.go new file mode 100644 index 0000000..68fbc1d --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/put.go @@ -0,0 +1,65 @@ +package handlers + +import ( + "net/http" + "github.com/samedi/caldav-go/errs" + "github.com/samedi/caldav-go/global" +) + +type putHandler struct { + request *http.Request + response *Response +} + +func (ph putHandler) Handle() *Response { + requestBody := readRequestBody(ph.request) + precond := requestPreconditions{ph.request} + success := false + + // check if resource exists + resourcePath := ph.request.URL.Path + resource, found, err := global.Storage.GetShallowResource(resourcePath) + if err != nil && err != errs.ResourceNotFoundError { + return ph.response.SetError(err) + } + + // PUT is allowed in 2 cases: + // + // 1. Item NOT FOUND and there is NO ETAG match header: CREATE a new item + if !found && !precond.IfMatchPresent() { + // create new event resource + resource, err = global.Storage.CreateResource(resourcePath, requestBody) + if err != nil { + return ph.response.SetError(err) + } + + success = true + } + + if found { + // TODO: Handle PUT on collections + if resource.IsCollection() { + return ph.response.Set(http.StatusPreconditionFailed, "") + } + + // 2. Item exists, the resource etag is verified and there's no IF-NONE-MATCH=* header: UPDATE the item + resourceEtag, _ := resource.GetEtag() + if found && precond.IfMatch(resourceEtag) && !precond.IfNoneMatch("*") { + // update resource + resource, err = global.Storage.UpdateResource(resourcePath, requestBody) + if err != nil { + return ph.response.SetError(err) + } + + success = true + } + } + + if !success { + return ph.response.Set(http.StatusPreconditionFailed, "") + } + + resourceEtag, _ := resource.GetEtag() + return ph.response.SetHeader("ETag", resourceEtag). + Set(http.StatusCreated, "") +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/report.go b/vendor/github.com/samedi/caldav-go/handlers/report.go new file mode 100644 index 0000000..39a6cca --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/report.go @@ -0,0 +1,168 @@ +package handlers + +import ( + "fmt" + "strings" + "net/http" + "encoding/xml" + + "github.com/samedi/caldav-go/data" + "github.com/samedi/caldav-go/ixml" + "github.com/samedi/caldav-go/global" +) + +type reportHandler struct{ + request *http.Request + response *Response +} + +// See more at RFC4791#section-7.1 +func (rh reportHandler) Handle() *Response { + requestBody := readRequestBody(rh.request) + header := headers{rh.request.Header} + + urlResource, found, err := global.Storage.GetShallowResource(rh.request.URL.Path) + if !found { + return rh.response.Set(http.StatusNotFound, "") + } else if err != nil { + return rh.response.SetError(err) + } + + // read body string to xml struct + var requestXML reportRootXML + xml.Unmarshal([]byte(requestBody), &requestXML) + + // The resources to be reported are fetched by the type of the request. If it is + // a `calendar-multiget`, the resources come based on a set of `hrefs` in the request body. + // If it is a `calendar-query`, the resources are calculated based on set of filters in the request. + var resourcesToReport []reportRes + switch requestXML.XMLName { + case ixml.CALENDAR_MULTIGET_TG: + resourcesToReport, err = rh.fetchResourcesByList(urlResource, requestXML.Hrefs) + case ixml.CALENDAR_QUERY_TG: + resourcesToReport, err = rh.fetchResourcesByFilters(urlResource, requestXML.Filters) + default: + return rh.response.Set(http.StatusPreconditionFailed, "") + } + + if err != nil { + return rh.response.SetError(err) + } + + multistatus := &multistatusResp{ + Minimal: header.IsMinimal(), + } + // for each href, build the multistatus responses + for _, r := range resourcesToReport { + propstats := multistatus.Propstats(r.resource, requestXML.Prop.Tags) + multistatus.AddResponse(r.href, r.found, propstats) + } + + if multistatus.Minimal { + rh.response.SetHeader(HD_PREFERENCE_APPLIED, HD_PREFER_MINIMAL) + } + + return rh.response.Set(207, multistatus.ToXML()) +} + +type reportPropXML struct { + Tags []xml.Name `xml:",any"` +} + +type reportRootXML struct { + XMLName xml.Name + Prop reportPropXML `xml:"DAV: prop"` + Hrefs []string `xml:"DAV: href"` + Filters reportFilterXML `xml:"urn:ietf:params:xml:ns:caldav filter"` +} + +type reportFilterXML struct { + XMLName xml.Name + InnerContent string `xml:",innerxml"` +} + +func (this reportFilterXML) toString() string { + return fmt.Sprintf("<%s>%s", this.XMLName.Local, this.InnerContent, this.XMLName.Local) +} + +// Wraps a resource that has to be reported, either fetched by filters or by a list. +// Basically it contains the original requested `href`, the actual `resource` (can be nil) +// and if the `resource` was `found` or not +type reportRes struct { + href string + resource *data.Resource + found bool +} + +// The resources are fetched based on the origin resource and a set of filters. +// If the origin resource is a collection, the filters are checked against each of the collection's resources +// to see if they match. The collection's resources that match the filters are returned. The ones that will be returned +// are the resources that were not found (does not exist) and the ones that matched the filters. The ones that did not +// match the filter will not appear in the response result. +// If the origin resource is not a collection, the function just returns it and ignore any filter processing. +// [See RFC4791#section-7.8] +func (rh reportHandler) fetchResourcesByFilters(origin *data.Resource, filtersXML reportFilterXML) ([]reportRes, error) { + // The list of resources that has to be reported back in the response. + reps := []reportRes{} + + if origin.IsCollection() { + filters, _ := data.ParseResourceFilters(filtersXML.toString()) + resources, err := global.Storage.GetResourcesByFilters(origin.Path, filters) + + if err != nil { + return reps, err + } + + for _, resource := range resources { + reps = append(reps, reportRes{resource.Path, &resource, true}) + } + } else { + // the origin resource is not a collection, so returns just that as the result + reps = append(reps, reportRes{origin.Path, origin, true}) + } + + return reps, nil +} + +// The hrefs can come from (1) the request URL or (2) from the request body itself. +// If the origin resource from the URL points to a collection (2), we will check the request body +// to get the requested `hrefs` (resource paths). Each requested href has to be related to the collection. +// The ones that are not, we simply ignore them. +// If the resource from the URL is NOT a collection (1) we process the the report only for this resource +// and ignore any othre requested hrefs that might be present in the request body. +// [See RFC4791#section-7.9] +func (rh reportHandler) fetchResourcesByList(origin *data.Resource, requestedPaths []string) ([]reportRes, error) { + reps := []reportRes{} + + if origin.IsCollection() { + resources, err := global.Storage.GetResourcesByList(requestedPaths) + + if err != nil { + return reps, err + } + + // we put all the resources found in a map path -> resource. + // this will be used later to query which requested resource was found + // or not and mount the response + resourcesMap := make(map[string]*data.Resource) + for _, resource := range resources { + r := resource + resourcesMap[resource.Path] = &r + } + + for _, requestedPath := range requestedPaths { + // if the requested path does not belong to the origin collection, skip + // ('belonging' means that the path's prefix is the same as the collection path) + if !strings.HasPrefix(requestedPath, origin.Path) { + continue + } + + resource, found := resourcesMap[requestedPath] + reps = append(reps, reportRes{requestedPath, resource, found}) + } + } else { + reps = append(reps, reportRes{origin.Path, origin, true}) + } + + return reps, nil +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/response.go b/vendor/github.com/samedi/caldav-go/handlers/response.go new file mode 100644 index 0000000..f478def --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/response.go @@ -0,0 +1,65 @@ +package handlers + +import ( + "io" + "net/http" + "github.com/samedi/caldav-go/errs" +) + +type Response struct { + Status int + Header http.Header + Body string + Error error +} + +func NewResponse() *Response { + return &Response{ + Header: make(http.Header), + } +} + +func (this *Response) Set(status int, body string) *Response { + this.Status = status + this.Body = body + + return this +} + +func (this *Response) SetHeader(key, value string) *Response { + this.Header.Set(key, value) + + return this +} + +func (this *Response) SetError(err error) *Response { + this.Error = err + + switch err { + case errs.ResourceNotFoundError: + this.Status = http.StatusNotFound + case errs.UnauthorizedError: + this.Status = http.StatusUnauthorized + case errs.ForbiddenError: + this.Status = http.StatusForbidden + default: + this.Status = http.StatusInternalServerError + } + + return this +} + +func (this *Response) Write(writer http.ResponseWriter) { + if this.Error == errs.UnauthorizedError { + this.SetHeader("WWW-Authenticate", `Basic realm="Restricted"`) + } + + for key, values := range this.Header { + for _, value := range values { + writer.Header().Set(key, value) + } + } + + writer.WriteHeader(this.Status) + io.WriteString(writer, this.Body) +} diff --git a/vendor/github.com/samedi/caldav-go/handlers/shared.go b/vendor/github.com/samedi/caldav-go/handlers/shared.go new file mode 100644 index 0000000..b133fe3 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/handlers/shared.go @@ -0,0 +1,22 @@ +package handlers + +import ( + "net/http" + "io/ioutil" + "bytes" + "github.com/samedi/caldav-go/lib" +) + +// Supported ICal components on this server. +var supportedComponents = []string{lib.VCALENDAR, lib.VEVENT} + +// This function reads the request body and restore its content, so that +// the request body can be read a second time. +func readRequestBody(request *http.Request) string { + // Read the content + body, _ := ioutil.ReadAll(request.Body) + // Restore the io.ReadCloser to its original state + request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + // Use the content + return string(body) +} diff --git a/vendor/github.com/samedi/caldav-go/ixml/ixml.go b/vendor/github.com/samedi/caldav-go/ixml/ixml.go new file mode 100644 index 0000000..6d3b94c --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/ixml/ixml.go @@ -0,0 +1,93 @@ +package ixml + +import ( + "fmt" + "bytes" + "net/http" + "encoding/xml" + + "github.com/samedi/caldav-go/lib" +) + +const ( + DAV_NS = "DAV:" + CALDAV_NS = "urn:ietf:params:xml:ns:caldav" + CALSERV_NS = "http://calendarserver.org/ns/" +) + +var NS_PREFIXES = map[string]string{ + DAV_NS: "D", + CALDAV_NS: "C", + CALSERV_NS: "CS", +} + +var ( + CALENDAR_TG = xml.Name{CALDAV_NS, "calendar"} + CALENDAR_DATA_TG = xml.Name{CALDAV_NS, "calendar-data"} + CALENDAR_HOME_SET_TG = xml.Name{CALDAV_NS, "calendar-home-set"} + CALENDAR_QUERY_TG = xml.Name{CALDAV_NS, "calendar-query"} + CALENDAR_MULTIGET_TG = xml.Name{CALDAV_NS, "calendar-multiget"} + CALENDAR_USER_ADDRESS_SET_TG = xml.Name{CALDAV_NS, "calendar-user-address-set"} + COLLECTION_TG = xml.Name{DAV_NS, "collection"} + CURRENT_USER_PRINCIPAL_TG = xml.Name{DAV_NS, "current-user-principal"} + DISPLAY_NAME_TG = xml.Name{DAV_NS, "displayname"} + GET_CONTENT_LENGTH_TG = xml.Name{DAV_NS, "getcontentlength"} + GET_CONTENT_TYPE_TG = xml.Name{DAV_NS, "getcontenttype"} + GET_CTAG_TG = xml.Name{CALSERV_NS, "getctag"} + GET_ETAG_TG = xml.Name{DAV_NS, "getetag"} + GET_LAST_MODIFIED_TG = xml.Name{DAV_NS, "getlastmodified"} + HREF_TG = xml.Name{DAV_NS, "href"} + OWNER_TG = xml.Name{DAV_NS, "owner"} + PRINCIPAL_TG = xml.Name{DAV_NS, "principal"} + PRINCIPAL_COLLECTION_SET_TG = xml.Name{DAV_NS, "principal-collection-set"} + PRINCIPAL_URL_TG = xml.Name{DAV_NS, "principal-URL"} + RESOURCE_TYPE_TG = xml.Name{DAV_NS, "resourcetype"} + STATUS_TG = xml.Name{DAV_NS, "status"} + SUPPORTED_CALENDAR_COMPONENT_SET_TG = xml.Name{CALDAV_NS, "supported-calendar-component-set"} +) + +func Namespaces() string { + bf := new(lib.StringBuffer) + bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[DAV_NS], DAV_NS) + bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[CALDAV_NS], CALDAV_NS) + bf.Write(`xmlns:%s="%s"`, NS_PREFIXES[CALSERV_NS], CALSERV_NS) + + return bf.String() +} + +// Tag returns a XML tag as string based on the given tag name and content. It +// takes in consideration the namespace and also if it is an empty content or not. +func Tag(xmlName xml.Name, content string) string { + name := xmlName.Local + ns := NS_PREFIXES[xmlName.Space] + + if ns != "" { + ns = ns + ":" + } + + if content != "" { + return fmt.Sprintf("<%s%s>%s", ns, name, content, ns, name) + } else { + return fmt.Sprintf("<%s%s/>", ns, name) + } +} + +// HrefTag returns a DAV tag with the given href path. +func HrefTag(href string) (tag string) { + return Tag(HREF_TG, href) +} + +// StatusTag returns a DAV tag with the given HTTP status. The +// status is translated into a label, e.g.: HTTP/1.1 404 NotFound. +func StatusTag(status int) string { + statusText := fmt.Sprintf("HTTP/1.1 %d %s", status, http.StatusText(status)) + return Tag(STATUS_TG, statusText) +} + +// EscapeText escapes any special character in the given text and returns the result. +func EscapeText(text string) string { + buffer := bytes.NewBufferString("") + xml.EscapeText(buffer, []byte(text)) + + return buffer.String() +} diff --git a/vendor/github.com/samedi/caldav-go/lib/components.go b/vendor/github.com/samedi/caldav-go/lib/components.go new file mode 100644 index 0000000..99ac3e7 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/lib/components.go @@ -0,0 +1,8 @@ +package lib + +const ( + VCALENDAR = "VCALENDAR" + VEVENT = "VEVENT" + VJOURNAL = "VJOURNAL" + VTODO = "VTODO" +) diff --git a/vendor/github.com/samedi/caldav-go/lib/paths.go b/vendor/github.com/samedi/caldav-go/lib/paths.go new file mode 100644 index 0000000..d46b176 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/lib/paths.go @@ -0,0 +1,10 @@ +package lib + +import ( + "path/filepath" +) + +func ToSlashPath(path string) string { + cleanPath := filepath.Clean(path) + return filepath.ToSlash(cleanPath) +} diff --git a/vendor/github.com/samedi/caldav-go/lib/strbuff.go b/vendor/github.com/samedi/caldav-go/lib/strbuff.go new file mode 100644 index 0000000..45a5beb --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/lib/strbuff.go @@ -0,0 +1,18 @@ +package lib + +import ( + "fmt" + "bytes" +) + +type StringBuffer struct { + buffer bytes.Buffer +} + +func (b *StringBuffer) Write(format string, elem ...interface{}) { + b.buffer.WriteString(fmt.Sprintf(format, elem...)) +} + +func (b *StringBuffer) String() string { + return b.buffer.String() +} diff --git a/vendor/github.com/samedi/caldav-go/test.sh b/vendor/github.com/samedi/caldav-go/test.sh new file mode 100755 index 0000000..09efdc7 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +go test -race ./... +rm -rf test-data diff --git a/vendor/github.com/samedi/caldav-go/version.go b/vendor/github.com/samedi/caldav-go/version.go new file mode 100644 index 0000000..bf787e3 --- /dev/null +++ b/vendor/github.com/samedi/caldav-go/version.go @@ -0,0 +1,5 @@ +package caldav + +const ( + VERSION = "3.0.0" +) diff --git a/vendor/golang.org/x/crypto/AUTHORS b/vendor/golang.org/x/crypto/AUTHORS new file mode 100644 index 0000000..2b00ddb --- /dev/null +++ b/vendor/golang.org/x/crypto/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at https://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/crypto/CONTRIBUTORS b/vendor/golang.org/x/crypto/CONTRIBUTORS new file mode 100644 index 0000000..1fbd3e9 --- /dev/null +++ b/vendor/golang.org/x/crypto/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at https://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/crypto/LICENSE b/vendor/golang.org/x/crypto/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/crypto/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/crypto/PATENTS b/vendor/golang.org/x/crypto/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/crypto/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/crypto/bcrypt/base64.go b/vendor/golang.org/x/crypto/bcrypt/base64.go new file mode 100644 index 0000000..fc31160 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/base64.go @@ -0,0 +1,35 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +import "encoding/base64" + +const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +var bcEncoding = base64.NewEncoding(alphabet) + +func base64Encode(src []byte) []byte { + n := bcEncoding.EncodedLen(len(src)) + dst := make([]byte, n) + bcEncoding.Encode(dst, src) + for dst[n-1] == '=' { + n-- + } + return dst[:n] +} + +func base64Decode(src []byte) ([]byte, error) { + numOfEquals := 4 - (len(src) % 4) + for i := 0; i < numOfEquals; i++ { + src = append(src, '=') + } + + dst := make([]byte, bcEncoding.DecodedLen(len(src))) + n, err := bcEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + return dst[:n], nil +} diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go new file mode 100644 index 0000000..aeb73f8 --- /dev/null +++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go @@ -0,0 +1,295 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing +// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf +package bcrypt // import "golang.org/x/crypto/bcrypt" + +// The code is a port of Provos and Mazières's C implementation. +import ( + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "io" + "strconv" + + "golang.org/x/crypto/blowfish" +) + +const ( + MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword + MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword + DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword +) + +// The error returned from CompareHashAndPassword when a password and hash do +// not match. +var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") + +// The error returned from CompareHashAndPassword when a hash is too short to +// be a bcrypt hash. +var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") + +// The error returned from CompareHashAndPassword when a hash was created with +// a bcrypt algorithm newer than this implementation. +type HashVersionTooNewError byte + +func (hv HashVersionTooNewError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) +} + +// The error returned from CompareHashAndPassword when a hash starts with something other than '$' +type InvalidHashPrefixError byte + +func (ih InvalidHashPrefixError) Error() string { + return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) +} + +type InvalidCostError int + +func (ic InvalidCostError) Error() string { + return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) +} + +const ( + majorVersion = '2' + minorVersion = 'a' + maxSaltSize = 16 + maxCryptedHashSize = 23 + encodedSaltSize = 22 + encodedHashSize = 31 + minHashSize = 59 +) + +// magicCipherData is an IV for the 64 Blowfish encryption calls in +// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. +var magicCipherData = []byte{ + 0x4f, 0x72, 0x70, 0x68, + 0x65, 0x61, 0x6e, 0x42, + 0x65, 0x68, 0x6f, 0x6c, + 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, + 0x6f, 0x75, 0x62, 0x74, +} + +type hashed struct { + hash []byte + salt []byte + cost int // allowed range is MinCost to MaxCost + major byte + minor byte +} + +// GenerateFromPassword returns the bcrypt hash of the password at the given +// cost. If the cost given is less than MinCost, the cost will be set to +// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, +// to compare the returned hashed password with its cleartext version. +func GenerateFromPassword(password []byte, cost int) ([]byte, error) { + p, err := newFromPassword(password, cost) + if err != nil { + return nil, err + } + return p.Hash(), nil +} + +// CompareHashAndPassword compares a bcrypt hashed password with its possible +// plaintext equivalent. Returns nil on success, or an error on failure. +func CompareHashAndPassword(hashedPassword, password []byte) error { + p, err := newFromHash(hashedPassword) + if err != nil { + return err + } + + otherHash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return err + } + + otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} + if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { + return nil + } + + return ErrMismatchedHashAndPassword +} + +// Cost returns the hashing cost used to create the given hashed +// password. When, in the future, the hashing cost of a password system needs +// to be increased in order to adjust for greater computational power, this +// function allows one to establish which passwords need to be updated. +func Cost(hashedPassword []byte) (int, error) { + p, err := newFromHash(hashedPassword) + if err != nil { + return 0, err + } + return p.cost, nil +} + +func newFromPassword(password []byte, cost int) (*hashed, error) { + if cost < MinCost { + cost = DefaultCost + } + p := new(hashed) + p.major = majorVersion + p.minor = minorVersion + + err := checkCost(cost) + if err != nil { + return nil, err + } + p.cost = cost + + unencodedSalt := make([]byte, maxSaltSize) + _, err = io.ReadFull(rand.Reader, unencodedSalt) + if err != nil { + return nil, err + } + + p.salt = base64Encode(unencodedSalt) + hash, err := bcrypt(password, p.cost, p.salt) + if err != nil { + return nil, err + } + p.hash = hash + return p, err +} + +func newFromHash(hashedSecret []byte) (*hashed, error) { + if len(hashedSecret) < minHashSize { + return nil, ErrHashTooShort + } + p := new(hashed) + n, err := p.decodeVersion(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + n, err = p.decodeCost(hashedSecret) + if err != nil { + return nil, err + } + hashedSecret = hashedSecret[n:] + + // The "+2" is here because we'll have to append at most 2 '=' to the salt + // when base64 decoding it in expensiveBlowfishSetup(). + p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) + copy(p.salt, hashedSecret[:encodedSaltSize]) + + hashedSecret = hashedSecret[encodedSaltSize:] + p.hash = make([]byte, len(hashedSecret)) + copy(p.hash, hashedSecret) + + return p, nil +} + +func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { + cipherData := make([]byte, len(magicCipherData)) + copy(cipherData, magicCipherData) + + c, err := expensiveBlowfishSetup(password, uint32(cost), salt) + if err != nil { + return nil, err + } + + for i := 0; i < 24; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) + } + } + + // Bug compatibility with C bcrypt implementations. We only encode 23 of + // the 24 bytes encrypted. + hsh := base64Encode(cipherData[:maxCryptedHashSize]) + return hsh, nil +} + +func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { + csalt, err := base64Decode(salt) + if err != nil { + return nil, err + } + + // Bug compatibility with C bcrypt implementations. They use the trailing + // NULL in the key string during expansion. + // We copy the key to prevent changing the underlying array. + ckey := append(key[:len(key):len(key)], 0) + + c, err := blowfish.NewSaltedCipher(ckey, csalt) + if err != nil { + return nil, err + } + + var i, rounds uint64 + rounds = 1 << cost + for i = 0; i < rounds; i++ { + blowfish.ExpandKey(ckey, c) + blowfish.ExpandKey(csalt, c) + } + + return c, nil +} + +func (p *hashed) Hash() []byte { + arr := make([]byte, 60) + arr[0] = '$' + arr[1] = p.major + n := 2 + if p.minor != 0 { + arr[2] = p.minor + n = 3 + } + arr[n] = '$' + n++ + copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) + n += 2 + arr[n] = '$' + n++ + copy(arr[n:], p.salt) + n += encodedSaltSize + copy(arr[n:], p.hash) + n += encodedHashSize + return arr[:n] +} + +func (p *hashed) decodeVersion(sbytes []byte) (int, error) { + if sbytes[0] != '$' { + return -1, InvalidHashPrefixError(sbytes[0]) + } + if sbytes[1] > majorVersion { + return -1, HashVersionTooNewError(sbytes[1]) + } + p.major = sbytes[1] + n := 3 + if sbytes[2] != '$' { + p.minor = sbytes[2] + n++ + } + return n, nil +} + +// sbytes should begin where decodeVersion left off. +func (p *hashed) decodeCost(sbytes []byte) (int, error) { + cost, err := strconv.Atoi(string(sbytes[0:2])) + if err != nil { + return -1, err + } + err = checkCost(cost) + if err != nil { + return -1, err + } + p.cost = cost + return 3, nil +} + +func (p *hashed) String() string { + return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) +} + +func checkCost(cost int) error { + if cost < MinCost || cost > MaxCost { + return InvalidCostError(cost) + } + return nil +} diff --git a/vendor/golang.org/x/crypto/blowfish/block.go b/vendor/golang.org/x/crypto/blowfish/block.go new file mode 100644 index 0000000..9d80f19 --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/block.go @@ -0,0 +1,159 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blowfish + +// getNextWord returns the next big-endian uint32 value from the byte slice +// at the given position in a circular manner, updating the position. +func getNextWord(b []byte, pos *int) uint32 { + var w uint32 + j := *pos + for i := 0; i < 4; i++ { + w = w<<8 | uint32(b[j]) + j++ + if j >= len(b) { + j = 0 + } + } + *pos = j + return w +} + +// ExpandKey performs a key expansion on the given *Cipher. Specifically, it +// performs the Blowfish algorithm's key schedule which sets up the *Cipher's +// pi and substitution tables for calls to Encrypt. This is used, primarily, +// by the bcrypt package to reuse the Blowfish key schedule during its +// set up. It's unlikely that you need to use this directly. +func ExpandKey(key []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + // Using inlined getNextWord for performance. + var d uint32 + for k := 0; k < 4; k++ { + d = d<<8 | uint32(key[j]) + j++ + if j >= len(key) { + j = 0 + } + } + c.p[i] ^= d + } + + var l, r uint32 + for i := 0; i < 18; i += 2 { + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +// This is similar to ExpandKey, but folds the salt during the key +// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero +// salt passed in, reusing ExpandKey turns out to be a place of inefficiency +// and specializing it here is useful. +func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + c.p[i] ^= getNextWord(key, &j) + } + + j = 0 + var l, r uint32 + for i := 0; i < 18; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[0] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] + xr ^= c.p[17] + return xr, xl +} + +func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[17] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] + xr ^= c.p[0] + return xr, xl +} diff --git a/vendor/golang.org/x/crypto/blowfish/cipher.go b/vendor/golang.org/x/crypto/blowfish/cipher.go new file mode 100644 index 0000000..2641dad --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/cipher.go @@ -0,0 +1,91 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. +package blowfish // import "golang.org/x/crypto/blowfish" + +// The code is a port of Bruce Schneier's C implementation. +// See https://www.schneier.com/blowfish.html. + +import "strconv" + +// The Blowfish block size in bytes. +const BlockSize = 8 + +// A Cipher is an instance of Blowfish encryption using a particular key. +type Cipher struct { + p [18]uint32 + s0, s1, s2, s3 [256]uint32 +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a Cipher. +// The key argument should be the Blowfish key, from 1 to 56 bytes. +func NewCipher(key []byte) (*Cipher, error) { + var result Cipher + if k := len(key); k < 1 || k > 56 { + return nil, KeySizeError(k) + } + initCipher(&result) + ExpandKey(key, &result) + return &result, nil +} + +// NewSaltedCipher creates a returns a Cipher that folds a salt into its key +// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is +// sufficient and desirable. For bcrypt compatibility, the key can be over 56 +// bytes. +func NewSaltedCipher(key, salt []byte) (*Cipher, error) { + if len(salt) == 0 { + return NewCipher(key) + } + var result Cipher + if k := len(key); k < 1 { + return nil, KeySizeError(k) + } + initCipher(&result) + expandKeyWithSalt(key, salt, &result) + return &result, nil +} + +// BlockSize returns the Blowfish block size, 8 bytes. +// It is necessary to satisfy the Block interface in the +// package "crypto/cipher". +func (c *Cipher) BlockSize() int { return BlockSize } + +// Encrypt encrypts the 8-byte buffer src using the key k +// and stores the result in dst. +// Note that for amounts of data larger than a block, +// it is not safe to just call Encrypt on successive blocks; +// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). +func (c *Cipher) Encrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = encryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +// Decrypt decrypts the 8-byte buffer src using the key k +// and stores the result in dst. +func (c *Cipher) Decrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = decryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +func initCipher(c *Cipher) { + copy(c.p[0:], p[0:]) + copy(c.s0[0:], s0[0:]) + copy(c.s1[0:], s1[0:]) + copy(c.s2[0:], s2[0:]) + copy(c.s3[0:], s3[0:]) +} diff --git a/vendor/golang.org/x/crypto/blowfish/const.go b/vendor/golang.org/x/crypto/blowfish/const.go new file mode 100644 index 0000000..d040775 --- /dev/null +++ b/vendor/golang.org/x/crypto/blowfish/const.go @@ -0,0 +1,199 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The startup permutation array and substitution boxes. +// They are the hexadecimal digits of PI; see: +// https://www.schneier.com/code/constants.txt. + +package blowfish + +var s0 = [256]uint32{ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, +} + +var s1 = [256]uint32{ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, + 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, + 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, + 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, + 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, + 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, + 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, + 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, + 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, + 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, + 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, + 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, + 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, + 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, + 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, + 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, + 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, + 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, + 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, + 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, + 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, + 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, +} + +var s2 = [256]uint32{ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, + 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, + 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, + 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, + 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, + 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, + 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, + 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, + 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, + 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, + 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, + 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, + 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, + 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, + 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, + 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, + 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, + 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, + 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, + 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, + 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, + 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, +} + +var s3 = [256]uint32{ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, +} + +var p = [18]uint32{ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, +} diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/net/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/net/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go new file mode 100644 index 0000000..a3c021d --- /dev/null +++ b/vendor/golang.org/x/net/context/context.go @@ -0,0 +1,56 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// As of Go 1.7 this package is available in the standard library under the +// name context. https://golang.org/pkg/context. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} diff --git a/vendor/golang.org/x/net/context/go17.go b/vendor/golang.org/x/net/context/go17.go new file mode 100644 index 0000000..d20f52b --- /dev/null +++ b/vendor/golang.org/x/net/context/go17.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package context + +import ( + "context" // standard library's context, as of Go 1.7 + "time" +) + +var ( + todo = context.TODO() + background = context.Background() +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = context.Canceled + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = context.DeadlineExceeded + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + ctx, f := context.WithCancel(parent) + return ctx, CancelFunc(f) +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + ctx, f := context.WithDeadline(parent, deadline) + return ctx, CancelFunc(f) +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return context.WithValue(parent, key, val) +} diff --git a/vendor/golang.org/x/net/context/go19.go b/vendor/golang.org/x/net/context/go19.go new file mode 100644 index 0000000..d88bd1d --- /dev/null +++ b/vendor/golang.org/x/net/context/go19.go @@ -0,0 +1,20 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +package context + +import "context" // standard library's context, as of Go 1.7 + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context = context.Context + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc = context.CancelFunc diff --git a/vendor/golang.org/x/net/context/pre_go17.go b/vendor/golang.org/x/net/context/pre_go17.go new file mode 100644 index 0000000..0f35592 --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go17.go @@ -0,0 +1,300 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package context + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, c) + return c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) *cancelCtx { + return &cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + *cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/vendor/golang.org/x/net/context/pre_go19.go b/vendor/golang.org/x/net/context/pre_go19.go new file mode 100644 index 0000000..b105f80 --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go19.go @@ -0,0 +1,109 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +package context + +import "time" + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out chan<- Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() diff --git a/vendor/golang.org/x/net/webdav/file.go b/vendor/golang.org/x/net/webdav/file.go new file mode 100644 index 0000000..748118d --- /dev/null +++ b/vendor/golang.org/x/net/webdav/file.go @@ -0,0 +1,796 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package webdav + +import ( + "encoding/xml" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "golang.org/x/net/context" +) + +// slashClean is equivalent to but slightly more efficient than +// path.Clean("/" + name). +func slashClean(name string) string { + if name == "" || name[0] != '/' { + name = "/" + name + } + return path.Clean(name) +} + +// A FileSystem implements access to a collection of named files. The elements +// in a file path are separated by slash ('/', U+002F) characters, regardless +// of host operating system convention. +// +// Each method has the same semantics as the os package's function of the same +// name. +// +// Note that the os.Rename documentation says that "OS-specific restrictions +// might apply". In particular, whether or not renaming a file or directory +// overwriting another existing file or directory is an error is OS-dependent. +type FileSystem interface { + Mkdir(ctx context.Context, name string, perm os.FileMode) error + OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) + RemoveAll(ctx context.Context, name string) error + Rename(ctx context.Context, oldName, newName string) error + Stat(ctx context.Context, name string) (os.FileInfo, error) +} + +// A File is returned by a FileSystem's OpenFile method and can be served by a +// Handler. +// +// A File may optionally implement the DeadPropsHolder interface, if it can +// load and save dead properties. +type File interface { + http.File + io.Writer +} + +// A Dir implements FileSystem using the native file system restricted to a +// specific directory tree. +// +// While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's +// string value is a filename on the native file system, not a URL, so it is +// separated by filepath.Separator, which isn't necessarily '/'. +// +// An empty Dir is treated as ".". +type Dir string + +func (d Dir) resolve(name string) string { + // This implementation is based on Dir.Open's code in the standard net/http package. + if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || + strings.Contains(name, "\x00") { + return "" + } + dir := string(d) + if dir == "" { + dir = "." + } + return filepath.Join(dir, filepath.FromSlash(slashClean(name))) +} + +func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { + if name = d.resolve(name); name == "" { + return os.ErrNotExist + } + return os.Mkdir(name, perm) +} + +func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { + if name = d.resolve(name); name == "" { + return nil, os.ErrNotExist + } + f, err := os.OpenFile(name, flag, perm) + if err != nil { + return nil, err + } + return f, nil +} + +func (d Dir) RemoveAll(ctx context.Context, name string) error { + if name = d.resolve(name); name == "" { + return os.ErrNotExist + } + if name == filepath.Clean(string(d)) { + // Prohibit removing the virtual root directory. + return os.ErrInvalid + } + return os.RemoveAll(name) +} + +func (d Dir) Rename(ctx context.Context, oldName, newName string) error { + if oldName = d.resolve(oldName); oldName == "" { + return os.ErrNotExist + } + if newName = d.resolve(newName); newName == "" { + return os.ErrNotExist + } + if root := filepath.Clean(string(d)); root == oldName || root == newName { + // Prohibit renaming from or to the virtual root directory. + return os.ErrInvalid + } + return os.Rename(oldName, newName) +} + +func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) { + if name = d.resolve(name); name == "" { + return nil, os.ErrNotExist + } + return os.Stat(name) +} + +// NewMemFS returns a new in-memory FileSystem implementation. +func NewMemFS() FileSystem { + return &memFS{ + root: memFSNode{ + children: make(map[string]*memFSNode), + mode: 0660 | os.ModeDir, + modTime: time.Now(), + }, + } +} + +// A memFS implements FileSystem, storing all metadata and actual file data +// in-memory. No limits on filesystem size are used, so it is not recommended +// this be used where the clients are untrusted. +// +// Concurrent access is permitted. The tree structure is protected by a mutex, +// and each node's contents and metadata are protected by a per-node mutex. +// +// TODO: Enforce file permissions. +type memFS struct { + mu sync.Mutex + root memFSNode +} + +// TODO: clean up and rationalize the walk/find code. + +// walk walks the directory tree for the fullname, calling f at each step. If f +// returns an error, the walk will be aborted and return that same error. +// +// dir is the directory at that step, frag is the name fragment, and final is +// whether it is the final step. For example, walking "/foo/bar/x" will result +// in 3 calls to f: +// - "/", "foo", false +// - "/foo/", "bar", false +// - "/foo/bar/", "x", true +// The frag argument will be empty only if dir is the root node and the walk +// ends at that root node. +func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error { + original := fullname + fullname = slashClean(fullname) + + // Strip any leading "/"s to make fullname a relative path, as the walk + // starts at fs.root. + if fullname[0] == '/' { + fullname = fullname[1:] + } + dir := &fs.root + + for { + frag, remaining := fullname, "" + i := strings.IndexRune(fullname, '/') + final := i < 0 + if !final { + frag, remaining = fullname[:i], fullname[i+1:] + } + if frag == "" && dir != &fs.root { + panic("webdav: empty path fragment for a clean path") + } + if err := f(dir, frag, final); err != nil { + return &os.PathError{ + Op: op, + Path: original, + Err: err, + } + } + if final { + break + } + child := dir.children[frag] + if child == nil { + return &os.PathError{ + Op: op, + Path: original, + Err: os.ErrNotExist, + } + } + if !child.mode.IsDir() { + return &os.PathError{ + Op: op, + Path: original, + Err: os.ErrInvalid, + } + } + dir, fullname = child, remaining + } + return nil +} + +// find returns the parent of the named node and the relative name fragment +// from the parent to the child. For example, if finding "/foo/bar/baz" then +// parent will be the node for "/foo/bar" and frag will be "baz". +// +// If the fullname names the root node, then parent, frag and err will be zero. +// +// find returns an error if the parent does not already exist or the parent +// isn't a directory, but it will not return an error per se if the child does +// not already exist. The error returned is either nil or an *os.PathError +// whose Op is op. +func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) { + err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error { + if !final { + return nil + } + if frag0 != "" { + parent, frag = parent0, frag0 + } + return nil + }) + return parent, frag, err +} + +func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("mkdir", name) + if err != nil { + return err + } + if dir == nil { + // We can't create the root. + return os.ErrInvalid + } + if _, ok := dir.children[frag]; ok { + return os.ErrExist + } + dir.children[frag] = &memFSNode{ + children: make(map[string]*memFSNode), + mode: perm.Perm() | os.ModeDir, + modTime: time.Now(), + } + return nil +} + +func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("open", name) + if err != nil { + return nil, err + } + var n *memFSNode + if dir == nil { + // We're opening the root. + if flag&(os.O_WRONLY|os.O_RDWR) != 0 { + return nil, os.ErrPermission + } + n, frag = &fs.root, "/" + + } else { + n = dir.children[frag] + if flag&(os.O_SYNC|os.O_APPEND) != 0 { + // memFile doesn't support these flags yet. + return nil, os.ErrInvalid + } + if flag&os.O_CREATE != 0 { + if flag&os.O_EXCL != 0 && n != nil { + return nil, os.ErrExist + } + if n == nil { + n = &memFSNode{ + mode: perm.Perm(), + } + dir.children[frag] = n + } + } + if n == nil { + return nil, os.ErrNotExist + } + if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 { + n.mu.Lock() + n.data = nil + n.mu.Unlock() + } + } + + children := make([]os.FileInfo, 0, len(n.children)) + for cName, c := range n.children { + children = append(children, c.stat(cName)) + } + return &memFile{ + n: n, + nameSnapshot: frag, + childrenSnapshot: children, + }, nil +} + +func (fs *memFS) RemoveAll(ctx context.Context, name string) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("remove", name) + if err != nil { + return err + } + if dir == nil { + // We can't remove the root. + return os.ErrInvalid + } + delete(dir.children, frag) + return nil +} + +func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error { + fs.mu.Lock() + defer fs.mu.Unlock() + + oldName = slashClean(oldName) + newName = slashClean(newName) + if oldName == newName { + return nil + } + if strings.HasPrefix(newName, oldName+"/") { + // We can't rename oldName to be a sub-directory of itself. + return os.ErrInvalid + } + + oDir, oFrag, err := fs.find("rename", oldName) + if err != nil { + return err + } + if oDir == nil { + // We can't rename from the root. + return os.ErrInvalid + } + + nDir, nFrag, err := fs.find("rename", newName) + if err != nil { + return err + } + if nDir == nil { + // We can't rename to the root. + return os.ErrInvalid + } + + oNode, ok := oDir.children[oFrag] + if !ok { + return os.ErrNotExist + } + if oNode.children != nil { + if nNode, ok := nDir.children[nFrag]; ok { + if nNode.children == nil { + return errNotADirectory + } + if len(nNode.children) != 0 { + return errDirectoryNotEmpty + } + } + } + delete(oDir.children, oFrag) + nDir.children[nFrag] = oNode + return nil +} + +func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + dir, frag, err := fs.find("stat", name) + if err != nil { + return nil, err + } + if dir == nil { + // We're stat'ting the root. + return fs.root.stat("/"), nil + } + if n, ok := dir.children[frag]; ok { + return n.stat(path.Base(name)), nil + } + return nil, os.ErrNotExist +} + +// A memFSNode represents a single entry in the in-memory filesystem and also +// implements os.FileInfo. +type memFSNode struct { + // children is protected by memFS.mu. + children map[string]*memFSNode + + mu sync.Mutex + data []byte + mode os.FileMode + modTime time.Time + deadProps map[xml.Name]Property +} + +func (n *memFSNode) stat(name string) *memFileInfo { + n.mu.Lock() + defer n.mu.Unlock() + return &memFileInfo{ + name: name, + size: int64(len(n.data)), + mode: n.mode, + modTime: n.modTime, + } +} + +func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) { + n.mu.Lock() + defer n.mu.Unlock() + if len(n.deadProps) == 0 { + return nil, nil + } + ret := make(map[xml.Name]Property, len(n.deadProps)) + for k, v := range n.deadProps { + ret[k] = v + } + return ret, nil +} + +func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) { + n.mu.Lock() + defer n.mu.Unlock() + pstat := Propstat{Status: http.StatusOK} + for _, patch := range patches { + for _, p := range patch.Props { + pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName}) + if patch.Remove { + delete(n.deadProps, p.XMLName) + continue + } + if n.deadProps == nil { + n.deadProps = map[xml.Name]Property{} + } + n.deadProps[p.XMLName] = p + } + } + return []Propstat{pstat}, nil +} + +type memFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (f *memFileInfo) Name() string { return f.name } +func (f *memFileInfo) Size() int64 { return f.size } +func (f *memFileInfo) Mode() os.FileMode { return f.mode } +func (f *memFileInfo) ModTime() time.Time { return f.modTime } +func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() } +func (f *memFileInfo) Sys() interface{} { return nil } + +// A memFile is a File implementation for a memFSNode. It is a per-file (not +// per-node) read/write position, and a snapshot of the memFS' tree structure +// (a node's name and children) for that node. +type memFile struct { + n *memFSNode + nameSnapshot string + childrenSnapshot []os.FileInfo + // pos is protected by n.mu. + pos int +} + +// A *memFile implements the optional DeadPropsHolder interface. +var _ DeadPropsHolder = (*memFile)(nil) + +func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() } +func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) } + +func (f *memFile) Close() error { + return nil +} + +func (f *memFile) Read(p []byte) (int, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + if f.n.mode.IsDir() { + return 0, os.ErrInvalid + } + if f.pos >= len(f.n.data) { + return 0, io.EOF + } + n := copy(p, f.n.data[f.pos:]) + f.pos += n + return n, nil +} + +func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + if !f.n.mode.IsDir() { + return nil, os.ErrInvalid + } + old := f.pos + if old >= len(f.childrenSnapshot) { + // The os.File Readdir docs say that at the end of a directory, + // the error is io.EOF if count > 0 and nil if count <= 0. + if count > 0 { + return nil, io.EOF + } + return nil, nil + } + if count > 0 { + f.pos += count + if f.pos > len(f.childrenSnapshot) { + f.pos = len(f.childrenSnapshot) + } + } else { + f.pos = len(f.childrenSnapshot) + old = 0 + } + return f.childrenSnapshot[old:f.pos], nil +} + +func (f *memFile) Seek(offset int64, whence int) (int64, error) { + f.n.mu.Lock() + defer f.n.mu.Unlock() + npos := f.pos + // TODO: How to handle offsets greater than the size of system int? + switch whence { + case os.SEEK_SET: + npos = int(offset) + case os.SEEK_CUR: + npos += int(offset) + case os.SEEK_END: + npos = len(f.n.data) + int(offset) + default: + npos = -1 + } + if npos < 0 { + return 0, os.ErrInvalid + } + f.pos = npos + return int64(f.pos), nil +} + +func (f *memFile) Stat() (os.FileInfo, error) { + return f.n.stat(f.nameSnapshot), nil +} + +func (f *memFile) Write(p []byte) (int, error) { + lenp := len(p) + f.n.mu.Lock() + defer f.n.mu.Unlock() + + if f.n.mode.IsDir() { + return 0, os.ErrInvalid + } + if f.pos < len(f.n.data) { + n := copy(f.n.data[f.pos:], p) + f.pos += n + p = p[n:] + } else if f.pos > len(f.n.data) { + // Write permits the creation of holes, if we've seek'ed past the + // existing end of file. + if f.pos <= cap(f.n.data) { + oldLen := len(f.n.data) + f.n.data = f.n.data[:f.pos] + hole := f.n.data[oldLen:] + for i := range hole { + hole[i] = 0 + } + } else { + d := make([]byte, f.pos, f.pos+len(p)) + copy(d, f.n.data) + f.n.data = d + } + } + + if len(p) > 0 { + // We should only get here if f.pos == len(f.n.data). + f.n.data = append(f.n.data, p...) + f.pos = len(f.n.data) + } + f.n.modTime = time.Now() + return lenp, nil +} + +// moveFiles moves files and/or directories from src to dst. +// +// See section 9.9.4 for when various HTTP status codes apply. +func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) { + created := false + if _, err := fs.Stat(ctx, dst); err != nil { + if !os.IsNotExist(err) { + return http.StatusForbidden, err + } + created = true + } else if overwrite { + // Section 9.9.3 says that "If a resource exists at the destination + // and the Overwrite header is "T", then prior to performing the move, + // the server must perform a DELETE with "Depth: infinity" on the + // destination resource. + if err := fs.RemoveAll(ctx, dst); err != nil { + return http.StatusForbidden, err + } + } else { + return http.StatusPreconditionFailed, os.ErrExist + } + if err := fs.Rename(ctx, src, dst); err != nil { + return http.StatusForbidden, err + } + if created { + return http.StatusCreated, nil + } + return http.StatusNoContent, nil +} + +func copyProps(dst, src File) error { + d, ok := dst.(DeadPropsHolder) + if !ok { + return nil + } + s, ok := src.(DeadPropsHolder) + if !ok { + return nil + } + m, err := s.DeadProps() + if err != nil { + return err + } + props := make([]Property, 0, len(m)) + for _, prop := range m { + props = append(props, prop) + } + _, err = d.Patch([]Proppatch{{Props: props}}) + return err +} + +// copyFiles copies files and/or directories from src to dst. +// +// See section 9.8.5 for when various HTTP status codes apply. +func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) { + if recursion == 1000 { + return http.StatusInternalServerError, errRecursionTooDeep + } + recursion++ + + // TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/ + // into /A/B/ could lead to infinite recursion if not handled correctly." + + srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0) + if err != nil { + if os.IsNotExist(err) { + return http.StatusNotFound, err + } + return http.StatusInternalServerError, err + } + defer srcFile.Close() + srcStat, err := srcFile.Stat() + if err != nil { + if os.IsNotExist(err) { + return http.StatusNotFound, err + } + return http.StatusInternalServerError, err + } + srcPerm := srcStat.Mode() & os.ModePerm + + created := false + if _, err := fs.Stat(ctx, dst); err != nil { + if os.IsNotExist(err) { + created = true + } else { + return http.StatusForbidden, err + } + } else { + if !overwrite { + return http.StatusPreconditionFailed, os.ErrExist + } + if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) { + return http.StatusForbidden, err + } + } + + if srcStat.IsDir() { + if err := fs.Mkdir(ctx, dst, srcPerm); err != nil { + return http.StatusForbidden, err + } + if depth == infiniteDepth { + children, err := srcFile.Readdir(-1) + if err != nil { + return http.StatusForbidden, err + } + for _, c := range children { + name := c.Name() + s := path.Join(src, name) + d := path.Join(dst, name) + cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion) + if cErr != nil { + // TODO: MultiStatus. + return cStatus, cErr + } + } + } + + } else { + dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm) + if err != nil { + if os.IsNotExist(err) { + return http.StatusConflict, err + } + return http.StatusForbidden, err + + } + _, copyErr := io.Copy(dstFile, srcFile) + propsErr := copyProps(dstFile, srcFile) + closeErr := dstFile.Close() + if copyErr != nil { + return http.StatusInternalServerError, copyErr + } + if propsErr != nil { + return http.StatusInternalServerError, propsErr + } + if closeErr != nil { + return http.StatusInternalServerError, closeErr + } + } + + if created { + return http.StatusCreated, nil + } + return http.StatusNoContent, nil +} + +// walkFS traverses filesystem fs starting at name up to depth levels. +// +// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node, +// walkFS calls walkFn. If a visited file system node is a directory and +// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node. +func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error { + // This implementation is based on Walk's code in the standard path/filepath package. + err := walkFn(name, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + if !info.IsDir() || depth == 0 { + return nil + } + if depth == 1 { + depth = 0 + } + + // Read directory names. + f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0) + if err != nil { + return walkFn(name, info, err) + } + fileInfos, err := f.Readdir(0) + f.Close() + if err != nil { + return walkFn(name, info, err) + } + + for _, fileInfo := range fileInfos { + filename := path.Join(name, fileInfo.Name()) + fileInfo, err := fs.Stat(ctx, filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} diff --git a/vendor/golang.org/x/net/webdav/file_go1.6.go b/vendor/golang.org/x/net/webdav/file_go1.6.go new file mode 100644 index 0000000..fa38770 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/file_go1.6.go @@ -0,0 +1,17 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package webdav + +import ( + "net/http" + + "golang.org/x/net/context" +) + +func getContext(r *http.Request) context.Context { + return context.Background() +} diff --git a/vendor/golang.org/x/net/webdav/file_go1.7.go b/vendor/golang.org/x/net/webdav/file_go1.7.go new file mode 100644 index 0000000..d1c3de8 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/file_go1.7.go @@ -0,0 +1,16 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package webdav + +import ( + "context" + "net/http" +) + +func getContext(r *http.Request) context.Context { + return r.Context() +} diff --git a/vendor/golang.org/x/net/webdav/if.go b/vendor/golang.org/x/net/webdav/if.go new file mode 100644 index 0000000..416e81c --- /dev/null +++ b/vendor/golang.org/x/net/webdav/if.go @@ -0,0 +1,173 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package webdav + +// The If header is covered by Section 10.4. +// http://www.webdav.org/specs/rfc4918.html#HEADER_If + +import ( + "strings" +) + +// ifHeader is a disjunction (OR) of ifLists. +type ifHeader struct { + lists []ifList +} + +// ifList is a conjunction (AND) of Conditions, and an optional resource tag. +type ifList struct { + resourceTag string + conditions []Condition +} + +// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string +// should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is +// returned by req.Header.Get("If") for a http.Request req. +func parseIfHeader(httpHeader string) (h ifHeader, ok bool) { + s := strings.TrimSpace(httpHeader) + switch tokenType, _, _ := lex(s); tokenType { + case '(': + return parseNoTagLists(s) + case angleTokenType: + return parseTaggedLists(s) + default: + return ifHeader{}, false + } +} + +func parseNoTagLists(s string) (h ifHeader, ok bool) { + for { + l, remaining, ok := parseList(s) + if !ok { + return ifHeader{}, false + } + h.lists = append(h.lists, l) + if remaining == "" { + return h, true + } + s = remaining + } +} + +func parseTaggedLists(s string) (h ifHeader, ok bool) { + resourceTag, n := "", 0 + for first := true; ; first = false { + tokenType, tokenStr, remaining := lex(s) + switch tokenType { + case angleTokenType: + if !first && n == 0 { + return ifHeader{}, false + } + resourceTag, n = tokenStr, 0 + s = remaining + case '(': + n++ + l, remaining, ok := parseList(s) + if !ok { + return ifHeader{}, false + } + l.resourceTag = resourceTag + h.lists = append(h.lists, l) + if remaining == "" { + return h, true + } + s = remaining + default: + return ifHeader{}, false + } + } +} + +func parseList(s string) (l ifList, remaining string, ok bool) { + tokenType, _, s := lex(s) + if tokenType != '(' { + return ifList{}, "", false + } + for { + tokenType, _, remaining = lex(s) + if tokenType == ')' { + if len(l.conditions) == 0 { + return ifList{}, "", false + } + return l, remaining, true + } + c, remaining, ok := parseCondition(s) + if !ok { + return ifList{}, "", false + } + l.conditions = append(l.conditions, c) + s = remaining + } +} + +func parseCondition(s string) (c Condition, remaining string, ok bool) { + tokenType, tokenStr, s := lex(s) + if tokenType == notTokenType { + c.Not = true + tokenType, tokenStr, s = lex(s) + } + switch tokenType { + case strTokenType, angleTokenType: + c.Token = tokenStr + case squareTokenType: + c.ETag = tokenStr + default: + return Condition{}, "", false + } + return c, s, true +} + +// Single-rune tokens like '(' or ')' have a token type equal to their rune. +// All other tokens have a negative token type. +const ( + errTokenType = rune(-1) + eofTokenType = rune(-2) + strTokenType = rune(-3) + notTokenType = rune(-4) + angleTokenType = rune(-5) + squareTokenType = rune(-6) +) + +func lex(s string) (tokenType rune, tokenStr string, remaining string) { + // The net/textproto Reader that parses the HTTP header will collapse + // Linear White Space that spans multiple "\r\n" lines to a single " ", + // so we don't need to look for '\r' or '\n'. + for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') { + s = s[1:] + } + if len(s) == 0 { + return eofTokenType, "", "" + } + i := 0 +loop: + for ; i < len(s); i++ { + switch s[i] { + case '\t', ' ', '(', ')', '<', '>', '[', ']': + break loop + } + } + + if i != 0 { + tokenStr, remaining = s[:i], s[i:] + if tokenStr == "Not" { + return notTokenType, "", remaining + } + return strTokenType, tokenStr, remaining + } + + j := 0 + switch s[0] { + case '<': + j, tokenType = strings.IndexByte(s, '>'), angleTokenType + case '[': + j, tokenType = strings.IndexByte(s, ']'), squareTokenType + default: + return rune(s[0]), "", s[1:] + } + if j < 0 { + return errTokenType, "", "" + } + return tokenType, s[1:j], s[j+1:] +} diff --git a/vendor/golang.org/x/net/webdav/internal/xml/README b/vendor/golang.org/x/net/webdav/internal/xml/README new file mode 100644 index 0000000..89656f4 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/internal/xml/README @@ -0,0 +1,11 @@ +This is a fork of the encoding/xml package at ca1d6c4, the last commit before +https://go.googlesource.com/go/+/c0d6d33 "encoding/xml: restore Go 1.4 name +space behavior" made late in the lead-up to the Go 1.5 release. + +The list of encoding/xml changes is at +https://go.googlesource.com/go/+log/master/src/encoding/xml + +This fork is temporary, and I (nigeltao) expect to revert it after Go 1.6 is +released. + +See http://golang.org/issue/11841 diff --git a/vendor/golang.org/x/net/webdav/internal/xml/marshal.go b/vendor/golang.org/x/net/webdav/internal/xml/marshal.go new file mode 100644 index 0000000..cb82ec2 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/internal/xml/marshal.go @@ -0,0 +1,1223 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "bufio" + "bytes" + "encoding" + "fmt" + "io" + "reflect" + "strconv" + "strings" +) + +const ( + // A generic XML header suitable for use with the output of Marshal. + // This is not automatically added to any output of this package, + // it is provided as a convenience. + Header = `` + "\n" +) + +// Marshal returns the XML encoding of v. +// +// Marshal handles an array or slice by marshalling each of the elements. +// Marshal handles a pointer by marshalling the value it points at or, if the +// pointer is nil, by writing nothing. Marshal handles an interface value by +// marshalling the value it contains or, if the interface value is nil, by +// writing nothing. Marshal handles all other data by writing one or more XML +// elements containing the data. +// +// The name for the XML elements is taken from, in order of preference: +// - the tag on the XMLName field, if the data is a struct +// - the value of the XMLName field of type xml.Name +// - the tag of the struct field used to obtain the data +// - the name of the struct field used to obtain the data +// - the name of the marshalled type +// +// The XML element for a struct contains marshalled elements for each of the +// exported fields of the struct, with these exceptions: +// - the XMLName field, described above, is omitted. +// - a field with tag "-" is omitted. +// - a field with tag "name,attr" becomes an attribute with +// the given name in the XML element. +// - a field with tag ",attr" becomes an attribute with the +// field name in the XML element. +// - a field with tag ",chardata" is written as character data, +// not as an XML element. +// - a field with tag ",innerxml" is written verbatim, not subject +// to the usual marshalling procedure. +// - a field with tag ",comment" is written as an XML comment, not +// subject to the usual marshalling procedure. It must not contain +// the "--" string within it. +// - a field with a tag including the "omitempty" option is omitted +// if the field value is empty. The empty values are false, 0, any +// nil pointer or interface value, and any array, slice, map, or +// string of length zero. +// - an anonymous struct field is handled as if the fields of its +// value were part of the outer struct. +// +// If a field uses a tag "a>b>c", then the element c will be nested inside +// parent elements a and b. Fields that appear next to each other that name +// the same parent will be enclosed in one XML element. +// +// See MarshalIndent for an example. +// +// Marshal will return an error if asked to marshal a channel, function, or map. +func Marshal(v interface{}) ([]byte, error) { + var b bytes.Buffer + if err := NewEncoder(&b).Encode(v); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// Marshaler is the interface implemented by objects that can marshal +// themselves into valid XML elements. +// +// MarshalXML encodes the receiver as zero or more XML elements. +// By convention, arrays or slices are typically encoded as a sequence +// of elements, one per entry. +// Using start as the element tag is not required, but doing so +// will enable Unmarshal to match the XML elements to the correct +// struct field. +// One common implementation strategy is to construct a separate +// value with a layout corresponding to the desired XML and then +// to encode it using e.EncodeElement. +// Another common strategy is to use repeated calls to e.EncodeToken +// to generate the XML output one token at a time. +// The sequence of encoded tokens must make up zero or more valid +// XML elements. +type Marshaler interface { + MarshalXML(e *Encoder, start StartElement) error +} + +// MarshalerAttr is the interface implemented by objects that can marshal +// themselves into valid XML attributes. +// +// MarshalXMLAttr returns an XML attribute with the encoded value of the receiver. +// Using name as the attribute name is not required, but doing so +// will enable Unmarshal to match the attribute to the correct +// struct field. +// If MarshalXMLAttr returns the zero attribute Attr{}, no attribute +// will be generated in the output. +// MarshalXMLAttr is used only for struct fields with the +// "attr" option in the field tag. +type MarshalerAttr interface { + MarshalXMLAttr(name Name) (Attr, error) +} + +// MarshalIndent works like Marshal, but each XML element begins on a new +// indented line that starts with prefix and is followed by one or more +// copies of indent according to the nesting depth. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + var b bytes.Buffer + enc := NewEncoder(&b) + enc.Indent(prefix, indent) + if err := enc.Encode(v); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// An Encoder writes XML data to an output stream. +type Encoder struct { + p printer +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + e := &Encoder{printer{Writer: bufio.NewWriter(w)}} + e.p.encoder = e + return e +} + +// Indent sets the encoder to generate XML in which each element +// begins on a new indented line that starts with prefix and is followed by +// one or more copies of indent according to the nesting depth. +func (enc *Encoder) Indent(prefix, indent string) { + enc.p.prefix = prefix + enc.p.indent = indent +} + +// Encode writes the XML encoding of v to the stream. +// +// See the documentation for Marshal for details about the conversion +// of Go values to XML. +// +// Encode calls Flush before returning. +func (enc *Encoder) Encode(v interface{}) error { + err := enc.p.marshalValue(reflect.ValueOf(v), nil, nil) + if err != nil { + return err + } + return enc.p.Flush() +} + +// EncodeElement writes the XML encoding of v to the stream, +// using start as the outermost tag in the encoding. +// +// See the documentation for Marshal for details about the conversion +// of Go values to XML. +// +// EncodeElement calls Flush before returning. +func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error { + err := enc.p.marshalValue(reflect.ValueOf(v), nil, &start) + if err != nil { + return err + } + return enc.p.Flush() +} + +var ( + begComment = []byte("") + endProcInst = []byte("?>") + endDirective = []byte(">") +) + +// EncodeToken writes the given XML token to the stream. +// It returns an error if StartElement and EndElement tokens are not +// properly matched. +// +// EncodeToken does not call Flush, because usually it is part of a +// larger operation such as Encode or EncodeElement (or a custom +// Marshaler's MarshalXML invoked during those), and those will call +// Flush when finished. Callers that create an Encoder and then invoke +// EncodeToken directly, without using Encode or EncodeElement, need to +// call Flush when finished to ensure that the XML is written to the +// underlying writer. +// +// EncodeToken allows writing a ProcInst with Target set to "xml" only +// as the first token in the stream. +// +// When encoding a StartElement holding an XML namespace prefix +// declaration for a prefix that is not already declared, contained +// elements (including the StartElement itself) will use the declared +// prefix when encoding names with matching namespace URIs. +func (enc *Encoder) EncodeToken(t Token) error { + + p := &enc.p + switch t := t.(type) { + case StartElement: + if err := p.writeStart(&t); err != nil { + return err + } + case EndElement: + if err := p.writeEnd(t.Name); err != nil { + return err + } + case CharData: + escapeText(p, t, false) + case Comment: + if bytes.Contains(t, endComment) { + return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") + } + p.WriteString("") + return p.cachedWriteError() + case ProcInst: + // First token to be encoded which is also a ProcInst with target of xml + // is the xml declaration. The only ProcInst where target of xml is allowed. + if t.Target == "xml" && p.Buffered() != 0 { + return fmt.Errorf("xml: EncodeToken of ProcInst xml target only valid for xml declaration, first token encoded") + } + if !isNameString(t.Target) { + return fmt.Errorf("xml: EncodeToken of ProcInst with invalid Target") + } + if bytes.Contains(t.Inst, endProcInst) { + return fmt.Errorf("xml: EncodeToken of ProcInst containing ?> marker") + } + p.WriteString(" 0 { + p.WriteByte(' ') + p.Write(t.Inst) + } + p.WriteString("?>") + case Directive: + if !isValidDirective(t) { + return fmt.Errorf("xml: EncodeToken of Directive containing wrong < or > markers") + } + p.WriteString("") + default: + return fmt.Errorf("xml: EncodeToken of invalid token type") + + } + return p.cachedWriteError() +} + +// isValidDirective reports whether dir is a valid directive text, +// meaning angle brackets are matched, ignoring comments and strings. +func isValidDirective(dir Directive) bool { + var ( + depth int + inquote uint8 + incomment bool + ) + for i, c := range dir { + switch { + case incomment: + if c == '>' { + if n := 1 + i - len(endComment); n >= 0 && bytes.Equal(dir[n:i+1], endComment) { + incomment = false + } + } + // Just ignore anything in comment + case inquote != 0: + if c == inquote { + inquote = 0 + } + // Just ignore anything within quotes + case c == '\'' || c == '"': + inquote = c + case c == '<': + if i+len(begComment) < len(dir) && bytes.Equal(dir[i:i+len(begComment)], begComment) { + incomment = true + } else { + depth++ + } + case c == '>': + if depth == 0 { + return false + } + depth-- + } + } + return depth == 0 && inquote == 0 && !incomment +} + +// Flush flushes any buffered XML to the underlying writer. +// See the EncodeToken documentation for details about when it is necessary. +func (enc *Encoder) Flush() error { + return enc.p.Flush() +} + +type printer struct { + *bufio.Writer + encoder *Encoder + seq int + indent string + prefix string + depth int + indentedIn bool + putNewline bool + defaultNS string + attrNS map[string]string // map prefix -> name space + attrPrefix map[string]string // map name space -> prefix + prefixes []printerPrefix + tags []Name +} + +// printerPrefix holds a namespace undo record. +// When an element is popped, the prefix record +// is set back to the recorded URL. The empty +// prefix records the URL for the default name space. +// +// The start of an element is recorded with an element +// that has mark=true. +type printerPrefix struct { + prefix string + url string + mark bool +} + +func (p *printer) prefixForNS(url string, isAttr bool) string { + // The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml" + // and must be referred to that way. + // (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns", + // but users should not be trying to use that one directly - that's our job.) + if url == xmlURL { + return "xml" + } + if !isAttr && url == p.defaultNS { + // We can use the default name space. + return "" + } + return p.attrPrefix[url] +} + +// defineNS pushes any namespace definition found in the given attribute. +// If ignoreNonEmptyDefault is true, an xmlns="nonempty" +// attribute will be ignored. +func (p *printer) defineNS(attr Attr, ignoreNonEmptyDefault bool) error { + var prefix string + if attr.Name.Local == "xmlns" { + if attr.Name.Space != "" && attr.Name.Space != "xml" && attr.Name.Space != xmlURL { + return fmt.Errorf("xml: cannot redefine xmlns attribute prefix") + } + } else if attr.Name.Space == "xmlns" && attr.Name.Local != "" { + prefix = attr.Name.Local + if attr.Value == "" { + // Technically, an empty XML namespace is allowed for an attribute. + // From http://www.w3.org/TR/xml-names11/#scoping-defaulting: + // + // The attribute value in a namespace declaration for a prefix may be + // empty. This has the effect, within the scope of the declaration, of removing + // any association of the prefix with a namespace name. + // + // However our namespace prefixes here are used only as hints. There's + // no need to respect the removal of a namespace prefix, so we ignore it. + return nil + } + } else { + // Ignore: it's not a namespace definition + return nil + } + if prefix == "" { + if attr.Value == p.defaultNS { + // No need for redefinition. + return nil + } + if attr.Value != "" && ignoreNonEmptyDefault { + // We have an xmlns="..." value but + // it can't define a name space in this context, + // probably because the element has an empty + // name space. In this case, we just ignore + // the name space declaration. + return nil + } + } else if _, ok := p.attrPrefix[attr.Value]; ok { + // There's already a prefix for the given name space, + // so use that. This prevents us from + // having two prefixes for the same name space + // so attrNS and attrPrefix can remain bijective. + return nil + } + p.pushPrefix(prefix, attr.Value) + return nil +} + +// createNSPrefix creates a name space prefix attribute +// to use for the given name space, defining a new prefix +// if necessary. +// If isAttr is true, the prefix is to be created for an attribute +// prefix, which means that the default name space cannot +// be used. +func (p *printer) createNSPrefix(url string, isAttr bool) { + if _, ok := p.attrPrefix[url]; ok { + // We already have a prefix for the given URL. + return + } + switch { + case !isAttr && url == p.defaultNS: + // We can use the default name space. + return + case url == "": + // The only way we can encode names in the empty + // name space is by using the default name space, + // so we must use that. + if p.defaultNS != "" { + // The default namespace is non-empty, so we + // need to set it to empty. + p.pushPrefix("", "") + } + return + case url == xmlURL: + return + } + // TODO If the URL is an existing prefix, we could + // use it as is. That would enable the + // marshaling of elements that had been unmarshaled + // and with a name space prefix that was not found. + // although technically it would be incorrect. + + // Pick a name. We try to use the final element of the path + // but fall back to _. + prefix := strings.TrimRight(url, "/") + if i := strings.LastIndex(prefix, "/"); i >= 0 { + prefix = prefix[i+1:] + } + if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") { + prefix = "_" + } + if strings.HasPrefix(prefix, "xml") { + // xmlanything is reserved. + prefix = "_" + prefix + } + if p.attrNS[prefix] != "" { + // Name is taken. Find a better one. + for p.seq++; ; p.seq++ { + if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" { + prefix = id + break + } + } + } + + p.pushPrefix(prefix, url) +} + +// writeNamespaces writes xmlns attributes for all the +// namespace prefixes that have been defined in +// the current element. +func (p *printer) writeNamespaces() { + for i := len(p.prefixes) - 1; i >= 0; i-- { + prefix := p.prefixes[i] + if prefix.mark { + return + } + p.WriteString(" ") + if prefix.prefix == "" { + // Default name space. + p.WriteString(`xmlns="`) + } else { + p.WriteString("xmlns:") + p.WriteString(prefix.prefix) + p.WriteString(`="`) + } + EscapeText(p, []byte(p.nsForPrefix(prefix.prefix))) + p.WriteString(`"`) + } +} + +// pushPrefix pushes a new prefix on the prefix stack +// without checking to see if it is already defined. +func (p *printer) pushPrefix(prefix, url string) { + p.prefixes = append(p.prefixes, printerPrefix{ + prefix: prefix, + url: p.nsForPrefix(prefix), + }) + p.setAttrPrefix(prefix, url) +} + +// nsForPrefix returns the name space for the given +// prefix. Note that this is not valid for the +// empty attribute prefix, which always has an empty +// name space. +func (p *printer) nsForPrefix(prefix string) string { + if prefix == "" { + return p.defaultNS + } + return p.attrNS[prefix] +} + +// markPrefix marks the start of an element on the prefix +// stack. +func (p *printer) markPrefix() { + p.prefixes = append(p.prefixes, printerPrefix{ + mark: true, + }) +} + +// popPrefix pops all defined prefixes for the current +// element. +func (p *printer) popPrefix() { + for len(p.prefixes) > 0 { + prefix := p.prefixes[len(p.prefixes)-1] + p.prefixes = p.prefixes[:len(p.prefixes)-1] + if prefix.mark { + break + } + p.setAttrPrefix(prefix.prefix, prefix.url) + } +} + +// setAttrPrefix sets an attribute name space prefix. +// If url is empty, the attribute is removed. +// If prefix is empty, the default name space is set. +func (p *printer) setAttrPrefix(prefix, url string) { + if prefix == "" { + p.defaultNS = url + return + } + if url == "" { + delete(p.attrPrefix, p.attrNS[prefix]) + delete(p.attrNS, prefix) + return + } + if p.attrPrefix == nil { + // Need to define a new name space. + p.attrPrefix = make(map[string]string) + p.attrNS = make(map[string]string) + } + // Remove any old prefix value. This is OK because we maintain a + // strict one-to-one mapping between prefix and URL (see + // defineNS) + delete(p.attrPrefix, p.attrNS[prefix]) + p.attrPrefix[url] = prefix + p.attrNS[prefix] = url +} + +var ( + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() +) + +// marshalValue writes one or more XML elements representing val. +// If val was obtained from a struct field, finfo must have its details. +func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplate *StartElement) error { + if startTemplate != nil && startTemplate.Name.Local == "" { + return fmt.Errorf("xml: EncodeElement of StartElement with missing name") + } + + if !val.IsValid() { + return nil + } + if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { + return nil + } + + // Drill into interfaces and pointers. + // This can turn into an infinite loop given a cyclic chain, + // but it matches the Go 1 behavior. + for val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + val = val.Elem() + } + + kind := val.Kind() + typ := val.Type() + + // Check for marshaler. + if val.CanInterface() && typ.Implements(marshalerType) { + return p.marshalInterface(val.Interface().(Marshaler), p.defaultStart(typ, finfo, startTemplate)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(marshalerType) { + return p.marshalInterface(pv.Interface().(Marshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) + } + } + + // Check for text marshaler. + if val.CanInterface() && typ.Implements(textMarshalerType) { + return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), p.defaultStart(typ, finfo, startTemplate)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) + } + } + + // Slices and arrays iterate over the elements. They do not have an enclosing tag. + if (kind == reflect.Slice || kind == reflect.Array) && typ.Elem().Kind() != reflect.Uint8 { + for i, n := 0, val.Len(); i < n; i++ { + if err := p.marshalValue(val.Index(i), finfo, startTemplate); err != nil { + return err + } + } + return nil + } + + tinfo, err := getTypeInfo(typ) + if err != nil { + return err + } + + // Create start element. + // Precedence for the XML element name is: + // 0. startTemplate + // 1. XMLName field in underlying struct; + // 2. field name/tag in the struct field; and + // 3. type name + var start StartElement + + // explicitNS records whether the element's name space has been + // explicitly set (for example an XMLName field). + explicitNS := false + + if startTemplate != nil { + start.Name = startTemplate.Name + explicitNS = true + start.Attr = append(start.Attr, startTemplate.Attr...) + } else if tinfo.xmlname != nil { + xmlname := tinfo.xmlname + if xmlname.name != "" { + start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name + } else if v, ok := xmlname.value(val).Interface().(Name); ok && v.Local != "" { + start.Name = v + } + explicitNS = true + } + if start.Name.Local == "" && finfo != nil { + start.Name.Local = finfo.name + if finfo.xmlns != "" { + start.Name.Space = finfo.xmlns + explicitNS = true + } + } + if start.Name.Local == "" { + name := typ.Name() + if name == "" { + return &UnsupportedTypeError{typ} + } + start.Name.Local = name + } + + // defaultNS records the default name space as set by a xmlns="..." + // attribute. We don't set p.defaultNS because we want to let + // the attribute writing code (in p.defineNS) be solely responsible + // for maintaining that. + defaultNS := p.defaultNS + + // Attributes + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr == 0 { + continue + } + attr, err := p.fieldAttr(finfo, val) + if err != nil { + return err + } + if attr.Name.Local == "" { + continue + } + start.Attr = append(start.Attr, attr) + if attr.Name.Space == "" && attr.Name.Local == "xmlns" { + defaultNS = attr.Value + } + } + if !explicitNS { + // Historic behavior: elements use the default name space + // they are contained in by default. + start.Name.Space = defaultNS + } + // Historic behaviour: an element that's in a namespace sets + // the default namespace for all elements contained within it. + start.setDefaultNamespace() + + if err := p.writeStart(&start); err != nil { + return err + } + + if val.Kind() == reflect.Struct { + err = p.marshalStruct(tinfo, val) + } else { + s, b, err1 := p.marshalSimple(typ, val) + if err1 != nil { + err = err1 + } else if b != nil { + EscapeText(p, b) + } else { + p.EscapeString(s) + } + } + if err != nil { + return err + } + + if err := p.writeEnd(start.Name); err != nil { + return err + } + + return p.cachedWriteError() +} + +// fieldAttr returns the attribute of the given field. +// If the returned attribute has an empty Name.Local, +// it should not be used. +// The given value holds the value containing the field. +func (p *printer) fieldAttr(finfo *fieldInfo, val reflect.Value) (Attr, error) { + fv := finfo.value(val) + name := Name{Space: finfo.xmlns, Local: finfo.name} + if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { + return Attr{}, nil + } + if fv.Kind() == reflect.Interface && fv.IsNil() { + return Attr{}, nil + } + if fv.CanInterface() && fv.Type().Implements(marshalerAttrType) { + attr, err := fv.Interface().(MarshalerAttr).MarshalXMLAttr(name) + return attr, err + } + if fv.CanAddr() { + pv := fv.Addr() + if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) { + attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name) + return attr, err + } + } + if fv.CanInterface() && fv.Type().Implements(textMarshalerType) { + text, err := fv.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return Attr{}, err + } + return Attr{name, string(text)}, nil + } + if fv.CanAddr() { + pv := fv.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + text, err := pv.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return Attr{}, err + } + return Attr{name, string(text)}, nil + } + } + // Dereference or skip nil pointer, interface values. + switch fv.Kind() { + case reflect.Ptr, reflect.Interface: + if fv.IsNil() { + return Attr{}, nil + } + fv = fv.Elem() + } + s, b, err := p.marshalSimple(fv.Type(), fv) + if err != nil { + return Attr{}, err + } + if b != nil { + s = string(b) + } + return Attr{name, s}, nil +} + +// defaultStart returns the default start element to use, +// given the reflect type, field info, and start template. +func (p *printer) defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) StartElement { + var start StartElement + // Precedence for the XML element name is as above, + // except that we do not look inside structs for the first field. + if startTemplate != nil { + start.Name = startTemplate.Name + start.Attr = append(start.Attr, startTemplate.Attr...) + } else if finfo != nil && finfo.name != "" { + start.Name.Local = finfo.name + start.Name.Space = finfo.xmlns + } else if typ.Name() != "" { + start.Name.Local = typ.Name() + } else { + // Must be a pointer to a named type, + // since it has the Marshaler methods. + start.Name.Local = typ.Elem().Name() + } + // Historic behaviour: elements use the name space of + // the element they are contained in by default. + if start.Name.Space == "" { + start.Name.Space = p.defaultNS + } + start.setDefaultNamespace() + return start +} + +// marshalInterface marshals a Marshaler interface value. +func (p *printer) marshalInterface(val Marshaler, start StartElement) error { + // Push a marker onto the tag stack so that MarshalXML + // cannot close the XML tags that it did not open. + p.tags = append(p.tags, Name{}) + n := len(p.tags) + + err := val.MarshalXML(p.encoder, start) + if err != nil { + return err + } + + // Make sure MarshalXML closed all its tags. p.tags[n-1] is the mark. + if len(p.tags) > n { + return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.tags[len(p.tags)-1].Local) + } + p.tags = p.tags[:n-1] + return nil +} + +// marshalTextInterface marshals a TextMarshaler interface value. +func (p *printer) marshalTextInterface(val encoding.TextMarshaler, start StartElement) error { + if err := p.writeStart(&start); err != nil { + return err + } + text, err := val.MarshalText() + if err != nil { + return err + } + EscapeText(p, text) + return p.writeEnd(start.Name) +} + +// writeStart writes the given start element. +func (p *printer) writeStart(start *StartElement) error { + if start.Name.Local == "" { + return fmt.Errorf("xml: start tag with no name") + } + + p.tags = append(p.tags, start.Name) + p.markPrefix() + // Define any name spaces explicitly declared in the attributes. + // We do this as a separate pass so that explicitly declared prefixes + // will take precedence over implicitly declared prefixes + // regardless of the order of the attributes. + ignoreNonEmptyDefault := start.Name.Space == "" + for _, attr := range start.Attr { + if err := p.defineNS(attr, ignoreNonEmptyDefault); err != nil { + return err + } + } + // Define any new name spaces implied by the attributes. + for _, attr := range start.Attr { + name := attr.Name + // From http://www.w3.org/TR/xml-names11/#defaulting + // "Default namespace declarations do not apply directly + // to attribute names; the interpretation of unprefixed + // attributes is determined by the element on which they + // appear." + // This means we don't need to create a new namespace + // when an attribute name space is empty. + if name.Space != "" && !name.isNamespace() { + p.createNSPrefix(name.Space, true) + } + } + p.createNSPrefix(start.Name.Space, false) + + p.writeIndent(1) + p.WriteByte('<') + p.writeName(start.Name, false) + p.writeNamespaces() + for _, attr := range start.Attr { + name := attr.Name + if name.Local == "" || name.isNamespace() { + // Namespaces have already been written by writeNamespaces above. + continue + } + p.WriteByte(' ') + p.writeName(name, true) + p.WriteString(`="`) + p.EscapeString(attr.Value) + p.WriteByte('"') + } + p.WriteByte('>') + return nil +} + +// writeName writes the given name. It assumes +// that p.createNSPrefix(name) has already been called. +func (p *printer) writeName(name Name, isAttr bool) { + if prefix := p.prefixForNS(name.Space, isAttr); prefix != "" { + p.WriteString(prefix) + p.WriteByte(':') + } + p.WriteString(name.Local) +} + +func (p *printer) writeEnd(name Name) error { + if name.Local == "" { + return fmt.Errorf("xml: end tag with no name") + } + if len(p.tags) == 0 || p.tags[len(p.tags)-1].Local == "" { + return fmt.Errorf("xml: end tag without start tag", name.Local) + } + if top := p.tags[len(p.tags)-1]; top != name { + if top.Local != name.Local { + return fmt.Errorf("xml: end tag does not match start tag <%s>", name.Local, top.Local) + } + return fmt.Errorf("xml: end tag in namespace %s does not match start tag <%s> in namespace %s", name.Local, name.Space, top.Local, top.Space) + } + p.tags = p.tags[:len(p.tags)-1] + + p.writeIndent(-1) + p.WriteByte('<') + p.WriteByte('/') + p.writeName(name, false) + p.WriteByte('>') + p.popPrefix() + return nil +} + +func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []byte, error) { + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(val.Int(), 10), nil, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(val.Uint(), 10), nil, nil + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()), nil, nil + case reflect.String: + return val.String(), nil, nil + case reflect.Bool: + return strconv.FormatBool(val.Bool()), nil, nil + case reflect.Array: + if typ.Elem().Kind() != reflect.Uint8 { + break + } + // [...]byte + var bytes []byte + if val.CanAddr() { + bytes = val.Slice(0, val.Len()).Bytes() + } else { + bytes = make([]byte, val.Len()) + reflect.Copy(reflect.ValueOf(bytes), val) + } + return "", bytes, nil + case reflect.Slice: + if typ.Elem().Kind() != reflect.Uint8 { + break + } + // []byte + return "", val.Bytes(), nil + } + return "", nil, &UnsupportedTypeError{typ} +} + +var ddBytes = []byte("--") + +func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { + s := parentStack{p: p} + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr != 0 { + continue + } + vf := finfo.value(val) + + // Dereference or skip nil pointer, interface values. + switch vf.Kind() { + case reflect.Ptr, reflect.Interface: + if !vf.IsNil() { + vf = vf.Elem() + } + } + + switch finfo.flags & fMode { + case fCharData: + if err := s.setParents(&noField, reflect.Value{}); err != nil { + return err + } + if vf.CanInterface() && vf.Type().Implements(textMarshalerType) { + data, err := vf.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + Escape(p, data) + continue + } + if vf.CanAddr() { + pv := vf.Addr() + if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { + data, err := pv.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return err + } + Escape(p, data) + continue + } + } + var scratch [64]byte + switch vf.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + Escape(p, strconv.AppendInt(scratch[:0], vf.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + Escape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10)) + case reflect.Float32, reflect.Float64: + Escape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits())) + case reflect.Bool: + Escape(p, strconv.AppendBool(scratch[:0], vf.Bool())) + case reflect.String: + if err := EscapeText(p, []byte(vf.String())); err != nil { + return err + } + case reflect.Slice: + if elem, ok := vf.Interface().([]byte); ok { + if err := EscapeText(p, elem); err != nil { + return err + } + } + } + continue + + case fComment: + if err := s.setParents(&noField, reflect.Value{}); err != nil { + return err + } + k := vf.Kind() + if !(k == reflect.String || k == reflect.Slice && vf.Type().Elem().Kind() == reflect.Uint8) { + return fmt.Errorf("xml: bad type for comment field of %s", val.Type()) + } + if vf.Len() == 0 { + continue + } + p.writeIndent(0) + p.WriteString("" is invalid grammar. Make it "- -->" + p.WriteByte(' ') + } + p.WriteString("-->") + continue + + case fInnerXml: + iface := vf.Interface() + switch raw := iface.(type) { + case []byte: + p.Write(raw) + continue + case string: + p.WriteString(raw) + continue + } + + case fElement, fElement | fAny: + if err := s.setParents(finfo, vf); err != nil { + return err + } + } + if err := p.marshalValue(vf, finfo, nil); err != nil { + return err + } + } + if err := s.setParents(&noField, reflect.Value{}); err != nil { + return err + } + return p.cachedWriteError() +} + +var noField fieldInfo + +// return the bufio Writer's cached write error +func (p *printer) cachedWriteError() error { + _, err := p.Write(nil) + return err +} + +func (p *printer) writeIndent(depthDelta int) { + if len(p.prefix) == 0 && len(p.indent) == 0 { + return + } + if depthDelta < 0 { + p.depth-- + if p.indentedIn { + p.indentedIn = false + return + } + p.indentedIn = false + } + if p.putNewline { + p.WriteByte('\n') + } else { + p.putNewline = true + } + if len(p.prefix) > 0 { + p.WriteString(p.prefix) + } + if len(p.indent) > 0 { + for i := 0; i < p.depth; i++ { + p.WriteString(p.indent) + } + } + if depthDelta > 0 { + p.depth++ + p.indentedIn = true + } +} + +type parentStack struct { + p *printer + xmlns string + parents []string +} + +// setParents sets the stack of current parents to those found in finfo. +// It only writes the start elements if vf holds a non-nil value. +// If finfo is &noField, it pops all elements. +func (s *parentStack) setParents(finfo *fieldInfo, vf reflect.Value) error { + xmlns := s.p.defaultNS + if finfo.xmlns != "" { + xmlns = finfo.xmlns + } + commonParents := 0 + if xmlns == s.xmlns { + for ; commonParents < len(finfo.parents) && commonParents < len(s.parents); commonParents++ { + if finfo.parents[commonParents] != s.parents[commonParents] { + break + } + } + } + // Pop off any parents that aren't in common with the previous field. + for i := len(s.parents) - 1; i >= commonParents; i-- { + if err := s.p.writeEnd(Name{ + Space: s.xmlns, + Local: s.parents[i], + }); err != nil { + return err + } + } + s.parents = finfo.parents + s.xmlns = xmlns + if commonParents >= len(s.parents) { + // No new elements to push. + return nil + } + if (vf.Kind() == reflect.Ptr || vf.Kind() == reflect.Interface) && vf.IsNil() { + // The element is nil, so no need for the start elements. + s.parents = s.parents[:commonParents] + return nil + } + // Push any new parents required. + for _, name := range s.parents[commonParents:] { + start := &StartElement{ + Name: Name{ + Space: s.xmlns, + Local: name, + }, + } + // Set the default name space for parent elements + // to match what we do with other elements. + if s.xmlns != s.p.defaultNS { + start.setDefaultNamespace() + } + if err := s.p.writeStart(start); err != nil { + return err + } + } + return nil +} + +// A MarshalXMLError is returned when Marshal encounters a type +// that cannot be converted into XML. +type UnsupportedTypeError struct { + Type reflect.Type +} + +func (e *UnsupportedTypeError) Error() string { + return "xml: unsupported type: " + e.Type.String() +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} diff --git a/vendor/golang.org/x/net/webdav/internal/xml/read.go b/vendor/golang.org/x/net/webdav/internal/xml/read.go new file mode 100644 index 0000000..4089056 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/internal/xml/read.go @@ -0,0 +1,692 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "bytes" + "encoding" + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// BUG(rsc): Mapping between XML elements and data structures is inherently flawed: +// an XML element is an order-dependent collection of anonymous +// values, while a data structure is an order-independent collection +// of named values. +// See package json for a textual representation more suitable +// to data structures. + +// Unmarshal parses the XML-encoded data and stores the result in +// the value pointed to by v, which must be an arbitrary struct, +// slice, or string. Well-formed data that does not fit into v is +// discarded. +// +// Because Unmarshal uses the reflect package, it can only assign +// to exported (upper case) fields. Unmarshal uses a case-sensitive +// comparison to match XML element names to tag values and struct +// field names. +// +// Unmarshal maps an XML element to a struct using the following rules. +// In the rules, the tag of a field refers to the value associated with the +// key 'xml' in the struct field's tag (see the example above). +// +// * If the struct has a field of type []byte or string with tag +// ",innerxml", Unmarshal accumulates the raw XML nested inside the +// element in that field. The rest of the rules still apply. +// +// * If the struct has a field named XMLName of type xml.Name, +// Unmarshal records the element name in that field. +// +// * If the XMLName field has an associated tag of the form +// "name" or "namespace-URL name", the XML element must have +// the given name (and, optionally, name space) or else Unmarshal +// returns an error. +// +// * If the XML element has an attribute whose name matches a +// struct field name with an associated tag containing ",attr" or +// the explicit name in a struct field tag of the form "name,attr", +// Unmarshal records the attribute value in that field. +// +// * If the XML element contains character data, that data is +// accumulated in the first struct field that has tag ",chardata". +// The struct field may have type []byte or string. +// If there is no such field, the character data is discarded. +// +// * If the XML element contains comments, they are accumulated in +// the first struct field that has tag ",comment". The struct +// field may have type []byte or string. If there is no such +// field, the comments are discarded. +// +// * If the XML element contains a sub-element whose name matches +// the prefix of a tag formatted as "a" or "a>b>c", unmarshal +// will descend into the XML structure looking for elements with the +// given names, and will map the innermost elements to that struct +// field. A tag starting with ">" is equivalent to one starting +// with the field name followed by ">". +// +// * If the XML element contains a sub-element whose name matches +// a struct field's XMLName tag and the struct field has no +// explicit name tag as per the previous rule, unmarshal maps +// the sub-element to that struct field. +// +// * If the XML element contains a sub-element whose name matches a +// field without any mode flags (",attr", ",chardata", etc), Unmarshal +// maps the sub-element to that struct field. +// +// * If the XML element contains a sub-element that hasn't matched any +// of the above rules and the struct has a field with tag ",any", +// unmarshal maps the sub-element to that struct field. +// +// * An anonymous struct field is handled as if the fields of its +// value were part of the outer struct. +// +// * A struct field with tag "-" is never unmarshalled into. +// +// Unmarshal maps an XML element to a string or []byte by saving the +// concatenation of that element's character data in the string or +// []byte. The saved []byte is never nil. +// +// Unmarshal maps an attribute value to a string or []byte by saving +// the value in the string or slice. +// +// Unmarshal maps an XML element to a slice by extending the length of +// the slice and mapping the element to the newly created value. +// +// Unmarshal maps an XML element or attribute value to a bool by +// setting it to the boolean value represented by the string. +// +// Unmarshal maps an XML element or attribute value to an integer or +// floating-point field by setting the field to the result of +// interpreting the string value in decimal. There is no check for +// overflow. +// +// Unmarshal maps an XML element to an xml.Name by recording the +// element name. +// +// Unmarshal maps an XML element to a pointer by setting the pointer +// to a freshly allocated value and then mapping the element to that value. +// +func Unmarshal(data []byte, v interface{}) error { + return NewDecoder(bytes.NewReader(data)).Decode(v) +} + +// Decode works like xml.Unmarshal, except it reads the decoder +// stream to find the start element. +func (d *Decoder) Decode(v interface{}) error { + return d.DecodeElement(v, nil) +} + +// DecodeElement works like xml.Unmarshal except that it takes +// a pointer to the start XML element to decode into v. +// It is useful when a client reads some raw XML tokens itself +// but also wants to defer to Unmarshal for some elements. +func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error { + val := reflect.ValueOf(v) + if val.Kind() != reflect.Ptr { + return errors.New("non-pointer passed to Unmarshal") + } + return d.unmarshal(val.Elem(), start) +} + +// An UnmarshalError represents an error in the unmarshalling process. +type UnmarshalError string + +func (e UnmarshalError) Error() string { return string(e) } + +// Unmarshaler is the interface implemented by objects that can unmarshal +// an XML element description of themselves. +// +// UnmarshalXML decodes a single XML element +// beginning with the given start element. +// If it returns an error, the outer call to Unmarshal stops and +// returns that error. +// UnmarshalXML must consume exactly one XML element. +// One common implementation strategy is to unmarshal into +// a separate value with a layout matching the expected XML +// using d.DecodeElement, and then to copy the data from +// that value into the receiver. +// Another common strategy is to use d.Token to process the +// XML object one token at a time. +// UnmarshalXML may not use d.RawToken. +type Unmarshaler interface { + UnmarshalXML(d *Decoder, start StartElement) error +} + +// UnmarshalerAttr is the interface implemented by objects that can unmarshal +// an XML attribute description of themselves. +// +// UnmarshalXMLAttr decodes a single XML attribute. +// If it returns an error, the outer call to Unmarshal stops and +// returns that error. +// UnmarshalXMLAttr is used only for struct fields with the +// "attr" option in the field tag. +type UnmarshalerAttr interface { + UnmarshalXMLAttr(attr Attr) error +} + +// receiverType returns the receiver type to use in an expression like "%s.MethodName". +func receiverType(val interface{}) string { + t := reflect.TypeOf(val) + if t.Name() != "" { + return t.String() + } + return "(" + t.String() + ")" +} + +// unmarshalInterface unmarshals a single XML element into val. +// start is the opening tag of the element. +func (p *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error { + // Record that decoder must stop at end tag corresponding to start. + p.pushEOF() + + p.unmarshalDepth++ + err := val.UnmarshalXML(p, *start) + p.unmarshalDepth-- + if err != nil { + p.popEOF() + return err + } + + if !p.popEOF() { + return fmt.Errorf("xml: %s.UnmarshalXML did not consume entire <%s> element", receiverType(val), start.Name.Local) + } + + return nil +} + +// unmarshalTextInterface unmarshals a single XML element into val. +// The chardata contained in the element (but not its children) +// is passed to the text unmarshaler. +func (p *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler, start *StartElement) error { + var buf []byte + depth := 1 + for depth > 0 { + t, err := p.Token() + if err != nil { + return err + } + switch t := t.(type) { + case CharData: + if depth == 1 { + buf = append(buf, t...) + } + case StartElement: + depth++ + case EndElement: + depth-- + } + } + return val.UnmarshalText(buf) +} + +// unmarshalAttr unmarshals a single XML attribute into val. +func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error { + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + + if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) { + return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) + } + } + + // Not an UnmarshalerAttr; try encoding.TextUnmarshaler. + if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) + } + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) + } + } + + copyValue(val, []byte(attr.Value)) + return nil +} + +var ( + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + unmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +// Unmarshal a single XML element into val. +func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error { + // Find start element if we need it. + if start == nil { + for { + tok, err := p.Token() + if err != nil { + return err + } + if t, ok := tok.(StartElement); ok { + start = &t + break + } + } + } + + // Load value from interface, but only if the result will be + // usefully addressable. + if val.Kind() == reflect.Interface && !val.IsNil() { + e := val.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() { + val = e + } + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + + if val.CanInterface() && val.Type().Implements(unmarshalerType) { + // This is an unmarshaler with a non-pointer receiver, + // so it's likely to be incorrect, but we do what we're told. + return p.unmarshalInterface(val.Interface().(Unmarshaler), start) + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(unmarshalerType) { + return p.unmarshalInterface(pv.Interface().(Unmarshaler), start) + } + } + + if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { + return p.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler), start) + } + + if val.CanAddr() { + pv := val.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + return p.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler), start) + } + } + + var ( + data []byte + saveData reflect.Value + comment []byte + saveComment reflect.Value + saveXML reflect.Value + saveXMLIndex int + saveXMLData []byte + saveAny reflect.Value + sv reflect.Value + tinfo *typeInfo + err error + ) + + switch v := val; v.Kind() { + default: + return errors.New("unknown type " + v.Type().String()) + + case reflect.Interface: + // TODO: For now, simply ignore the field. In the near + // future we may choose to unmarshal the start + // element on it, if not nil. + return p.Skip() + + case reflect.Slice: + typ := v.Type() + if typ.Elem().Kind() == reflect.Uint8 { + // []byte + saveData = v + break + } + + // Slice of element values. + // Grow slice. + n := v.Len() + if n >= v.Cap() { + ncap := 2 * n + if ncap < 4 { + ncap = 4 + } + new := reflect.MakeSlice(typ, n, ncap) + reflect.Copy(new, v) + v.Set(new) + } + v.SetLen(n + 1) + + // Recur to read element into slice. + if err := p.unmarshal(v.Index(n), start); err != nil { + v.SetLen(n) + return err + } + return nil + + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.String: + saveData = v + + case reflect.Struct: + typ := v.Type() + if typ == nameType { + v.Set(reflect.ValueOf(start.Name)) + break + } + + sv = v + tinfo, err = getTypeInfo(typ) + if err != nil { + return err + } + + // Validate and assign element name. + if tinfo.xmlname != nil { + finfo := tinfo.xmlname + if finfo.name != "" && finfo.name != start.Name.Local { + return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">") + } + if finfo.xmlns != "" && finfo.xmlns != start.Name.Space { + e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have " + if start.Name.Space == "" { + e += "no name space" + } else { + e += start.Name.Space + } + return UnmarshalError(e) + } + fv := finfo.value(sv) + if _, ok := fv.Interface().(Name); ok { + fv.Set(reflect.ValueOf(start.Name)) + } + } + + // Assign attributes. + // Also, determine whether we need to save character data or comments. + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + switch finfo.flags & fMode { + case fAttr: + strv := finfo.value(sv) + // Look for attribute. + for _, a := range start.Attr { + if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) { + if err := p.unmarshalAttr(strv, a); err != nil { + return err + } + break + } + } + + case fCharData: + if !saveData.IsValid() { + saveData = finfo.value(sv) + } + + case fComment: + if !saveComment.IsValid() { + saveComment = finfo.value(sv) + } + + case fAny, fAny | fElement: + if !saveAny.IsValid() { + saveAny = finfo.value(sv) + } + + case fInnerXml: + if !saveXML.IsValid() { + saveXML = finfo.value(sv) + if p.saved == nil { + saveXMLIndex = 0 + p.saved = new(bytes.Buffer) + } else { + saveXMLIndex = p.savedOffset() + } + } + } + } + } + + // Find end element. + // Process sub-elements along the way. +Loop: + for { + var savedOffset int + if saveXML.IsValid() { + savedOffset = p.savedOffset() + } + tok, err := p.Token() + if err != nil { + return err + } + switch t := tok.(type) { + case StartElement: + consumed := false + if sv.IsValid() { + consumed, err = p.unmarshalPath(tinfo, sv, nil, &t) + if err != nil { + return err + } + if !consumed && saveAny.IsValid() { + consumed = true + if err := p.unmarshal(saveAny, &t); err != nil { + return err + } + } + } + if !consumed { + if err := p.Skip(); err != nil { + return err + } + } + + case EndElement: + if saveXML.IsValid() { + saveXMLData = p.saved.Bytes()[saveXMLIndex:savedOffset] + if saveXMLIndex == 0 { + p.saved = nil + } + } + break Loop + + case CharData: + if saveData.IsValid() { + data = append(data, t...) + } + + case Comment: + if saveComment.IsValid() { + comment = append(comment, t...) + } + } + } + + if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) { + if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return err + } + saveData = reflect.Value{} + } + + if saveData.IsValid() && saveData.CanAddr() { + pv := saveData.Addr() + if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { + if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return err + } + saveData = reflect.Value{} + } + } + + if err := copyValue(saveData, data); err != nil { + return err + } + + switch t := saveComment; t.Kind() { + case reflect.String: + t.SetString(string(comment)) + case reflect.Slice: + t.Set(reflect.ValueOf(comment)) + } + + switch t := saveXML; t.Kind() { + case reflect.String: + t.SetString(string(saveXMLData)) + case reflect.Slice: + t.Set(reflect.ValueOf(saveXMLData)) + } + + return nil +} + +func copyValue(dst reflect.Value, src []byte) (err error) { + dst0 := dst + + if dst.Kind() == reflect.Ptr { + if dst.IsNil() { + dst.Set(reflect.New(dst.Type().Elem())) + } + dst = dst.Elem() + } + + // Save accumulated data. + switch dst.Kind() { + case reflect.Invalid: + // Probably a comment. + default: + return errors.New("cannot unmarshal into " + dst0.Type().String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits()) + if err != nil { + return err + } + dst.SetInt(itmp) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits()) + if err != nil { + return err + } + dst.SetUint(utmp) + case reflect.Float32, reflect.Float64: + ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits()) + if err != nil { + return err + } + dst.SetFloat(ftmp) + case reflect.Bool: + value, err := strconv.ParseBool(strings.TrimSpace(string(src))) + if err != nil { + return err + } + dst.SetBool(value) + case reflect.String: + dst.SetString(string(src)) + case reflect.Slice: + if len(src) == 0 { + // non-nil to flag presence + src = []byte{} + } + dst.SetBytes(src) + } + return nil +} + +// unmarshalPath walks down an XML structure looking for wanted +// paths, and calls unmarshal on them. +// The consumed result tells whether XML elements have been consumed +// from the Decoder until start's matching end element, or if it's +// still untouched because start is uninteresting for sv's fields. +func (p *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) { + recurse := false +Loop: + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) || finfo.xmlns != "" && finfo.xmlns != start.Name.Space { + continue + } + for j := range parents { + if parents[j] != finfo.parents[j] { + continue Loop + } + } + if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local { + // It's a perfect match, unmarshal the field. + return true, p.unmarshal(finfo.value(sv), start) + } + if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local { + // It's a prefix for the field. Break and recurse + // since it's not ok for one field path to be itself + // the prefix for another field path. + recurse = true + + // We can reuse the same slice as long as we + // don't try to append to it. + parents = finfo.parents[:len(parents)+1] + break + } + } + if !recurse { + // We have no business with this element. + return false, nil + } + // The element is not a perfect match for any field, but one + // or more fields have the path to this element as a parent + // prefix. Recurse and attempt to match these. + for { + var tok Token + tok, err = p.Token() + if err != nil { + return true, err + } + switch t := tok.(type) { + case StartElement: + consumed2, err := p.unmarshalPath(tinfo, sv, parents, &t) + if err != nil { + return true, err + } + if !consumed2 { + if err := p.Skip(); err != nil { + return true, err + } + } + case EndElement: + return true, nil + } + } +} + +// Skip reads tokens until it has consumed the end element +// matching the most recent start element already consumed. +// It recurs if it encounters a start element, so it can be used to +// skip nested structures. +// It returns nil if it finds an end element matching the start +// element; otherwise it returns an error describing the problem. +func (d *Decoder) Skip() error { + for { + tok, err := d.Token() + if err != nil { + return err + } + switch tok.(type) { + case StartElement: + if err := d.Skip(); err != nil { + return err + } + case EndElement: + return nil + } + } +} diff --git a/vendor/golang.org/x/net/webdav/internal/xml/typeinfo.go b/vendor/golang.org/x/net/webdav/internal/xml/typeinfo.go new file mode 100644 index 0000000..fdde288 --- /dev/null +++ b/vendor/golang.org/x/net/webdav/internal/xml/typeinfo.go @@ -0,0 +1,371 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "fmt" + "reflect" + "strings" + "sync" +) + +// typeInfo holds details for the xml representation of a type. +type typeInfo struct { + xmlname *fieldInfo + fields []fieldInfo +} + +// fieldInfo holds details for the xml representation of a single field. +type fieldInfo struct { + idx []int + name string + xmlns string + flags fieldFlags + parents []string +} + +type fieldFlags int + +const ( + fElement fieldFlags = 1 << iota + fAttr + fCharData + fInnerXml + fComment + fAny + + fOmitEmpty + + fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny +) + +var tinfoMap = make(map[reflect.Type]*typeInfo) +var tinfoLock sync.RWMutex + +var nameType = reflect.TypeOf(Name{}) + +// getTypeInfo returns the typeInfo structure with details necessary +// for marshalling and unmarshalling typ. +func getTypeInfo(typ reflect.Type) (*typeInfo, error) { + tinfoLock.RLock() + tinfo, ok := tinfoMap[typ] + tinfoLock.RUnlock() + if ok { + return tinfo, nil + } + tinfo = &typeInfo{} + if typ.Kind() == reflect.Struct && typ != nameType { + n := typ.NumField() + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.PkgPath != "" || f.Tag.Get("xml") == "-" { + continue // Private field + } + + // For embedded structs, embed its fields. + if f.Anonymous { + t := f.Type + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Struct { + inner, err := getTypeInfo(t) + if err != nil { + return nil, err + } + if tinfo.xmlname == nil { + tinfo.xmlname = inner.xmlname + } + for _, finfo := range inner.fields { + finfo.idx = append([]int{i}, finfo.idx...) + if err := addFieldInfo(typ, tinfo, &finfo); err != nil { + return nil, err + } + } + continue + } + } + + finfo, err := structFieldInfo(typ, &f) + if err != nil { + return nil, err + } + + if f.Name == "XMLName" { + tinfo.xmlname = finfo + continue + } + + // Add the field if it doesn't conflict with other fields. + if err := addFieldInfo(typ, tinfo, finfo); err != nil { + return nil, err + } + } + } + tinfoLock.Lock() + tinfoMap[typ] = tinfo + tinfoLock.Unlock() + return tinfo, nil +} + +// structFieldInfo builds and returns a fieldInfo for f. +func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { + finfo := &fieldInfo{idx: f.Index} + + // Split the tag from the xml namespace if necessary. + tag := f.Tag.Get("xml") + if i := strings.Index(tag, " "); i >= 0 { + finfo.xmlns, tag = tag[:i], tag[i+1:] + } + + // Parse flags. + tokens := strings.Split(tag, ",") + if len(tokens) == 1 { + finfo.flags = fElement + } else { + tag = tokens[0] + for _, flag := range tokens[1:] { + switch flag { + case "attr": + finfo.flags |= fAttr + case "chardata": + finfo.flags |= fCharData + case "innerxml": + finfo.flags |= fInnerXml + case "comment": + finfo.flags |= fComment + case "any": + finfo.flags |= fAny + case "omitempty": + finfo.flags |= fOmitEmpty + } + } + + // Validate the flags used. + valid := true + switch mode := finfo.flags & fMode; mode { + case 0: + finfo.flags |= fElement + case fAttr, fCharData, fInnerXml, fComment, fAny: + if f.Name == "XMLName" || tag != "" && mode != fAttr { + valid = false + } + default: + // This will also catch multiple modes in a single field. + valid = false + } + if finfo.flags&fMode == fAny { + finfo.flags |= fElement + } + if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { + valid = false + } + if !valid { + return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", + f.Name, typ, f.Tag.Get("xml")) + } + } + + // Use of xmlns without a name is not allowed. + if finfo.xmlns != "" && tag == "" { + return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", + f.Name, typ, f.Tag.Get("xml")) + } + + if f.Name == "XMLName" { + // The XMLName field records the XML element name. Don't + // process it as usual because its name should default to + // empty rather than to the field name. + finfo.name = tag + return finfo, nil + } + + if tag == "" { + // If the name part of the tag is completely empty, get + // default from XMLName of underlying struct if feasible, + // or field name otherwise. + if xmlname := lookupXMLName(f.Type); xmlname != nil { + finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name + } else { + finfo.name = f.Name + } + return finfo, nil + } + + if finfo.xmlns == "" && finfo.flags&fAttr == 0 { + // If it's an element no namespace specified, get the default + // from the XMLName of enclosing struct if possible. + if xmlname := lookupXMLName(typ); xmlname != nil { + finfo.xmlns = xmlname.xmlns + } + } + + // Prepare field name and parents. + parents := strings.Split(tag, ">") + if parents[0] == "" { + parents[0] = f.Name + } + if parents[len(parents)-1] == "" { + return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) + } + finfo.name = parents[len(parents)-1] + if len(parents) > 1 { + if (finfo.flags & fElement) == 0 { + return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) + } + finfo.parents = parents[:len(parents)-1] + } + + // If the field type has an XMLName field, the names must match + // so that the behavior of both marshalling and unmarshalling + // is straightforward and unambiguous. + if finfo.flags&fElement != 0 { + ftyp := f.Type + xmlname := lookupXMLName(ftyp) + if xmlname != nil && xmlname.name != finfo.name { + return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", + finfo.name, typ, f.Name, xmlname.name, ftyp) + } + } + return finfo, nil +} + +// lookupXMLName returns the fieldInfo for typ's XMLName field +// in case it exists and has a valid xml field tag, otherwise +// it returns nil. +func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() != reflect.Struct { + return nil + } + for i, n := 0, typ.NumField(); i < n; i++ { + f := typ.Field(i) + if f.Name != "XMLName" { + continue + } + finfo, err := structFieldInfo(typ, &f) + if finfo.name != "" && err == nil { + return finfo + } + // Also consider errors as a non-existent field tag + // and let getTypeInfo itself report the error. + break + } + return nil +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +// addFieldInfo adds finfo to tinfo.fields if there are no +// conflicts, or if conflicts arise from previous fields that were +// obtained from deeper embedded structures than finfo. In the latter +// case, the conflicting entries are dropped. +// A conflict occurs when the path (parent + name) to a field is +// itself a prefix of another path, or when two paths match exactly. +// It is okay for field paths to share a common, shorter prefix. +func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { + var conflicts []int +Loop: + // First, figure all conflicts. Most working code will have none. + for i := range tinfo.fields { + oldf := &tinfo.fields[i] + if oldf.flags&fMode != newf.flags&fMode { + continue + } + if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { + continue + } + minl := min(len(newf.parents), len(oldf.parents)) + for p := 0; p < minl; p++ { + if oldf.parents[p] != newf.parents[p] { + continue Loop + } + } + if len(oldf.parents) > len(newf.parents) { + if oldf.parents[len(newf.parents)] == newf.name { + conflicts = append(conflicts, i) + } + } else if len(oldf.parents) < len(newf.parents) { + if newf.parents[len(oldf.parents)] == oldf.name { + conflicts = append(conflicts, i) + } + } else { + if newf.name == oldf.name { + conflicts = append(conflicts, i) + } + } + } + // Without conflicts, add the new field and return. + if conflicts == nil { + tinfo.fields = append(tinfo.fields, *newf) + return nil + } + + // If any conflict is shallower, ignore the new field. + // This matches the Go field resolution on embedding. + for _, i := range conflicts { + if len(tinfo.fields[i].idx) < len(newf.idx) { + return nil + } + } + + // Otherwise, if any of them is at the same depth level, it's an error. + for _, i := range conflicts { + oldf := &tinfo.fields[i] + if len(oldf.idx) == len(newf.idx) { + f1 := typ.FieldByIndex(oldf.idx) + f2 := typ.FieldByIndex(newf.idx) + return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} + } + } + + // Otherwise, the new field is shallower, and thus takes precedence, + // so drop the conflicting fields from tinfo and append the new one. + for c := len(conflicts) - 1; c >= 0; c-- { + i := conflicts[c] + copy(tinfo.fields[i:], tinfo.fields[i+1:]) + tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] + } + tinfo.fields = append(tinfo.fields, *newf) + return nil +} + +// A TagPathError represents an error in the unmarshalling process +// caused by the use of field tags with conflicting paths. +type TagPathError struct { + Struct reflect.Type + Field1, Tag1 string + Field2, Tag2 string +} + +func (e *TagPathError) Error() string { + return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2) +} + +// value returns v's field value corresponding to finfo. +// It's equivalent to v.FieldByIndex(finfo.idx), but initializes +// and dereferences pointers as necessary. +func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { + for i, x := range finfo.idx { + if i > 0 { + t := v.Type() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} diff --git a/vendor/golang.org/x/net/webdav/internal/xml/xml.go b/vendor/golang.org/x/net/webdav/internal/xml/xml.go new file mode 100644 index 0000000..5b79cbe --- /dev/null +++ b/vendor/golang.org/x/net/webdav/internal/xml/xml.go @@ -0,0 +1,1998 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xml implements a simple XML 1.0 parser that +// understands XML name spaces. +package xml + +// References: +// Annotated XML spec: http://www.xml.com/axml/testaxml.htm +// XML name spaces: http://www.w3.org/TR/REC-xml-names/ + +// TODO(rsc): +// Test error handling. + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A SyntaxError represents a syntax error in the XML input stream. +type SyntaxError struct { + Msg string + Line int +} + +func (e *SyntaxError) Error() string { + return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg +} + +// A Name represents an XML name (Local) annotated with a name space +// identifier (Space). In tokens returned by Decoder.Token, the Space +// identifier is given as a canonical URL, not the short prefix used in +// the document being parsed. +// +// As a special case, XML namespace declarations will use the literal +// string "xmlns" for the Space field instead of the fully resolved URL. +// See Encoder.EncodeToken for more information on namespace encoding +// behaviour. +type Name struct { + Space, Local string +} + +// isNamespace reports whether the name is a namespace-defining name. +func (name Name) isNamespace() bool { + return name.Local == "xmlns" || name.Space == "xmlns" +} + +// An Attr represents an attribute in an XML element (Name=Value). +type Attr struct { + Name Name + Value string +} + +// A Token is an interface holding one of the token types: +// StartElement, EndElement, CharData, Comment, ProcInst, or Directive. +type Token interface{} + +// A StartElement represents an XML start element. +type StartElement struct { + Name Name + Attr []Attr +} + +func (e StartElement) Copy() StartElement { + attrs := make([]Attr, len(e.Attr)) + copy(attrs, e.Attr) + e.Attr = attrs + return e +} + +// End returns the corresponding XML end element. +func (e StartElement) End() EndElement { + return EndElement{e.Name} +} + +// setDefaultNamespace sets the namespace of the element +// as the default for all elements contained within it. +func (e *StartElement) setDefaultNamespace() { + if e.Name.Space == "" { + // If there's no namespace on the element, don't + // set the default. Strictly speaking this might be wrong, as + // we can't tell if the element had no namespace set + // or was just using the default namespace. + return + } + // Don't add a default name space if there's already one set. + for _, attr := range e.Attr { + if attr.Name.Space == "" && attr.Name.Local == "xmlns" { + return + } + } + e.Attr = append(e.Attr, Attr{ + Name: Name{ + Local: "xmlns", + }, + Value: e.Name.Space, + }) +} + +// An EndElement represents an XML end element. +type EndElement struct { + Name Name +} + +// A CharData represents XML character data (raw text), +// in which XML escape sequences have been replaced by +// the characters they represent. +type CharData []byte + +func makeCopy(b []byte) []byte { + b1 := make([]byte, len(b)) + copy(b1, b) + return b1 +} + +func (c CharData) Copy() CharData { return CharData(makeCopy(c)) } + +// A Comment represents an XML comment of the form . +// The bytes do not include the comment markers. +type Comment []byte + +func (c Comment) Copy() Comment { return Comment(makeCopy(c)) } + +// A ProcInst represents an XML processing instruction of the form +type ProcInst struct { + Target string + Inst []byte +} + +func (p ProcInst) Copy() ProcInst { + p.Inst = makeCopy(p.Inst) + return p +} + +// A Directive represents an XML directive of the form . +// The bytes do not include the markers. +type Directive []byte + +func (d Directive) Copy() Directive { return Directive(makeCopy(d)) } + +// CopyToken returns a copy of a Token. +func CopyToken(t Token) Token { + switch v := t.(type) { + case CharData: + return v.Copy() + case Comment: + return v.Copy() + case Directive: + return v.Copy() + case ProcInst: + return v.Copy() + case StartElement: + return v.Copy() + } + return t +} + +// A Decoder represents an XML parser reading a particular input stream. +// The parser assumes that its input is encoded in UTF-8. +type Decoder struct { + // Strict defaults to true, enforcing the requirements + // of the XML specification. + // If set to false, the parser allows input containing common + // mistakes: + // * If an element is missing an end tag, the parser invents + // end tags as necessary to keep the return values from Token + // properly balanced. + // * In attribute values and character data, unknown or malformed + // character entities (sequences beginning with &) are left alone. + // + // Setting: + // + // d.Strict = false; + // d.AutoClose = HTMLAutoClose; + // d.Entity = HTMLEntity + // + // creates a parser that can handle typical HTML. + // + // Strict mode does not enforce the requirements of the XML name spaces TR. + // In particular it does not reject name space tags using undefined prefixes. + // Such tags are recorded with the unknown prefix as the name space URL. + Strict bool + + // When Strict == false, AutoClose indicates a set of elements to + // consider closed immediately after they are opened, regardless + // of whether an end element is present. + AutoClose []string + + // Entity can be used to map non-standard entity names to string replacements. + // The parser behaves as if these standard mappings are present in the map, + // regardless of the actual map content: + // + // "lt": "<", + // "gt": ">", + // "amp": "&", + // "apos": "'", + // "quot": `"`, + Entity map[string]string + + // CharsetReader, if non-nil, defines a function to generate + // charset-conversion readers, converting from the provided + // non-UTF-8 charset into UTF-8. If CharsetReader is nil or + // returns an error, parsing stops with an error. One of the + // the CharsetReader's result values must be non-nil. + CharsetReader func(charset string, input io.Reader) (io.Reader, error) + + // DefaultSpace sets the default name space used for unadorned tags, + // as if the entire XML stream were wrapped in an element containing + // the attribute xmlns="DefaultSpace". + DefaultSpace string + + r io.ByteReader + buf bytes.Buffer + saved *bytes.Buffer + stk *stack + free *stack + needClose bool + toClose Name + nextToken Token + nextByte int + ns map[string]string + err error + line int + offset int64 + unmarshalDepth int +} + +// NewDecoder creates a new XML parser reading from r. +// If r does not implement io.ByteReader, NewDecoder will +// do its own buffering. +func NewDecoder(r io.Reader) *Decoder { + d := &Decoder{ + ns: make(map[string]string), + nextByte: -1, + line: 1, + Strict: true, + } + d.switchToReader(r) + return d +} + +// Token returns the next XML token in the input stream. +// At the end of the input stream, Token returns nil, io.EOF. +// +// Slices of bytes in the returned token data refer to the +// parser's internal buffer and remain valid only until the next +// call to Token. To acquire a copy of the bytes, call CopyToken +// or the token's Copy method. +// +// Token expands self-closing elements such as
+// into separate start and end elements returned by successive calls. +// +// Token guarantees that the StartElement and EndElement +// tokens it returns are properly nested and matched: +// if Token encounters an unexpected end element, +// it will return an error. +// +// Token implements XML name spaces as described by +// http://www.w3.org/TR/REC-xml-names/. Each of the +// Name structures contained in the Token has the Space +// set to the URL identifying its name space when known. +// If Token encounters an unrecognized name space prefix, +// it uses the prefix as the Space rather than report an error. +func (d *Decoder) Token() (t Token, err error) { + if d.stk != nil && d.stk.kind == stkEOF { + err = io.EOF + return + } + if d.nextToken != nil { + t = d.nextToken + d.nextToken = nil + } else if t, err = d.rawToken(); err != nil { + return + } + + if !d.Strict { + if t1, ok := d.autoClose(t); ok { + d.nextToken = t + t = t1 + } + } + switch t1 := t.(type) { + case StartElement: + // In XML name spaces, the translations listed in the + // attributes apply to the element name and + // to the other attribute names, so process + // the translations first. + for _, a := range t1.Attr { + if a.Name.Space == "xmlns" { + v, ok := d.ns[a.Name.Local] + d.pushNs(a.Name.Local, v, ok) + d.ns[a.Name.Local] = a.Value + } + if a.Name.Space == "" && a.Name.Local == "xmlns" { + // Default space for untagged names + v, ok := d.ns[""] + d.pushNs("", v, ok) + d.ns[""] = a.Value + } + } + + d.translate(&t1.Name, true) + for i := range t1.Attr { + d.translate(&t1.Attr[i].Name, false) + } + d.pushElement(t1.Name) + t = t1 + + case EndElement: + d.translate(&t1.Name, true) + if !d.popElement(&t1) { + return nil, d.err + } + t = t1 + } + return +} + +const xmlURL = "http://www.w3.org/XML/1998/namespace" + +// Apply name space translation to name n. +// The default name space (for Space=="") +// applies only to element names, not to attribute names. +func (d *Decoder) translate(n *Name, isElementName bool) { + switch { + case n.Space == "xmlns": + return + case n.Space == "" && !isElementName: + return + case n.Space == "xml": + n.Space = xmlURL + case n.Space == "" && n.Local == "xmlns": + return + } + if v, ok := d.ns[n.Space]; ok { + n.Space = v + } else if n.Space == "" { + n.Space = d.DefaultSpace + } +} + +func (d *Decoder) switchToReader(r io.Reader) { + // Get efficient byte at a time reader. + // Assume that if reader has its own + // ReadByte, it's efficient enough. + // Otherwise, use bufio. + if rb, ok := r.(io.ByteReader); ok { + d.r = rb + } else { + d.r = bufio.NewReader(r) + } +} + +// Parsing state - stack holds old name space translations +// and the current set of open elements. The translations to pop when +// ending a given tag are *below* it on the stack, which is +// more work but forced on us by XML. +type stack struct { + next *stack + kind int + name Name + ok bool +} + +const ( + stkStart = iota + stkNs + stkEOF +) + +func (d *Decoder) push(kind int) *stack { + s := d.free + if s != nil { + d.free = s.next + } else { + s = new(stack) + } + s.next = d.stk + s.kind = kind + d.stk = s + return s +} + +func (d *Decoder) pop() *stack { + s := d.stk + if s != nil { + d.stk = s.next + s.next = d.free + d.free = s + } + return s +} + +// Record that after the current element is finished +// (that element is already pushed on the stack) +// Token should return EOF until popEOF is called. +func (d *Decoder) pushEOF() { + // Walk down stack to find Start. + // It might not be the top, because there might be stkNs + // entries above it. + start := d.stk + for start.kind != stkStart { + start = start.next + } + // The stkNs entries below a start are associated with that + // element too; skip over them. + for start.next != nil && start.next.kind == stkNs { + start = start.next + } + s := d.free + if s != nil { + d.free = s.next + } else { + s = new(stack) + } + s.kind = stkEOF + s.next = start.next + start.next = s +} + +// Undo a pushEOF. +// The element must have been finished, so the EOF should be at the top of the stack. +func (d *Decoder) popEOF() bool { + if d.stk == nil || d.stk.kind != stkEOF { + return false + } + d.pop() + return true +} + +// Record that we are starting an element with the given name. +func (d *Decoder) pushElement(name Name) { + s := d.push(stkStart) + s.name = name +} + +// Record that we are changing the value of ns[local]. +// The old value is url, ok. +func (d *Decoder) pushNs(local string, url string, ok bool) { + s := d.push(stkNs) + s.name.Local = local + s.name.Space = url + s.ok = ok +} + +// Creates a SyntaxError with the current line number. +func (d *Decoder) syntaxError(msg string) error { + return &SyntaxError{Msg: msg, Line: d.line} +} + +// Record that we are ending an element with the given name. +// The name must match the record at the top of the stack, +// which must be a pushElement record. +// After popping the element, apply any undo records from +// the stack to restore the name translations that existed +// before we saw this element. +func (d *Decoder) popElement(t *EndElement) bool { + s := d.pop() + name := t.Name + switch { + case s == nil || s.kind != stkStart: + d.err = d.syntaxError("unexpected end element ") + return false + case s.name.Local != name.Local: + if !d.Strict { + d.needClose = true + d.toClose = t.Name + t.Name = s.name + return true + } + d.err = d.syntaxError("element <" + s.name.Local + "> closed by ") + return false + case s.name.Space != name.Space: + d.err = d.syntaxError("element <" + s.name.Local + "> in space " + s.name.Space + + "closed by in space " + name.Space) + return false + } + + // Pop stack until a Start or EOF is on the top, undoing the + // translations that were associated with the element we just closed. + for d.stk != nil && d.stk.kind != stkStart && d.stk.kind != stkEOF { + s := d.pop() + if s.ok { + d.ns[s.name.Local] = s.name.Space + } else { + delete(d.ns, s.name.Local) + } + } + + return true +} + +// If the top element on the stack is autoclosing and +// t is not the end tag, invent the end tag. +func (d *Decoder) autoClose(t Token) (Token, bool) { + if d.stk == nil || d.stk.kind != stkStart { + return nil, false + } + name := strings.ToLower(d.stk.name.Local) + for _, s := range d.AutoClose { + if strings.ToLower(s) == name { + // This one should be auto closed if t doesn't close it. + et, ok := t.(EndElement) + if !ok || et.Name.Local != name { + return EndElement{d.stk.name}, true + } + break + } + } + return nil, false +} + +var errRawToken = errors.New("xml: cannot use RawToken from UnmarshalXML method") + +// RawToken is like Token but does not verify that +// start and end elements match and does not translate +// name space prefixes to their corresponding URLs. +func (d *Decoder) RawToken() (Token, error) { + if d.unmarshalDepth > 0 { + return nil, errRawToken + } + return d.rawToken() +} + +func (d *Decoder) rawToken() (Token, error) { + if d.err != nil { + return nil, d.err + } + if d.needClose { + // The last element we read was self-closing and + // we returned just the StartElement half. + // Return the EndElement half now. + d.needClose = false + return EndElement{d.toClose}, nil + } + + b, ok := d.getc() + if !ok { + return nil, d.err + } + + if b != '<' { + // Text section. + d.ungetc(b) + data := d.text(-1, false) + if data == nil { + return nil, d.err + } + return CharData(data), nil + } + + if b, ok = d.mustgetc(); !ok { + return nil, d.err + } + switch b { + case '/': + // ' { + d.err = d.syntaxError("invalid characters between ") + return nil, d.err + } + return EndElement{name}, nil + + case '?': + // ' { + break + } + b0 = b + } + data := d.buf.Bytes() + data = data[0 : len(data)-2] // chop ?> + + if target == "xml" { + content := string(data) + ver := procInst("version", content) + if ver != "" && ver != "1.0" { + d.err = fmt.Errorf("xml: unsupported version %q; only version 1.0 is supported", ver) + return nil, d.err + } + enc := procInst("encoding", content) + if enc != "" && enc != "utf-8" && enc != "UTF-8" { + if d.CharsetReader == nil { + d.err = fmt.Errorf("xml: encoding %q declared but Decoder.CharsetReader is nil", enc) + return nil, d.err + } + newr, err := d.CharsetReader(enc, d.r.(io.Reader)) + if err != nil { + d.err = fmt.Errorf("xml: opening charset %q: %v", enc, err) + return nil, d.err + } + if newr == nil { + panic("CharsetReader returned a nil Reader for charset " + enc) + } + d.switchToReader(newr) + } + } + return ProcInst{target, data}, nil + + case '!': + // ' { + break + } + b0, b1 = b1, b + } + data := d.buf.Bytes() + data = data[0 : len(data)-3] // chop --> + return Comment(data), nil + + case '[': // . + data := d.text(-1, true) + if data == nil { + return nil, d.err + } + return CharData(data), nil + } + + // Probably a directive: , , etc. + // We don't care, but accumulate for caller. Quoted angle + // brackets do not count for nesting. + d.buf.Reset() + d.buf.WriteByte(b) + inquote := uint8(0) + depth := 0 + for { + if b, ok = d.mustgetc(); !ok { + return nil, d.err + } + if inquote == 0 && b == '>' && depth == 0 { + break + } + HandleB: + d.buf.WriteByte(b) + switch { + case b == inquote: + inquote = 0 + + case inquote != 0: + // in quotes, no special action + + case b == '\'' || b == '"': + inquote = b + + case b == '>' && inquote == 0: + depth-- + + case b == '<' && inquote == 0: + // Look for