From d03fca801b2eb989a89dc4ed33e139c2209d9d89 Mon Sep 17 00:00:00 2001 From: konrad Date: Sat, 3 Nov 2018 15:05:45 +0000 Subject: [PATCH] CalDAV support (#15) --- Featurecreep.md | 5 + REST-Tests/lists.http | 6 + go.mod | 11 +- go.sum | 18 + pkg/caldav/caldav.go | 95 +++++ pkg/models/list.go | 5 +- pkg/routes/api/v1/caldav.go | 74 ++++ pkg/routes/routes.go | 5 + pkg/utils/sha256.go | 11 + vendor/github.com/dgrijalva/jwt-go/README.md | 27 +- .../dgrijalva/jwt-go/VERSION_HISTORY.md | 13 + vendor/github.com/dgrijalva/jwt-go/ecdsa.go | 1 + vendor/github.com/dgrijalva/jwt-go/hmac.go | 3 +- vendor/github.com/dgrijalva/jwt-go/parser.go | 113 +++--- vendor/github.com/dgrijalva/jwt-go/rsa.go | 5 +- .../github.com/dgrijalva/jwt-go/rsa_utils.go | 32 ++ vendor/github.com/labstack/echo/.gitignore | 11 +- vendor/github.com/labstack/echo/.travis.yml | 20 +- vendor/github.com/labstack/echo/Gopkg.lock | 114 ++++++ vendor/github.com/labstack/echo/Gopkg.toml | 50 +++ vendor/github.com/labstack/echo/Makefile | 17 + vendor/github.com/labstack/echo/README.md | 63 +++- vendor/github.com/labstack/echo/bind.go | 38 +- vendor/github.com/labstack/echo/context.go | 68 ++-- vendor/github.com/labstack/echo/echo.go | 344 ++++++++++------- vendor/github.com/labstack/echo/echo_go1.8.go | 25 -- vendor/github.com/labstack/echo/glide.lock | 92 ----- vendor/github.com/labstack/echo/glide.yaml | 30 -- vendor/github.com/labstack/echo/go.mod | 15 + vendor/github.com/labstack/echo/go.sum | 22 ++ vendor/github.com/labstack/echo/group.go | 69 ++-- .../labstack/echo/middleware/basic_auth.go | 18 +- .../labstack/echo/middleware/body_dump.go | 111 ++++++ .../labstack/echo/middleware/body_limit.go | 7 +- .../labstack/echo/middleware/compress.go | 5 +- .../labstack/echo/middleware/cors.go | 16 +- .../labstack/echo/middleware/csrf.go | 26 +- .../labstack/echo/middleware/jwt.go | 58 ++- .../labstack/echo/middleware/key_auth.go | 28 +- .../labstack/echo/middleware/logger.go | 28 +- .../labstack/echo/middleware/middleware.go | 28 +- .../labstack/echo/middleware/proxy.go | 252 +++++++++++++ .../labstack/echo/middleware/recover.go | 16 +- .../labstack/echo/middleware/redirect.go | 170 +++------ .../labstack/echo/middleware/rewrite.go | 84 +++++ .../labstack/echo/middleware/secure.go | 12 +- .../labstack/echo/middleware/slash.go | 2 +- .../labstack/echo/middleware/static.go | 150 ++++++-- vendor/github.com/labstack/echo/response.go | 31 +- vendor/github.com/labstack/echo/router.go | 97 +++-- .../github.com/labstack/gommon/bytes/bytes.go | 68 ++-- vendor/github.com/labstack/gommon/log/log.go | 5 +- .../labstack/gommon/random/random.go | 14 +- .../mattn/go-colorable/colorable_windows.go | 180 +++------ vendor/golang.org/x/crypto/acme/acme.go | 17 +- .../x/crypto/acme/autocert/autocert.go | 345 ++++++++++++------ .../x/crypto/acme/autocert/renewal.go | 4 +- vendor/golang.org/x/crypto/bcrypt/bcrypt.go | 4 +- vendor/modules.txt | 12 +- 59 files changed, 2192 insertions(+), 998 deletions(-) create mode 100644 pkg/caldav/caldav.go create mode 100644 pkg/routes/api/v1/caldav.go create mode 100644 pkg/utils/sha256.go create mode 100644 vendor/github.com/labstack/echo/Gopkg.lock create mode 100644 vendor/github.com/labstack/echo/Gopkg.toml create mode 100644 vendor/github.com/labstack/echo/Makefile delete mode 100644 vendor/github.com/labstack/echo/echo_go1.8.go delete mode 100644 vendor/github.com/labstack/echo/glide.lock delete mode 100644 vendor/github.com/labstack/echo/glide.yaml create mode 100644 vendor/github.com/labstack/echo/go.mod create mode 100644 vendor/github.com/labstack/echo/go.sum create mode 100644 vendor/github.com/labstack/echo/middleware/body_dump.go create mode 100644 vendor/github.com/labstack/echo/middleware/proxy.go create mode 100644 vendor/github.com/labstack/echo/middleware/rewrite.go diff --git a/Featurecreep.md b/Featurecreep.md index 826997236..623da26a0 100644 --- a/Featurecreep.md +++ b/Featurecreep.md @@ -236,6 +236,11 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten. * [x] Email-Verifizierung beim Registrieren * [x] Password Reset -> Link via email oder so * [ ] Settings +* [ ] CalDAV + * [x] Basics + * [ ] Reminders + * [ ] Discovery, stichwort PROPFIND + ### Later/Nice to have diff --git a/REST-Tests/lists.http b/REST-Tests/lists.http index 5718b803e..fcf19fca5 100644 --- a/REST-Tests/lists.http +++ b/REST-Tests/lists.http @@ -104,4 +104,10 @@ Authorization: Bearer {{auth_token}} GET http://localhost:8080/api/v1/tasks Authorization: Bearer {{auth_token}} +### + +# Get all pending tasks in caldav +GET http://localhost:8080/api/v1/tasks/caldav +#Authorization: Bearer {{auth_token}} + ### \ No newline at end of file diff --git a/go.mod b/go.mod index 0df9f188a..c7d3b79de 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/client9/misspell v0.3.4 github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect - github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/garyburd/redigo v1.6.0 // indirect github.com/go-openapi/analysis v0.17.2 // indirect @@ -32,11 +32,9 @@ require ( github.com/joho/godotenv v1.3.0 // indirect github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 github.com/kr/pretty v0.1.0 // indirect - github.com/labstack/echo v3.1.0+incompatible - github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 + github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251 + github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec github.com/lib/pq v1.0.0 // indirect - github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd // indirect - github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee // indirect github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 // indirect github.com/mattn/go-sqlite3 v1.9.0 github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 @@ -44,9 +42,8 @@ require ( github.com/stretchr/testify v1.2.2 github.com/toqueteos/webbrowser v1.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect github.com/ziutek/mymysql v1.5.4 // indirect - golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 + golang.org/x/crypto v0.0.0-20180312195533-182114d58262 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index c1897597a..75c3d777b 100644 --- a/go.sum +++ b/go.sum @@ -10,12 +10,15 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzs github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a h1:nmYyGtn9AO7FCeZ2tHr1ZWjJAHi6SfGB3o80F8o7EbA= github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -92,10 +95,16 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251 h1:4q++nZ4OEtmbHazhA/7i3T9B+CBWtnHpuMMcW55ZjRk= +github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251/go.mod h1:rWD2DNQgFb1IY9lVYZVLWn2Ko4dyHZ/LpHORyBLP3hI= github.com/labstack/echo v3.1.0+incompatible h1:kb0CCZ0boaiGsZOqR9E9+GDpQEoIaKClVqqo0+/hzbM= github.com/labstack/echo v3.1.0+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/echo v3.2.1+incompatible h1:J2M7YArHx4gi8p/3fDw8tX19SXhBCoRpviyAZSN3I88= +github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 h1:kcJPx2Ug9owxOsVfuXPCludLaIudyI57YQd6ocyrO4o= github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= +github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec h1:aYKwS4iCpqxskMuvI8+Byq0CxnnWHO/xuLk2pZJ96tY= +github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -104,8 +113,12 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd h1:eYiiP5pgdf+n78BU5JFWt7yI2bpxW31L/R5Rrk8vLgs= github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee h1:tUyoJR5V1TdXnTh9v8c1YAHvDdut2+zkuyUX3gAY/wI= github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 h1:+IPgoz43mdEYG5lrqNcjr3DQpAE38SqHtyx1IsqqQGM= github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= @@ -131,10 +144,12 @@ github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.2.0 h1:M4Rzxlu+RgU4pyBRKhKaVN1VeYOm8h2jgyXnAseDgCc= github.com/spf13/viper v1.2.0/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0= github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A= +github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= @@ -143,10 +158,13 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180312195533-182114d58262 h1:1NLVUmR8SQ7cNNA5Vo7ronpXbR+5A+9IwIC/bLE7D8Y= +golang.org/x/crypto v0.0.0-20180312195533-182114d58262/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go new file mode 100644 index 000000000..a290f5e2b --- /dev/null +++ b/pkg/caldav/caldav.go @@ -0,0 +1,95 @@ +package caldav + +import ( + "code.vikunja.io/api/pkg/utils" + "strconv" + "time" +) + +// Event holds a single caldav event +type Event struct { + Summary string + Description string + UID string + Alarms []Alarm + + TimestampUnix int64 + StartUnix int64 + EndUnix int64 +} + +// Alarm holds infos about an alarm from a caldav event +type Alarm struct { + TimeUnix int64 + Description string +} + +// Config is the caldav calendar config +type Config struct { + Name string + ProdID string +} + +// ParseEvents parses an array of caldav events and gives them back as string +func ParseEvents(config *Config, events []*Event) (caldavevents string) { + caldavevents += `BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +X-PUBLISHED-TTL:PT4H +X-WR-CALNAME:` + config.Name + ` +PRODID:-//` + config.ProdID + `//EN` + + for _, e := range events { + + if e.UID == "" { + e.UID = makeCalDavTimeFromUnixTime(e.TimestampUnix) + utils.Sha256(e.Summary) + } + + caldavevents += ` +BEGIN:VEVENT +UID:` + e.UID + ` +SUMMARY:` + e.Summary + ` +DESCRIPTION:` + e.Description + ` +DTSTAMP:` + makeCalDavTimeFromUnixTime(e.TimestampUnix) + ` +DTSTART:` + makeCalDavTimeFromUnixTime(e.StartUnix) + ` +DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix) + + for _, a := range e.Alarms { + if a.Description == "" { + a.Description = e.Summary + } + + caldavevents += ` +BEGIN:VALARM +TRIGGER:` + calcAlarmDateFromReminder(e.StartUnix, a.TimeUnix) + ` +ACTION:DISPLAY +DESCRIPTION:` + a.Description + ` +END:VALARM` + } + caldavevents += ` +END:VEVENT` + } + + caldavevents += ` +END:VCALENDAR` // Need a line break + + return +} + +func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) { + tm := time.Unix(unixtime, 0) + return tm.Format("20060102T150405") +} + +func calcAlarmDateFromReminder(eventStartUnix, reminderUnix int64) (alarmTime string) { + if eventStartUnix > reminderUnix { + alarmTime += `-` + } + alarmTime += `PT` + diff := eventStartUnix - reminderUnix + if diff < 0 { // Make it positive + diff = diff * -1 + } + alarmTime += strconv.Itoa(int(diff/60)) + "M" + return +} diff --git a/pkg/models/list.go b/pkg/models/list.go index 1785577b3..17cd65711 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -161,7 +161,11 @@ type ListTasksDummy struct { // ReadAll gets all tasks for a user func (lt *ListTasksDummy) ReadAll(u *User) (interface{}, error) { + return GetTasksByUser(u) +} +//GetTasksByUser returns all tasks for a user +func GetTasksByUser(u *User) (tasks []*ListTask, err error) { // Get all lists lists, err := getRawListsForUser(u) if err != nil { @@ -175,7 +179,6 @@ func (lt *ListTasksDummy) ReadAll(u *User) (interface{}, error) { } // Then return all tasks for that lists - var tasks []*ListTask if err := x.In("list_id", listIDs).Find(&tasks); err != nil { return nil, err } diff --git a/pkg/routes/api/v1/caldav.go b/pkg/routes/api/v1/caldav.go new file mode 100644 index 000000000..5e28a904c --- /dev/null +++ b/pkg/routes/api/v1/caldav.go @@ -0,0 +1,74 @@ +package v1 + +import ( + "code.vikunja.io/api/pkg/caldav" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/routes/crud" + "github.com/labstack/echo" + "net/http" + "time" +) + +// Caldav returns a caldav-readable format with all tasks +func Caldav(c echo.Context) error { + + // swagger:operation GET /tasks/caldav list caldavTasks + // --- + // summary: Get all tasks as caldav + // responses: + // "200": + // "$ref": "#/responses/Message" + // "400": + // "$ref": "#/responses/Message" + // "500": + // "$ref": "#/responses/Message" + + // Request basic auth + user, pass, ok := c.Request().BasicAuth() + + // Check credentials + creds := &models.UserLogin{ + Username: user, + Password: pass, + } + u, err := models.CheckUserCredentials(creds) + + if !ok || err != nil { + c.Response().Header().Set("WWW-Authenticate", `Basic realm="Vikunja cal"`) + return c.String(http.StatusUnauthorized, "Unauthorized.") + } + + // Get all tasks for that user + tasks, err := models.GetTasksByUser(&u) + if err != nil { + return crud.HandleHTTPError(err) + } + + hour := int64(time.Hour.Seconds()) + var caldavTasks []*caldav.Event + for _, t := range tasks { + if t.DueDateUnix != 0 { + event := &caldav.Event{ + Summary: t.Text, + Description: t.Description, + UID: "", + TimestampUnix: t.Updated, + StartUnix: t.DueDateUnix, + EndUnix: t.DueDateUnix + hour, + } + + if t.ReminderUnix != 0 { + event.Alarms = append(event.Alarms, caldav.Alarm{TimeUnix: t.ReminderUnix}) + } + + caldavTasks = append(caldavTasks, event) + } + } + + caldavConfig := &caldav.Config{ + Name: "Vikunja Calendar for " + u.Username, + ProdID: "Vikunja Todo App", + } + + return c.String(http.StatusOK, caldav.ParseEvents(caldavConfig, caldavTasks)) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 1c222054a..901ea7869 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -41,6 +41,8 @@ import ( func NewEcho() *echo.Echo { e := echo.New() + e.HideBanner = true + // Logger e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${time_rfc3339_nano}: ${remote_ip} ${method} ${status} ${uri} ${latency_human} - ${user_agent}\n", @@ -69,6 +71,9 @@ func RegisterRoutes(e *echo.Echo) { a.POST("/user/password/reset", apiv1.UserResetPassword) a.POST("/user/confirm", apiv1.UserConfirmEmail) + // Caldav, with auth + a.GET("/tasks/caldav", apiv1.Caldav) + // ===== Routes with Authetification ===== // Authetification a.Use(middleware.JWT([]byte(viper.GetString("service.JWTSecret")))) diff --git a/pkg/utils/sha256.go b/pkg/utils/sha256.go new file mode 100644 index 000000000..980746491 --- /dev/null +++ b/pkg/utils/sha256.go @@ -0,0 +1,11 @@ +package utils + +import ( + "crypto/sha256" + "fmt" +) + +// Sha256 calculates a sha256 hash from a string +func Sha256(cleartext string) string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(cleartext)))[:45] +} diff --git a/vendor/github.com/dgrijalva/jwt-go/README.md b/vendor/github.com/dgrijalva/jwt-go/README.md index 25aec486c..d358d881b 100644 --- a/vendor/github.com/dgrijalva/jwt-go/README.md +++ b/vendor/github.com/dgrijalva/jwt-go/README.md @@ -1,11 +1,15 @@ -A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) +# jwt-go [![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) +[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go) -**BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. +A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) -**NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. +**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3. +**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail. + +**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. ## What the heck is a JWT? @@ -37,7 +41,7 @@ Here's an example of an extension that integrates with the Google App Engine sig ## Compliance -This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: +This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: * In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. @@ -47,7 +51,10 @@ This library is considered production ready. Feedback and feature requests are This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). -While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning. +While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning. + +**BREAKING CHANGES:*** +* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. ## Usage Tips @@ -68,6 +75,14 @@ Symmetric signing methods, such as HSA, use only a single secret. This is probab Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. +### Signing Methods and Key Types + +Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: + +* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation +* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation +* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation + ### JWT and OAuth It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. @@ -77,7 +92,7 @@ Without going too far down the rabbit hole, here's a description of the interact * OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. * OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. * Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. - + ## More Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). diff --git a/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md b/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md index b605b4509..637029831 100644 --- a/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md +++ b/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md @@ -1,5 +1,18 @@ ## `jwt-go` Version History +#### 3.2.0 + +* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation +* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate +* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before. +* Deprecated `ParseFromRequestWithClaims` to simplify API in the future. + +#### 3.1.0 + +* Improvements to `jwt` command line tool +* Added `SkipClaimsValidation` option to `Parser` +* Documentation updates + #### 3.0.0 * **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code diff --git a/vendor/github.com/dgrijalva/jwt-go/ecdsa.go b/vendor/github.com/dgrijalva/jwt-go/ecdsa.go index 2f59a2223..f97738124 100644 --- a/vendor/github.com/dgrijalva/jwt-go/ecdsa.go +++ b/vendor/github.com/dgrijalva/jwt-go/ecdsa.go @@ -14,6 +14,7 @@ var ( ) // Implements the ECDSA family of signing methods signing methods +// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification type SigningMethodECDSA struct { Name string Hash crypto.Hash diff --git a/vendor/github.com/dgrijalva/jwt-go/hmac.go b/vendor/github.com/dgrijalva/jwt-go/hmac.go index c22991925..addbe5d40 100644 --- a/vendor/github.com/dgrijalva/jwt-go/hmac.go +++ b/vendor/github.com/dgrijalva/jwt-go/hmac.go @@ -7,6 +7,7 @@ import ( ) // Implements the HMAC-SHA family of signing methods signing methods +// Expects key type of []byte for both signing and validation type SigningMethodHMAC struct { Name string Hash crypto.Hash @@ -90,5 +91,5 @@ func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, return EncodeSegment(hasher.Sum(nil)), nil } - return "", ErrInvalidKey + return "", ErrInvalidKeyType } diff --git a/vendor/github.com/dgrijalva/jwt-go/parser.go b/vendor/github.com/dgrijalva/jwt-go/parser.go index 7bf1c4ea0..d6901d9ad 100644 --- a/vendor/github.com/dgrijalva/jwt-go/parser.go +++ b/vendor/github.com/dgrijalva/jwt-go/parser.go @@ -21,55 +21,9 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { - parts := strings.Split(tokenString, ".") - if len(parts) != 3 { - return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) - } - - var err error - token := &Token{Raw: tokenString} - - // parse Header - var headerBytes []byte - if headerBytes, err = DecodeSegment(parts[0]); err != nil { - if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { - return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) - } - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - if err = json.Unmarshal(headerBytes, &token.Header); err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // parse Claims - var claimBytes []byte - token.Claims = claims - - if claimBytes, err = DecodeSegment(parts[1]); err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) - if p.UseJSONNumber { - dec.UseNumber() - } - // JSON Decode. Special case for map type to avoid weird pointer behavior - if c, ok := token.Claims.(MapClaims); ok { - err = dec.Decode(&c) - } else { - err = dec.Decode(&claims) - } - // Handle decode error + token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // Lookup signature method - if method, ok := token.Header["alg"].(string); ok { - if token.Method = GetSigningMethod(method); token.Method == nil { - return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) - } - } else { - return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) + return token, err } // Verify signing method is in the required set @@ -96,6 +50,9 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf } if key, err = keyFunc(token); err != nil { // keyFunc returned an error + if ve, ok := err.(*ValidationError); ok { + return token, ve + } return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} } @@ -129,3 +86,63 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf return token, vErr } + +// WARNING: Don't use this method unless you know what you're doing +// +// This method parses the token but doesn't validate the signature. It's only +// ever useful in cases where you know the signature is valid (because it has +// been checked previously in the stack) and you want to extract values from +// it. +func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { + parts = strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) + } + + token = &Token{Raw: tokenString} + + // parse Header + var headerBytes []byte + if headerBytes, err = DecodeSegment(parts[0]); err != nil { + if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { + return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) + } + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + if err = json.Unmarshal(headerBytes, &token.Header); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // parse Claims + var claimBytes []byte + token.Claims = claims + + if claimBytes, err = DecodeSegment(parts[1]); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) + if p.UseJSONNumber { + dec.UseNumber() + } + // JSON Decode. Special case for map type to avoid weird pointer behavior + if c, ok := token.Claims.(MapClaims); ok { + err = dec.Decode(&c) + } else { + err = dec.Decode(&claims) + } + // Handle decode error + if err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // Lookup signature method + if method, ok := token.Header["alg"].(string); ok { + if token.Method = GetSigningMethod(method); token.Method == nil { + return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) + } + } else { + return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) + } + + return token, parts, nil +} diff --git a/vendor/github.com/dgrijalva/jwt-go/rsa.go b/vendor/github.com/dgrijalva/jwt-go/rsa.go index 0ae0b1984..e4caf1ca4 100644 --- a/vendor/github.com/dgrijalva/jwt-go/rsa.go +++ b/vendor/github.com/dgrijalva/jwt-go/rsa.go @@ -7,6 +7,7 @@ import ( ) // Implements the RSA family of signing methods signing methods +// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation type SigningMethodRSA struct { Name string Hash crypto.Hash @@ -44,7 +45,7 @@ func (m *SigningMethodRSA) Alg() string { } // Implements the Verify method from SigningMethod -// For this signing method, must be an rsa.PublicKey structure. +// For this signing method, must be an *rsa.PublicKey structure. func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { var err error @@ -73,7 +74,7 @@ func (m *SigningMethodRSA) Verify(signingString, signature string, key interface } // Implements the Sign method from SigningMethod -// For this signing method, must be an rsa.PrivateKey structure. +// For this signing method, must be an *rsa.PrivateKey structure. func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { var rsaKey *rsa.PrivateKey var ok bool diff --git a/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go b/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go index 213a90dbb..a5ababf95 100644 --- a/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go +++ b/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go @@ -39,6 +39,38 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { return pkey, nil } +// Parse PEM encoded PKCS1 or PKCS8 private key protected with password +func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + + var blockDecrypted []byte + if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil { + return nil, err + } + + if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + // Parse PEM encoded PKCS1 or PKCS8 public key func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { var err error diff --git a/vendor/github.com/labstack/echo/.gitignore b/vendor/github.com/labstack/echo/.gitignore index 36331f0cd..dd74acca4 100644 --- a/vendor/github.com/labstack/echo/.gitignore +++ b/vendor/github.com/labstack/echo/.gitignore @@ -1,8 +1,7 @@ -# Website -website/public - -# Glide -vendor - .DS_Store +coverage.txt _test +vendor +.idea +*.iml +*.out diff --git a/vendor/github.com/labstack/echo/.travis.yml b/vendor/github.com/labstack/echo/.travis.yml index 689ab3565..7de4f9663 100644 --- a/vendor/github.com/labstack/echo/.travis.yml +++ b/vendor/github.com/labstack/echo/.travis.yml @@ -1,19 +1,15 @@ language: go go: - - 1.7 - - 1.8 - - tip + - 1.9.x + - 1.10.x + - 1.11.x + - tip install: - - go get golang.org/x/tools/cmd/cover - - go get github.com/Masterminds/glide - - go get github.com/mattn/goveralls - - go get github.com/modocache/gover - - glide install + - make dependency script: - - go test -coverprofile=echo.coverprofile - - go test -coverprofile=middleware.coverprofile ./middleware - - gover - - goveralls -coverprofile=gover.coverprofile -service=travis-ci + - make test +after_success: + - bash <(curl -s https://codecov.io/bash) matrix: allow_failures: - go: tip diff --git a/vendor/github.com/labstack/echo/Gopkg.lock b/vendor/github.com/labstack/echo/Gopkg.lock new file mode 100644 index 000000000..7b402d8b5 --- /dev/null +++ b/vendor/github.com/labstack/echo/Gopkg.lock @@ -0,0 +1,114 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + pruneopts = "UT" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" + +[[projects]] + digest = "1:568171fc14a3d819b112c3e219d351ea7b05e8dad7935c4168c6b3373244a686" + name = "github.com/labstack/gommon" + packages = [ + "bytes", + "color", + "log", + "random", + ] + pruneopts = "UT" + revision = "2a618302b929cc20862dda3aa6f02f64dbe740dd" + version = "v0.2.7" + +[[projects]] + digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" + name = "github.com/mattn/go-colorable" + packages = ["."] + pruneopts = "UT" + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" + +[[projects]] + digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" + version = "v0.0.4" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83" + name = "github.com/stretchr/testify" + packages = ["assert"] + pruneopts = "UT" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" + +[[projects]] + digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59" + name = "github.com/valyala/bytebufferpool" + packages = ["."] + pruneopts = "UT" + revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7" + version = "v1.0.0" + +[[projects]] + branch = "master" + digest = "1:268b8bce0064e8c057d7b913605459f9a26dcab864c0886a56d196540fbf003f" + name = "github.com/valyala/fasttemplate" + packages = ["."] + pruneopts = "UT" + revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0" + +[[projects]] + branch = "master" + digest = "1:dedf20eb0d3e8d6aa8a4d3d2fae248222b688ed528201995e152cc497899123c" + name = "golang.org/x/crypto" + packages = [ + "acme", + "acme/autocert", + ] + pruneopts = "UT" + revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" + +[[projects]] + branch = "master" + digest = "1:6eb2645d74b43d9c87b51947df39f7c668a4f422cd512053f7f6f75bfaad0197" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "d0be0721c37eeb5299f245a996a483160fc36940" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/dgrijalva/jwt-go", + "github.com/labstack/gommon/bytes", + "github.com/labstack/gommon/color", + "github.com/labstack/gommon/log", + "github.com/labstack/gommon/random", + "github.com/stretchr/testify/assert", + "github.com/valyala/fasttemplate", + "golang.org/x/crypto/acme/autocert", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/labstack/echo/Gopkg.toml b/vendor/github.com/labstack/echo/Gopkg.toml new file mode 100644 index 000000000..42cb731e8 --- /dev/null +++ b/vendor/github.com/labstack/echo/Gopkg.toml @@ -0,0 +1,50 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# 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/dgrijalva/jwt-go" + version = "3.2.0" + +[[constraint]] + name = "github.com/labstack/gommon" + version = "0.2.7" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.2" + +[[constraint]] + branch = "master" + name = "github.com/valyala/fasttemplate" + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" + +[prune] + go-tests = true + unused-packages = true diff --git a/vendor/github.com/labstack/echo/Makefile b/vendor/github.com/labstack/echo/Makefile new file mode 100644 index 000000000..494667d87 --- /dev/null +++ b/vendor/github.com/labstack/echo/Makefile @@ -0,0 +1,17 @@ +DEP_VERSION=0.4.1 + +dependency: + curl -fsSL -o ${GOPATH}/bin/dep https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 + chmod +x ${GOPATH}/bin/dep + dep ensure + +test: + echo "" > coverage.txt + for d in $(shell go list ./... | grep -v vendor); do \ + go test -race -coverprofile=profile.out -covermode=atomic $$d || exit 1; \ + [ -f profile.out ] && cat profile.out >> coverage.txt && rm profile.out; \ + done + +tag: + @git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'` + @git tag|grep -v ^v diff --git a/vendor/github.com/labstack/echo/README.md b/vendor/github.com/labstack/echo/README.md index 84ca26c56..49e4d3b10 100644 --- a/vendor/github.com/labstack/echo/README.md +++ b/vendor/github.com/labstack/echo/README.md @@ -1,4 +1,14 @@ -# [Echo](https://echo.labstack.com) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) [![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo) [![Coverage Status](http://img.shields.io/coveralls/labstack/echo.svg?style=flat-square)](https://coveralls.io/r/labstack/echo) [![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo) [![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) + + +[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo) +[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo) +[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo) +[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo) +[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo) +[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com) +[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) ## Feature Overview @@ -16,18 +26,53 @@ - Automatic TLS via Let’s Encrypt - HTTP/2 support -## Performance +## Benchmarks -![Performance](https://i.imgur.com/F2V7TfO.png) +Date: 2018/03/15
+Source: https://github.com/vishr/web-framework-benchmark
+Lower is better! -## [Get Started](https://echo.labstack.com/guide) + -## Support Us +## [Guide](https://echo.labstack.com/guide) -- :star: the project -- [Donate](https://echo.labstack.com/support-echo) -- :earth_americas: spread the word -- [Contribute](#contribute) to the project +### Example + +```go +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func main() { + // Echo instance + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Routes + e.GET("/", hello) + + // Start server + e.Logger.Fatal(e.Start(":1323")) +} + +// Handler +func hello(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") +} +``` + +## Help + +- [Forum](https://forum.labstack.com) +- [Chat](https://gitter.im/labstack/echo) ## Contribute diff --git a/vendor/github.com/labstack/echo/bind.go b/vendor/github.com/labstack/echo/bind.go index 2598840c0..23f3a1b49 100644 --- a/vendor/github.com/labstack/echo/bind.go +++ b/vendor/github.com/labstack/echo/bind.go @@ -44,12 +44,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { case strings.HasPrefix(ctype, MIMEApplicationJSON): if err = json.NewDecoder(req.Body).Decode(i); err != nil { if ute, ok := err.(*json.UnmarshalTypeError); ok { - return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset)) + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)) } else if se, ok := err.(*json.SyntaxError); ok { return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())) - } else { - return NewHTTPError(http.StatusBadRequest, err.Error()) } + return NewHTTPError(http.StatusBadRequest, err.Error()) } case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML): if err = xml.NewDecoder(req.Body).Decode(i); err != nil { @@ -57,9 +56,8 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())) } else if se, ok := err.(*xml.SyntaxError); ok { return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())) - } else { - return NewHTTPError(http.StatusBadRequest, err.Error()) } + return NewHTTPError(http.StatusBadRequest, err.Error()) } case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm): params, err := c.FormParams() @@ -80,7 +78,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag val := reflect.ValueOf(ptr).Elem() if typ.Kind() != reflect.Struct { - return errors.New("Binding element must be a struct") + return errors.New("binding element must be a struct") } for i := 0; i < typ.NumField(); i++ { @@ -96,14 +94,29 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag inputFieldName = typeField.Name // If tag is nil, we inspect if the field is a struct. if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct { - err := b.bindData(structField.Addr().Interface(), data, tag) - if err != nil { + if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil { return err } continue } } + inputValue, exists := data[inputFieldName] + if !exists { + // Go json.Unmarshal supports case insensitive binding. However the + // url params are bound case sensitive which is inconsistent. To + // fix this we must check all of the map values in a + // case-insensitive search. + inputFieldName = strings.ToLower(inputFieldName) + for k, v := range data { + if strings.ToLower(k) == inputFieldName { + inputValue = v + exists = true + break + } + } + } + if !exists { continue } @@ -126,10 +139,9 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag } } val.Field(i).Set(slice) - } else { - if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { - return err - } + } else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err + } } return nil @@ -142,6 +154,8 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V } switch valueKind { + case reflect.Ptr: + return setWithProperType(structField.Elem().Kind(), val, structField.Elem()) case reflect.Int: return setIntField(val, 0, structField) case reflect.Int8: diff --git a/vendor/github.com/labstack/echo/context.go b/vendor/github.com/labstack/echo/context.go index d42315587..cf780c513 100644 --- a/vendor/github.com/labstack/echo/context.go +++ b/vendor/github.com/labstack/echo/context.go @@ -31,6 +31,9 @@ type ( // IsTLS returns true if HTTP connection is TLS otherwise false. IsTLS() bool + // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. + IsWebSocket() bool + // Scheme returns the HTTP protocol scheme, `http` or `https`. Scheme() string @@ -203,6 +206,13 @@ const ( indexPage = "index.html" ) +func (c *context) writeContentType(value string) { + header := c.Response().Header() + if header.Get(HeaderContentType) == "" { + header.Set(HeaderContentType, value) + } +} + func (c *context) Request() *http.Request { return c.request } @@ -219,12 +229,29 @@ func (c *context) IsTLS() bool { return c.request.TLS != nil } +func (c *context) IsWebSocket() bool { + upgrade := c.request.Header.Get(HeaderUpgrade) + return upgrade == "websocket" || upgrade == "Websocket" +} + func (c *context) Scheme() string { // Can't use `r.Request.URL.Scheme` // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 if c.IsTLS() { return "https" } + if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { + return scheme + } + if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { + return scheme + } + if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { + return "https" + } + if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { + return scheme + } return "http" } @@ -254,13 +281,6 @@ func (c *context) Param(name string) string { if n == name { return c.pvalues[i] } - - // Param name with aliases - for _, p := range strings.Split(n, ",") { - if p == name { - return c.pvalues[i] - } - } } } return "" @@ -385,7 +405,8 @@ func (c *context) String(code int, s string) (err error) { } func (c *context) JSON(code int, i interface{}) (err error) { - if c.echo.Debug { + _, pretty := c.QueryParams()["pretty"] + if c.echo.Debug || pretty { return c.JSONPretty(code, i, " ") } b, err := json.Marshal(i) @@ -416,7 +437,7 @@ func (c *context) JSONP(code int, callback string, i interface{}) (err error) { } func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8) + c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) c.response.WriteHeader(code) if _, err = c.response.Write([]byte(callback + "(")); err != nil { return @@ -429,7 +450,8 @@ func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { } func (c *context) XML(code int, i interface{}) (err error) { - if c.echo.Debug { + _, pretty := c.QueryParams()["pretty"] + if c.echo.Debug || pretty { return c.XMLPretty(code, i, " ") } b, err := xml.Marshal(i) @@ -448,7 +470,7 @@ func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) } func (c *context) XMLBlob(code int, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) + c.writeContentType(MIMEApplicationXMLCharsetUTF8) c.response.WriteHeader(code) if _, err = c.response.Write([]byte(xml.Header)); err != nil { return @@ -458,28 +480,23 @@ func (c *context) XMLBlob(code int, b []byte) (err error) { } func (c *context) Blob(code int, contentType string, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, contentType) + c.writeContentType(contentType) c.response.WriteHeader(code) _, err = c.response.Write(b) return } func (c *context) Stream(code int, contentType string, r io.Reader) (err error) { - c.response.Header().Set(HeaderContentType, contentType) + c.writeContentType(contentType) c.response.WriteHeader(code) _, err = io.Copy(c.response, r) return } func (c *context) File(file string) (err error) { - file, err = url.QueryUnescape(file) // Issue #839 - if err != nil { - return - } - f, err := os.Open(file) if err != nil { - return ErrNotFound + return NotFoundHandler(c) } defer f.Close() @@ -488,7 +505,7 @@ func (c *context) File(file string) (err error) { file = filepath.Join(file, indexPage) f, err = os.Open(file) if err != nil { - return ErrNotFound + return NotFoundHandler(c) } defer f.Close() if fi, err = f.Stat(); err != nil { @@ -499,18 +516,17 @@ func (c *context) File(file string) (err error) { return } -func (c *context) Attachment(file, name string) (err error) { +func (c *context) Attachment(file, name string) error { return c.contentDisposition(file, name, "attachment") } -func (c *context) Inline(file, name string) (err error) { +func (c *context) Inline(file, name string) error { return c.contentDisposition(file, name, "inline") } -func (c *context) contentDisposition(file, name, dispositionType string) (err error) { - c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name)) - c.File(file) - return +func (c *context) contentDisposition(file, name, dispositionType string) error { + c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name)) + return c.File(file) } func (c *context) NoContent(code int) error { diff --git a/vendor/github.com/labstack/echo/echo.go b/vendor/github.com/labstack/echo/echo.go index 5b43fff0c..9b7f17311 100644 --- a/vendor/github.com/labstack/echo/echo.go +++ b/vendor/github.com/labstack/echo/echo.go @@ -38,6 +38,7 @@ package echo import ( "bytes" + stdContext "context" "crypto/tls" "errors" "fmt" @@ -45,6 +46,7 @@ import ( stdLog "log" "net" "net/http" + "net/url" "path" "path/filepath" "reflect" @@ -72,34 +74,36 @@ type ( TLSServer *http.Server Listener net.Listener TLSListener net.Listener + AutoTLSManager autocert.Manager DisableHTTP2 bool Debug bool + HideBanner bool + HidePort bool HTTPErrorHandler HTTPErrorHandler Binder Binder Validator Validator Renderer Renderer - AutoTLSManager autocert.Manager - Mutex sync.RWMutex Logger Logger } // Route contains a handler and information for matching against requests. Route struct { - Method string - Path string - Handler string + Method string `json:"method"` + Path string `json:"path"` + Name string `json:"name"` } // HTTPError represents an error that occurred while handling a request. HTTPError struct { - Code int - Message interface{} + Code int + Message interface{} + Internal error // Stores the error returned by an external dependency } // MiddlewareFunc defines a function to process middleware. MiddlewareFunc func(HandlerFunc) HandlerFunc - // HandlerFunc defines a function to server HTTP requests. + // HandlerFunc defines a function to serve HTTP requests. HandlerFunc func(Context) error // HTTPErrorHandler is a centralized HTTP error handler. @@ -120,21 +124,22 @@ type ( // i is the interface for Echo and Group. i interface { - GET(string, HandlerFunc, ...MiddlewareFunc) + GET(string, HandlerFunc, ...MiddlewareFunc) *Route } ) // HTTP methods const ( - CONNECT = "CONNECT" - DELETE = "DELETE" - GET = "GET" - HEAD = "HEAD" - OPTIONS = "OPTIONS" - PATCH = "PATCH" - POST = "POST" - PUT = "PUT" - TRACE = "TRACE" + CONNECT = "CONNECT" + DELETE = "DELETE" + GET = "GET" + HEAD = "HEAD" + OPTIONS = "OPTIONS" + PATCH = "PATCH" + POST = "POST" + PROPFIND = "PROPFIND" + PUT = "PUT" + TRACE = "TRACE" ) // MIME types @@ -164,28 +169,35 @@ const ( // Headers const ( - HeaderAcceptEncoding = "Accept-Encoding" - HeaderAllow = "Allow" - HeaderAuthorization = "Authorization" - HeaderContentDisposition = "Content-Disposition" - HeaderContentEncoding = "Content-Encoding" - HeaderContentLength = "Content-Length" - HeaderContentType = "Content-Type" - HeaderCookie = "Cookie" - HeaderSetCookie = "Set-Cookie" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderLastModified = "Last-Modified" - HeaderLocation = "Location" - HeaderUpgrade = "Upgrade" - HeaderVary = "Vary" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderXForwardedProto = "X-Forwarded-Proto" - HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" - HeaderXForwardedFor = "X-Forwarded-For" - HeaderXRealIP = "X-Real-IP" - HeaderXRequestID = "X-Request-ID" - HeaderServer = "Server" - HeaderOrigin = "Origin" + HeaderAccept = "Accept" + HeaderAcceptEncoding = "Accept-Encoding" + HeaderAllow = "Allow" + HeaderAuthorization = "Authorization" + HeaderContentDisposition = "Content-Disposition" + HeaderContentEncoding = "Content-Encoding" + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" + HeaderCookie = "Cookie" + HeaderSetCookie = "Set-Cookie" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderUpgrade = "Upgrade" + HeaderVary = "Vary" + HeaderWWWAuthenticate = "WWW-Authenticate" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedProto = "X-Forwarded-Proto" + HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderXForwardedSsl = "X-Forwarded-Ssl" + HeaderXUrlScheme = "X-Url-Scheme" + HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" + HeaderXRealIP = "X-Real-IP" + HeaderXRequestID = "X-Request-ID" + HeaderXRequestedWith = "X-Requested-With" + HeaderServer = "Server" + HeaderOrigin = "Origin" + + // Access control HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" @@ -204,6 +216,22 @@ const ( HeaderXCSRFToken = "X-CSRF-Token" ) +const ( + Version = "3.3.6" + website = "https://echo.labstack.com" + // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo + banner = ` + ____ __ + / __/___/ / ___ + / _// __/ _ \/ _ \ +/___/\__/_//_/\___/ %s +High performance, minimalist Go web framework +%s +____________________________________O/_______ + O\ +` +) + var ( methods = [...]string{ CONNECT, @@ -213,6 +241,7 @@ var ( OPTIONS, PATCH, POST, + PROPFIND, PUT, TRACE, } @@ -226,10 +255,10 @@ var ( ErrForbidden = NewHTTPError(http.StatusForbidden) ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) - ErrValidatorNotRegistered = errors.New("Validator not registered") - ErrRendererNotRegistered = errors.New("Renderer not registered") - ErrInvalidRedirectCode = errors.New("Invalid redirect status code") - ErrCookieNotFound = errors.New("Cookie not found") + ErrValidatorNotRegistered = errors.New("validator not registered") + ErrRendererNotRegistered = errors.New("renderer not registered") + ErrInvalidRedirectCode = errors.New("invalid redirect status code") + ErrCookieNotFound = errors.New("cookie not found") ) // Error handlers @@ -259,7 +288,7 @@ func New() (e *Echo) { e.TLSServer.Handler = e e.HTTPErrorHandler = e.DefaultHTTPErrorHandler e.Binder = &DefaultBinder{} - e.Logger.SetLevel(log.OFF) + e.Logger.SetLevel(log.ERROR) e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0) e.pool.New = func() interface{} { return e.NewContext(nil, nil) @@ -296,6 +325,9 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { if he, ok := err.(*HTTPError); ok { code = he.Code msg = he.Message + if he.Internal != nil { + err = fmt.Errorf("%v, %v", err, he.Internal) + } } else if e.Debug { msg = err.Error() } else { @@ -305,19 +337,17 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { msg = Map{"message": msg} } + // Send response if !c.Response().Committed { if c.Request().Method == HEAD { // Issue #608 - if err := c.NoContent(code); err != nil { - goto ERROR - } + err = c.NoContent(code) } else { - if err := c.JSON(code, msg); err != nil { - goto ERROR - } + err = c.JSON(code, msg) + } + if err != nil { + e.Logger.Error(err) } } -ERROR: - e.Logger.Error(err) } // Pre adds middleware to the chain which is run before router. @@ -332,104 +362,114 @@ func (e *Echo) Use(middleware ...MiddlewareFunc) { // CONNECT registers a new CONNECT route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(CONNECT, path, h, m...) +func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(CONNECT, path, h, m...) } // DELETE registers a new DELETE route for a path with matching handler in the router // with optional route-level middleware. -func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(DELETE, path, h, m...) +func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(DELETE, path, h, m...) } // GET registers a new GET route for a path with matching handler in the router // with optional route-level middleware. -func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(GET, path, h, m...) +func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(GET, path, h, m...) } // HEAD registers a new HEAD route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(HEAD, path, h, m...) +func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(HEAD, path, h, m...) } // OPTIONS registers a new OPTIONS route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(OPTIONS, path, h, m...) +func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(OPTIONS, path, h, m...) } // PATCH registers a new PATCH route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(PATCH, path, h, m...) +func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(PATCH, path, h, m...) } // POST registers a new POST route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(POST, path, h, m...) +func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(POST, path, h, m...) } // PUT registers a new PUT route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(PUT, path, h, m...) +func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(PUT, path, h, m...) } // TRACE registers a new TRACE route for a path with matching handler in the // router with optional route-level middleware. -func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) { - e.add(TRACE, path, h, m...) +func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(TRACE, path, h, m...) } // Any registers a new route for all HTTP methods and path with matching handler // in the router with optional route-level middleware. -func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) { - for _, m := range methods { - e.add(m, path, handler, middleware...) +func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = e.Add(m, path, handler, middleware...) } + return routes } // Match registers a new route for multiple HTTP methods and path with matching // handler in the router with optional route-level middleware. -func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { - for _, m := range methods { - e.add(m, path, handler, middleware...) +func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = e.Add(m, path, handler, middleware...) } + return routes } // Static registers a new route with path prefix to serve static files from the // provided root directory. -func (e *Echo) Static(prefix, root string) { +func (e *Echo) Static(prefix, root string) *Route { if root == "" { root = "." // For security we want to restrict to CWD. } - static(e, prefix, root) + return static(e, prefix, root) } -func static(i i, prefix, root string) { +func static(i i, prefix, root string) *Route { h := func(c Context) error { - name := filepath.Join(root, path.Clean("/"+c.Param("*"))) // "/"+ for security + p, err := url.PathUnescape(c.Param("*")) + if err != nil { + return err + } + name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security return c.File(name) } i.GET(prefix, h) if prefix == "/" { - i.GET(prefix+"*", h) - } else { - i.GET(prefix+"/*", h) + return i.GET(prefix+"*", h) } + + return i.GET(prefix+"/*", h) } -// File registers a new route with path to serve a static file. -func (e *Echo) File(path, file string) { - e.GET(path, func(c Context) error { +// File registers a new route with path to serve a static file with optional route-level middleware. +func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route { + return e.GET(path, func(c Context) error { return c.File(file) - }) + }, m...) } -func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { +// Add registers a new route for an HTTP method and path with matching handler +// in the router with optional route-level middleware. +func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { name := handlerName(handler) e.router.Add(method, path, func(c Context) error { h := handler @@ -439,12 +479,13 @@ func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...Middl } return h(c) }) - r := Route{ - Method: method, - Path: path, - Handler: name, + r := &Route{ + Method: method, + Path: path, + Name: name, } e.router.routes[method+path] = r + return r } // Group creates a new router group with prefix and optional group-level middleware. @@ -456,12 +497,22 @@ func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) { // URI generates a URI from handler. func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string { + name := handlerName(handler) + return e.Reverse(name, params...) +} + +// URL is an alias for `URI` function. +func (e *Echo) URL(h HandlerFunc, params ...interface{}) string { + return e.URI(h, params...) +} + +// Reverse generates an URL from route name and provided parameters. +func (e *Echo) Reverse(name string, params ...interface{}) string { uri := new(bytes.Buffer) ln := len(params) n := 0 - name := handlerName(handler) for _, r := range e.router.routes { - if r.Handler == name { + if r.Name == name { for i, l := 0, len(r.Path); i < l; i++ { if r.Path[i] == ':' && n < ln { for ; i < l && r.Path[i] != '/'; i++ { @@ -479,14 +530,9 @@ func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string { return uri.String() } -// URL is an alias for `URI` function. -func (e *Echo) URL(h HandlerFunc, params ...interface{}) string { - return e.URI(h, params...) -} - // Routes returns the registered routes. -func (e *Echo) Routes() []Route { - routes := []Route{} +func (e *Echo) Routes() []*Route { + routes := make([]*Route, 0, len(e.router.routes)) for _, v := range e.router.routes { routes = append(routes, v) } @@ -507,39 +553,39 @@ func (e *Echo) ReleaseContext(c Context) { // ServeHTTP implements `http.Handler` interface, which serves HTTP requests. func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Acquire lock - e.Mutex.RLock() - defer e.Mutex.RUnlock() - // Acquire context c := e.pool.Get().(*context) - defer e.pool.Put(c) c.Reset(r, w) - // Middleware - h := func(c Context) error { - method := r.Method - path := r.URL.RawPath - if path == "" { - path = r.URL.Path - } - e.router.Find(method, path, c) - h := c.Handler() + h := NotFoundHandler + + if e.premiddleware == nil { + e.router.Find(r.Method, getPath(r), c) + h = c.Handler() for i := len(e.middleware) - 1; i >= 0; i-- { h = e.middleware[i](h) } - return h(c) - } - - // Premiddleware - for i := len(e.premiddleware) - 1; i >= 0; i-- { - h = e.premiddleware[i](h) + } else { + h = func(c Context) error { + e.router.Find(r.Method, getPath(r), c) + h := c.Handler() + for i := len(e.middleware) - 1; i >= 0; i-- { + h = e.middleware[i](h) + } + return h(c) + } + for i := len(e.premiddleware) - 1; i >= 0; i-- { + h = e.premiddleware[i](h) + } } // Execute chain if err := h(c); err != nil { e.HTTPErrorHandler(err, c) } + + // Release context + e.pool.Put(c) } // Start starts an HTTP server. @@ -565,6 +611,10 @@ func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) { // StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org. func (e *Echo) StartAutoTLS(address string) error { + if e.Listener == nil { + go http.ListenAndServe(":http", e.AutoTLSManager.HTTPHandler(nil)) + } + s := e.TLSServer s.TLSConfig = new(tls.Config) s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate @@ -584,8 +634,15 @@ func (e *Echo) startTLS(address string) error { func (e *Echo) StartServer(s *http.Server) (err error) { // Setup e.colorer.SetOutput(e.Logger.Output()) - s.Handler = e s.ErrorLog = e.stdLogger + s.Handler = e + if e.Debug { + e.Logger.SetLevel(log.DEBUG) + } + + if !e.HideBanner { + e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website)) + } if s.TLSConfig == nil { if e.Listener == nil { @@ -594,7 +651,9 @@ func (e *Echo) StartServer(s *http.Server) (err error) { return err } } - e.colorer.Printf("⇛ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } return s.Serve(e.Listener) } if e.TLSListener == nil { @@ -604,10 +663,30 @@ func (e *Echo) StartServer(s *http.Server) (err error) { } e.TLSListener = tls.NewListener(l, s.TLSConfig) } - e.colorer.Printf("⇛ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) + if !e.HidePort { + e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) + } return s.Serve(e.TLSListener) } +// Close immediately stops the server. +// It internally calls `http.Server#Close()`. +func (e *Echo) Close() error { + if err := e.TLSServer.Close(); err != nil { + return err + } + return e.Server.Close() +} + +// Shutdown stops server the gracefully. +// It internally calls `http.Server#Shutdown()`. +func (e *Echo) Shutdown(ctx stdContext.Context) error { + if err := e.TLSServer.Shutdown(ctx); err != nil { + return err + } + return e.Server.Shutdown(ctx) +} + // NewHTTPError creates a new HTTPError instance. func NewHTTPError(code int, message ...interface{}) *HTTPError { he := &HTTPError{Code: code, Message: http.StatusText(code)} @@ -619,7 +698,7 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError { // Error makes it compatible with `error` interface. func (he *HTTPError) Error() string { - return fmt.Sprintf("code=%d, message=%s", he.Code, he.Message) + return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) } // WrapHandler wraps `http.Handler` into `echo.HandlerFunc`. @@ -643,6 +722,14 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc { } } +func getPath(r *http.Request) string { + path := r.URL.RawPath + if path == "" { + path = r.URL.Path + } + return path +} + func handlerName(h HandlerFunc) string { t := reflect.ValueOf(h).Type() if t.Kind() == reflect.Func { @@ -651,6 +738,11 @@ func handlerName(h HandlerFunc) string { return t.String() } +// // PathUnescape is wraps `url.PathUnescape` +// func PathUnescape(s string) (string, error) { +// return url.PathUnescape(s) +// } + // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. It's used by ListenAndServe and ListenAndServeTLS so // dead TCP connections (e.g. closing laptop mid-download) eventually diff --git a/vendor/github.com/labstack/echo/echo_go1.8.go b/vendor/github.com/labstack/echo/echo_go1.8.go deleted file mode 100644 index 340bed705..000000000 --- a/vendor/github.com/labstack/echo/echo_go1.8.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build go1.8 - -package echo - -import ( - stdContext "context" -) - -// Close immediately stops the server. -// It internally calls `http.Server#Close()`. -func (e *Echo) Close() error { - if err := e.TLSServer.Close(); err != nil { - return err - } - return e.Server.Close() -} - -// Shutdown stops server the gracefully. -// It internally calls `http.Server#Shutdown()`. -func (e *Echo) Shutdown(ctx stdContext.Context) error { - if err := e.TLSServer.Shutdown(ctx); err != nil { - return err - } - return e.Server.Shutdown(ctx) -} diff --git a/vendor/github.com/labstack/echo/glide.lock b/vendor/github.com/labstack/echo/glide.lock deleted file mode 100644 index c51f7aac0..000000000 --- a/vendor/github.com/labstack/echo/glide.lock +++ /dev/null @@ -1,92 +0,0 @@ -hash: 3de2a96bbdc145cce325de2a482111b0524cc330f60a4fbc781a08ed3b879e58 -updated: 2017-01-28T10:22:00.230111692-08:00 -imports: -- name: github.com/daaku/go.zipexe - version: a5fe2436ffcb3236e175e5149162b41cd28bd27d -- name: github.com/dgrijalva/jwt-go - version: a601269ab70c205d26370c16f7c81e9017c14e04 -- name: github.com/facebookgo/clock - version: 600d898af40aa09a7a93ecb9265d87b0504b6f03 -- name: github.com/facebookgo/grace - version: 5729e484473f52048578af1b80d0008c7024089b - subpackages: - - gracehttp - - gracenet -- name: github.com/facebookgo/httpdown - version: a3b1354551a26449fbe05f5d855937f6e7acbd71 -- name: github.com/facebookgo/stats - version: 1b76add642e42c6ffba7211ad7b3939ce654526e -- name: github.com/GeertJohan/go.rice - version: 4bbccbfa39e784796e483270451217d3369ecfbe - subpackages: - - embedded -- name: github.com/golang/protobuf - version: 8ee79997227bf9b34611aee7946ae64735e6fd93 - subpackages: - - proto -- name: github.com/gorilla/websocket - version: c36f2fe5c330f0ac404b616b96c438b8616b1aaf -- name: github.com/kardianos/osext - version: c2c54e542fb797ad986b31721e1baedf214ca413 -- name: github.com/labstack/gommon - version: f72d3c883f8ea180da8f085dd320804c41332ad1 - subpackages: - - bytes - - color - - log - - random -- name: github.com/mattn/go-colorable - version: d228849504861217f796da67fae4f6e347643f15 -- name: github.com/mattn/go-isatty - version: 30a891c33c7cde7b02a981314b4228ec99380cca -- name: github.com/tylerb/graceful - version: 0e9129e9c6d47da90dc0c188b26bd7bb1dab53cd -- name: github.com/valyala/bytebufferpool - version: e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7 -- name: github.com/valyala/fasttemplate - version: d090d65668a286d9a180d43a19dfdc5dcad8fe88 -- name: golang.org/x/crypto - version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd - subpackages: - - acme - - acme/autocert -- name: golang.org/x/net - version: f2499483f923065a842d38eb4c7f1927e6fc6e6d - subpackages: - - context - - context/ctxhttp - - websocket -- name: golang.org/x/sys - version: d75a52659825e75fff6158388dddc6a5b04f9ba5 - subpackages: - - unix -- name: google.golang.org/appengine - version: a2c54d2174c17540446e0ced57d9d459af61bc1c - subpackages: - - internal - - internal/app_identity - - internal/base - - internal/datastore - - internal/log - - internal/modules - - internal/remote_api -- name: gopkg.in/mgo.v2 - version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 - subpackages: - - bson - - internal/json - - internal/sasl - - internal/scram -testImports: -- name: github.com/davecgh/go-spew - version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 - subpackages: - - spew -- name: github.com/pmezard/go-difflib - version: d8ed2627bdf02c080bf22230dbb337003b7aba2d - subpackages: - - difflib -- name: github.com/stretchr/testify - version: 2402e8e7a02fc811447d11f881aa9746cdc57983 - subpackages: - - assert diff --git a/vendor/github.com/labstack/echo/glide.yaml b/vendor/github.com/labstack/echo/glide.yaml deleted file mode 100644 index 9ac9d1c82..000000000 --- a/vendor/github.com/labstack/echo/glide.yaml +++ /dev/null @@ -1,30 +0,0 @@ -package: github.com/labstack/echo -import: -- package: github.com/GeertJohan/go.rice -- package: github.com/dgrijalva/jwt-go -- package: github.com/facebookgo/grace - subpackages: - - gracehttp -- package: github.com/gorilla/websocket -- package: github.com/labstack/gommon - subpackages: - - bytes - - color - - log - - random -- package: github.com/tylerb/graceful -- package: github.com/valyala/fasttemplate -- package: golang.org/x/crypto - subpackages: - - acme/autocert -- package: golang.org/x/net - subpackages: - - websocket -- package: google.golang.org/appengine -- package: gopkg.in/mgo.v2 - subpackages: - - bson -testImport: -- package: github.com/stretchr/testify - subpackages: - - assert diff --git a/vendor/github.com/labstack/echo/go.mod b/vendor/github.com/labstack/echo/go.mod new file mode 100644 index 000000000..ab42dbafe --- /dev/null +++ b/vendor/github.com/labstack/echo/go.mod @@ -0,0 +1,15 @@ +module github.com/labstack/echo + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.1 + github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a // indirect + github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 + golang.org/x/crypto v0.0.0-20180312195533-182114d58262 + golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc // indirect +) diff --git a/vendor/github.com/labstack/echo/go.sum b/vendor/github.com/labstack/echo/go.sum new file mode 100644 index 000000000..53a0e7431 --- /dev/null +++ b/vendor/github.com/labstack/echo/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec h1:aYKwS4iCpqxskMuvI8+Byq0CxnnWHO/xuLk2pZJ96tY= +github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a h1:AOcehBWpFhYPYw0ioDTppQzgI8pAAahVCiMSKTp9rbo= +github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= +github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= +golang.org/x/crypto v0.0.0-20180312195533-182114d58262 h1:1NLVUmR8SQ7cNNA5Vo7ronpXbR+5A+9IwIC/bLE7D8Y= +golang.org/x/crypto v0.0.0-20180312195533-182114d58262/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc h1:eCzBKjjvhDDXMoH5l7eA+YK1PEtyJ2QLj3f4IArr+zg= +golang.org/x/sys v0.0.0-20180312081825-c28acc882ebc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/labstack/echo/group.go b/vendor/github.com/labstack/echo/group.go index 799a8f90b..5257e83ca 100644 --- a/vendor/github.com/labstack/echo/group.go +++ b/vendor/github.com/labstack/echo/group.go @@ -20,73 +20,79 @@ func (g *Group) Use(middleware ...MiddlewareFunc) { g.middleware = append(g.middleware, middleware...) // Allow all requests to reach the group as they might get dropped if router // doesn't find a match, making none of the group middleware process. - g.echo.Any(path.Clean(g.prefix+"/*"), func(c Context) error { - return ErrNotFound - }, g.middleware...) + for _, p := range []string{"", "/*"} { + g.echo.Any(path.Clean(g.prefix+p), func(c Context) error { + return NotFoundHandler(c) + }, g.middleware...) + } } // CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. -func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(CONNECT, path, h, m...) +func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(CONNECT, path, h, m...) } // DELETE implements `Echo#DELETE()` for sub-routes within the Group. -func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(DELETE, path, h, m...) +func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(DELETE, path, h, m...) } // GET implements `Echo#GET()` for sub-routes within the Group. -func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(GET, path, h, m...) +func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(GET, path, h, m...) } // HEAD implements `Echo#HEAD()` for sub-routes within the Group. -func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(HEAD, path, h, m...) +func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(HEAD, path, h, m...) } // OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group. -func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(OPTIONS, path, h, m...) +func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(OPTIONS, path, h, m...) } // PATCH implements `Echo#PATCH()` for sub-routes within the Group. -func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(PATCH, path, h, m...) +func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(PATCH, path, h, m...) } // POST implements `Echo#POST()` for sub-routes within the Group. -func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(POST, path, h, m...) +func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(POST, path, h, m...) } // PUT implements `Echo#PUT()` for sub-routes within the Group. -func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(PUT, path, h, m...) +func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(PUT, path, h, m...) } // TRACE implements `Echo#TRACE()` for sub-routes within the Group. -func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) { - g.add(TRACE, path, h, m...) +func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(TRACE, path, h, m...) } // Any implements `Echo#Any()` for sub-routes within the Group. -func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) { - for _, m := range methods { - g.add(m, path, handler, middleware...) +func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = g.Add(m, path, handler, middleware...) } + return routes } // Match implements `Echo#Match()` for sub-routes within the Group. -func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { - for _, m := range methods { - g.add(m, path, handler, middleware...) +func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = g.Add(m, path, handler, middleware...) } + return routes } // Group creates a new sub-group with prefix and optional sub-group-level middleware. func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group { - m := []MiddlewareFunc{} + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) m = append(m, g.middleware...) m = append(m, middleware...) return g.echo.Group(g.prefix+prefix, m...) @@ -102,12 +108,13 @@ func (g *Group) File(path, file string) { g.echo.File(g.prefix+path, file) } -func (g *Group) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { +// Add implements `Echo#Add()` for sub-routes within the Group. +func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { // Combine into a new slice to avoid accidentally passing the same slice for // multiple routes, which would lead to later add() calls overwriting the // middleware from earlier calls. - m := []MiddlewareFunc{} + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) m = append(m, g.middleware...) m = append(m, middleware...) - g.echo.add(method, g.prefix+path, handler, m...) + return g.echo.Add(method, g.prefix+path, handler, m...) } diff --git a/vendor/github.com/labstack/echo/middleware/basic_auth.go b/vendor/github.com/labstack/echo/middleware/basic_auth.go index 97c3ae9cc..e6c963245 100644 --- a/vendor/github.com/labstack/echo/middleware/basic_auth.go +++ b/vendor/github.com/labstack/echo/middleware/basic_auth.go @@ -3,6 +3,7 @@ package middleware import ( "encoding/base64" "strconv" + "strings" "github.com/labstack/echo" ) @@ -23,11 +24,11 @@ type ( } // BasicAuthValidator defines a function to validate BasicAuth credentials. - BasicAuthValidator func(string, string, echo.Context) (error, bool) + BasicAuthValidator func(string, string, echo.Context) (bool, error) ) const ( - basic = "Basic" + basic = "basic" defaultRealm = "Restricted" ) @@ -54,7 +55,7 @@ func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc { func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { // Defaults if config.Validator == nil { - panic("basic-auth middleware requires a validator function") + panic("echo: basic-auth middleware requires a validator function") } if config.Skipper == nil { config.Skipper = DefaultBasicAuthConfig.Skipper @@ -72,7 +73,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { auth := c.Request().Header.Get(echo.HeaderAuthorization) l := len(basic) - if len(auth) > l+1 && auth[:l] == basic { + if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic { b, err := base64.StdEncoding.DecodeString(auth[l+1:]) if err != nil { return err @@ -81,20 +82,19 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { for i := 0; i < len(cred); i++ { if cred[i] == ':' { // Verify credentials - err, valid := config.Validator(cred[:i], cred[i+1:], c) + valid, err := config.Validator(cred[:i], cred[i+1:], c) if err != nil { return err } else if valid { return next(c) } + break } } } - realm := "" - if config.Realm == defaultRealm { - realm = defaultRealm - } else { + realm := defaultRealm + if config.Realm != defaultRealm { realm = strconv.Quote(config.Realm) } diff --git a/vendor/github.com/labstack/echo/middleware/body_dump.go b/vendor/github.com/labstack/echo/middleware/body_dump.go new file mode 100644 index 000000000..e64e5e112 --- /dev/null +++ b/vendor/github.com/labstack/echo/middleware/body_dump.go @@ -0,0 +1,111 @@ +package middleware + +import ( + "bufio" + "bytes" + "io" + "io/ioutil" + "net" + "net/http" + + "github.com/labstack/echo" +) + +type ( + // BodyDumpConfig defines the config for BodyDump middleware. + BodyDumpConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Handler receives request and response payload. + // Required. + Handler BodyDumpHandler + } + + // BodyDumpHandler receives the request and response payload. + BodyDumpHandler func(echo.Context, []byte, []byte) + + bodyDumpResponseWriter struct { + io.Writer + http.ResponseWriter + } +) + +var ( + // DefaultBodyDumpConfig is the default BodyDump middleware config. + DefaultBodyDumpConfig = BodyDumpConfig{ + Skipper: DefaultSkipper, + } +) + +// BodyDump returns a BodyDump middleware. +// +// BodyLimit middleware captures the request and response payload and calls the +// registered handler. +func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc { + c := DefaultBodyDumpConfig + c.Handler = handler + return BodyDumpWithConfig(c) +} + +// BodyDumpWithConfig returns a BodyDump middleware with config. +// See: `BodyDump()`. +func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc { + // Defaults + if config.Handler == nil { + panic("echo: body-dump middleware requires a handler function") + } + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + // Request + reqBody := []byte{} + if c.Request().Body != nil { // Read + reqBody, _ = ioutil.ReadAll(c.Request().Body) + } + c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset + + // Response + resBody := new(bytes.Buffer) + mw := io.MultiWriter(c.Response().Writer, resBody) + writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer} + c.Response().Writer = writer + + if err = next(c); err != nil { + c.Error(err) + } + + // Callback + config.Handler(c, reqBody, resBody.Bytes()) + + return + } + } +} + +func (w *bodyDumpResponseWriter) WriteHeader(code int) { + w.ResponseWriter.WriteHeader(code) +} + +func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func (w *bodyDumpResponseWriter) Flush() { + w.ResponseWriter.(http.Flusher).Flush() +} + +func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return w.ResponseWriter.(http.Hijacker).Hijack() +} + +func (w *bodyDumpResponseWriter) CloseNotify() <-chan bool { + return w.ResponseWriter.(http.CloseNotifier).CloseNotify() +} diff --git a/vendor/github.com/labstack/echo/middleware/body_limit.go b/vendor/github.com/labstack/echo/middleware/body_limit.go index a2ff8d629..c83f57e1f 100644 --- a/vendor/github.com/labstack/echo/middleware/body_limit.go +++ b/vendor/github.com/labstack/echo/middleware/body_limit.go @@ -17,7 +17,7 @@ type ( // Maximum allowed size for a request body, it can be specified // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. - Limit string `json:"limit"` + Limit string `yaml:"limit"` limit int64 } @@ -30,7 +30,7 @@ type ( ) var ( - // DefaultBodyLimitConfig is the default Gzip middleware config. + // DefaultBodyLimitConfig is the default BodyLimit middleware config. DefaultBodyLimitConfig = BodyLimitConfig{ Skipper: DefaultSkipper, } @@ -60,7 +60,7 @@ func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc { limit, err := bytes.Parse(config.Limit) if err != nil { - panic(fmt.Errorf("invalid body-limit=%s", config.Limit)) + panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit)) } config.limit = limit pool := limitedReaderPool(config) @@ -105,6 +105,7 @@ func (r *limitedReader) Close() error { func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) { r.reader = reader r.context = context + r.read = 0 } func limitedReaderPool(c BodyLimitConfig) sync.Pool { diff --git a/vendor/github.com/labstack/echo/middleware/compress.go b/vendor/github.com/labstack/echo/middleware/compress.go index cffadbd1c..b876009cb 100644 --- a/vendor/github.com/labstack/echo/middleware/compress.go +++ b/vendor/github.com/labstack/echo/middleware/compress.go @@ -20,7 +20,7 @@ type ( // Gzip compression level. // Optional. Default value -1. - Level int `json:"level"` + Level int `yaml:"level"` } gzipResponseWriter struct { @@ -67,7 +67,7 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc { res := c.Response() res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) { - res.Header().Add(echo.HeaderContentEncoding, gzipScheme) // Issue #806 + res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806 rw := res.Writer w, err := gzip.NewWriterLevel(rw, config.Level) if err != nil { @@ -98,6 +98,7 @@ func (w *gzipResponseWriter) WriteHeader(code int) { if code == http.StatusNoContent { // Issue #489 w.ResponseWriter.Header().Del(echo.HeaderContentEncoding) } + w.Header().Del(echo.HeaderContentLength) // Issue #444 w.ResponseWriter.WriteHeader(code) } diff --git a/vendor/github.com/labstack/echo/middleware/cors.go b/vendor/github.com/labstack/echo/middleware/cors.go index c35fc36ce..a6126a69e 100644 --- a/vendor/github.com/labstack/echo/middleware/cors.go +++ b/vendor/github.com/labstack/echo/middleware/cors.go @@ -16,34 +16,34 @@ type ( // AllowOrigin defines a list of origins that may access the resource. // Optional. Default value []string{"*"}. - AllowOrigins []string `json:"allow_origins"` + AllowOrigins []string `yaml:"allow_origins"` // AllowMethods defines a list methods allowed when accessing the resource. // This is used in response to a preflight request. // Optional. Default value DefaultCORSConfig.AllowMethods. - AllowMethods []string `json:"allow_methods"` + AllowMethods []string `yaml:"allow_methods"` // AllowHeaders defines a list of request headers that can be used when // making the actual request. This in response to a preflight request. // Optional. Default value []string{}. - AllowHeaders []string `json:"allow_headers"` + AllowHeaders []string `yaml:"allow_headers"` // AllowCredentials indicates whether or not the response to the request // can be exposed when the credentials flag is true. When used as part of // a response to a preflight request, this indicates whether or not the // actual request can be made using credentials. // Optional. Default value false. - AllowCredentials bool `json:"allow_credentials"` + AllowCredentials bool `yaml:"allow_credentials"` // ExposeHeaders defines a whitelist headers that clients are allowed to // access. // Optional. Default value []string{}. - ExposeHeaders []string `json:"expose_headers"` + ExposeHeaders []string `yaml:"expose_headers"` // MaxAge indicates how long (in seconds) the results of a preflight request // can be cached. // Optional. Default value 0. - MaxAge int `json:"max_age"` + MaxAge int `yaml:"max_age"` } ) @@ -94,6 +94,10 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc { // Check allowed origins for _, o := range config.AllowOrigins { + if o == "*" && config.AllowCredentials { + allowOrigin = origin + break + } if o == "*" || o == origin { allowOrigin = o break diff --git a/vendor/github.com/labstack/echo/middleware/csrf.go b/vendor/github.com/labstack/echo/middleware/csrf.go index 5bbeecb4a..5d1f4671c 100644 --- a/vendor/github.com/labstack/echo/middleware/csrf.go +++ b/vendor/github.com/labstack/echo/middleware/csrf.go @@ -18,7 +18,7 @@ type ( Skipper Skipper // TokenLength is the length of the generated token. - TokenLength uint8 `json:"token_length"` + TokenLength uint8 `yaml:"token_length"` // Optional. Default value 32. // TokenLookup is a string in the form of ":" that is used @@ -28,35 +28,35 @@ type ( // - "header:" // - "form:" // - "query:" - TokenLookup string `json:"token_lookup"` + TokenLookup string `yaml:"token_lookup"` // Context key to store generated CSRF token into context. // Optional. Default value "csrf". - ContextKey string `json:"context_key"` + ContextKey string `yaml:"context_key"` // Name of the CSRF cookie. This cookie will store CSRF token. // Optional. Default value "csrf". - CookieName string `json:"cookie_name"` + CookieName string `yaml:"cookie_name"` // Domain of the CSRF cookie. // Optional. Default value none. - CookieDomain string `json:"cookie_domain"` + CookieDomain string `yaml:"cookie_domain"` // Path of the CSRF cookie. // Optional. Default value none. - CookiePath string `json:"cookie_path"` + CookiePath string `yaml:"cookie_path"` // Max age (in seconds) of the CSRF cookie. // Optional. Default value 86400 (24hr). - CookieMaxAge int `json:"cookie_max_age"` + CookieMaxAge int `yaml:"cookie_max_age"` // Indicates if CSRF cookie is secure. // Optional. Default value false. - CookieSecure bool `json:"cookie_secure"` + CookieSecure bool `yaml:"cookie_secure"` // Indicates if CSRF cookie is HTTP only. // Optional. Default value false. - CookieHTTPOnly bool `json:"cookie_http_only"` + CookieHTTPOnly bool `yaml:"cookie_http_only"` } // csrfTokenExtractor defines a function that takes `echo.Context` and returns @@ -126,8 +126,8 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { k, err := c.Cookie(config.CookieName) token := "" + // Generate token if err != nil { - // Generate token token = random.String(config.TokenLength) } else { // Reuse token @@ -143,7 +143,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if !validateCSRFToken(token, clientToken) { - return echo.NewHTTPError(http.StatusForbidden, "Invalid csrf token") + return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token") } } @@ -187,7 +187,7 @@ func csrfTokenFromForm(param string) csrfTokenExtractor { return func(c echo.Context) (string, error) { token := c.FormValue(param) if token == "" { - return "", errors.New("Missing csrf token in the form parameter") + return "", errors.New("missing csrf token in the form parameter") } return token, nil } @@ -199,7 +199,7 @@ func csrfTokenFromQuery(param string) csrfTokenExtractor { return func(c echo.Context) (string, error) { token := c.QueryParam(param) if token == "" { - return "", errors.New("Missing csrf token in the query string") + return "", errors.New("missing csrf token in the query string") } return token, nil } diff --git a/vendor/github.com/labstack/echo/middleware/jwt.go b/vendor/github.com/labstack/echo/middleware/jwt.go index b26587391..051c589cd 100644 --- a/vendor/github.com/labstack/echo/middleware/jwt.go +++ b/vendor/github.com/labstack/echo/middleware/jwt.go @@ -1,7 +1,6 @@ package middleware import ( - "errors" "fmt" "net/http" "reflect" @@ -17,6 +16,16 @@ type ( // Skipper defines a function to skip middleware. Skipper Skipper + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc BeforeFunc + + // SuccessHandler defines a function which is executed for a valid token. + SuccessHandler JWTSuccessHandler + + // ErrorHandler defines a function which is executed for an invalid token. + // It may be used to define a custom JWT error. + ErrorHandler JWTErrorHandler + // Signing key to validate token. // Required. SigningKey interface{} @@ -49,6 +58,12 @@ type ( keyFunc jwt.Keyfunc } + // JWTSuccessHandler defines a function which is executed for a valid token. + JWTSuccessHandler func(echo.Context) + + // JWTErrorHandler defines a function which is executed for an invalid token. + JWTErrorHandler func(error) error + jwtExtractor func(echo.Context) (string, error) ) @@ -57,6 +72,11 @@ const ( AlgorithmHS256 = "HS256" ) +// Errors +var ( + ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") +) + var ( // DefaultJWTConfig is the default JWT auth middleware config. DefaultJWTConfig = JWTConfig{ @@ -77,7 +97,7 @@ var ( // // See: https://jwt.io/introduction // See `JWTConfig.TokenLookup` -func JWT(key []byte) echo.MiddlewareFunc { +func JWT(key interface{}) echo.MiddlewareFunc { c := DefaultJWTConfig c.SigningKey = key return JWTWithConfig(c) @@ -91,7 +111,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { config.Skipper = DefaultJWTConfig.Skipper } if config.SigningKey == nil { - panic("jwt middleware requires signing key") + panic("echo: jwt middleware requires signing key") } if config.SigningMethod == "" { config.SigningMethod = DefaultJWTConfig.SigningMethod @@ -111,7 +131,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { config.keyFunc = func(t *jwt.Token) (interface{}, error) { // Check the signing method if t.Method.Alg() != config.SigningMethod { - return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"]) + return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"]) } return config.SigningKey, nil } @@ -132,24 +152,42 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { return next(c) } + if config.BeforeFunc != nil { + config.BeforeFunc(c) + } + auth, err := extractor(c) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + if config.ErrorHandler != nil { + return config.ErrorHandler(err) + } + return err } token := new(jwt.Token) // Issue #647, #656 if _, ok := config.Claims.(jwt.MapClaims); ok { token, err = jwt.Parse(auth, config.keyFunc) } else { - claims := reflect.ValueOf(config.Claims).Interface().(jwt.Claims) + t := reflect.ValueOf(config.Claims).Type().Elem() + claims := reflect.New(t).Interface().(jwt.Claims) token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc) } if err == nil && token.Valid { // Store user information from token into context. c.Set(config.ContextKey, token) + if config.SuccessHandler != nil { + config.SuccessHandler(c) + } return next(c) } - return echo.ErrUnauthorized + if config.ErrorHandler != nil { + return config.ErrorHandler(err) + } + return &echo.HTTPError{ + Code: http.StatusUnauthorized, + Message: "invalid or expired jwt", + Internal: err, + } } } } @@ -162,7 +200,7 @@ func jwtFromHeader(header string, authScheme string) jwtExtractor { if len(auth) > l+1 && auth[:l] == authScheme { return auth[l+1:], nil } - return "", errors.New("Missing or invalid jwt in the request header") + return "", ErrJWTMissing } } @@ -171,7 +209,7 @@ func jwtFromQuery(param string) jwtExtractor { return func(c echo.Context) (string, error) { token := c.QueryParam(param) if token == "" { - return "", errors.New("Missing jwt in the query string") + return "", ErrJWTMissing } return token, nil } @@ -182,7 +220,7 @@ func jwtFromCookie(name string) jwtExtractor { return func(c echo.Context) (string, error) { cookie, err := c.Cookie(name) if err != nil { - return "", errors.New("Missing jwt in the cookie") + return "", ErrJWTMissing } return cookie.Value, nil } diff --git a/vendor/github.com/labstack/echo/middleware/key_auth.go b/vendor/github.com/labstack/echo/middleware/key_auth.go index 25189c95f..c12f4ca9d 100644 --- a/vendor/github.com/labstack/echo/middleware/key_auth.go +++ b/vendor/github.com/labstack/echo/middleware/key_auth.go @@ -20,7 +20,8 @@ type ( // Possible values: // - "header:" // - "query:" - KeyLookup string `json:"key_lookup"` + // - "form:" + KeyLookup string `yaml:"key_lookup"` // AuthScheme to be used in the Authorization header. // Optional. Default value "Bearer". @@ -32,7 +33,7 @@ type ( } // KeyAuthValidator defines a function to validate KeyAuth credentials. - KeyAuthValidator func(string, echo.Context) (error, bool) + KeyAuthValidator func(string, echo.Context) (bool, error) keyExtractor func(echo.Context) (string, error) ) @@ -72,7 +73,7 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc { config.KeyLookup = DefaultKeyAuthConfig.KeyLookup } if config.Validator == nil { - panic("key-auth middleware requires a validator function") + panic("echo: key-auth middleware requires a validator function") } // Initialize @@ -81,6 +82,8 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc { switch parts[0] { case "query": extractor = keyFromQuery(parts[1]) + case "form": + extractor = keyFromForm(parts[1]) } return func(next echo.HandlerFunc) echo.HandlerFunc { @@ -94,7 +97,7 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc { if err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } - err, valid := config.Validator(key, c) + valid, err := config.Validator(key, c) if err != nil { return err } else if valid { @@ -111,14 +114,14 @@ func keyFromHeader(header string, authScheme string) keyExtractor { return func(c echo.Context) (string, error) { auth := c.Request().Header.Get(header) if auth == "" { - return "", errors.New("Missing key in request header") + return "", errors.New("missing key in request header") } if header == echo.HeaderAuthorization { l := len(authScheme) if len(auth) > l+1 && auth[:l] == authScheme { return auth[l+1:], nil } - return "", errors.New("Invalid key in the request header") + return "", errors.New("invalid key in the request header") } return auth, nil } @@ -129,7 +132,18 @@ func keyFromQuery(param string) keyExtractor { return func(c echo.Context) (string, error) { key := c.QueryParam(param) if key == "" { - return "", errors.New("Missing key in the query string") + return "", errors.New("missing key in the query string") + } + return key, nil + } +} + +// keyFromForm returns a `keyExtractor` that extracts key from the form. +func keyFromForm(param string) keyExtractor { + return func(c echo.Context) (string, error) { + key := c.FormValue(param) + if key == "" { + return "", errors.New("missing key in the form") } return key, nil } diff --git a/vendor/github.com/labstack/echo/middleware/logger.go b/vendor/github.com/labstack/echo/middleware/logger.go index 071d0f672..b778ee34e 100644 --- a/vendor/github.com/labstack/echo/middleware/logger.go +++ b/vendor/github.com/labstack/echo/middleware/logger.go @@ -26,15 +26,18 @@ type ( // - time_unix_nano // - time_rfc3339 // - time_rfc3339_nano + // - time_custom // - id (Request ID) // - remote_ip // - uri // - host // - method // - path + // - protocol // - referer // - user_agent // - status + // - error // - latency (In nanoseconds) // - latency_human (Human readable) // - bytes_in (Bytes received) @@ -46,7 +49,10 @@ type ( // Example "${remote_ip} ${status}" // // Optional. Default value DefaultLoggerConfig.Format. - Format string `json:"format"` + Format string `yaml:"format"` + + // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. + CustomTimeFormat string `yaml:"custom_time_format"` // Output is a writer where logs in JSON format are written. // Optional. Default value os.Stdout. @@ -63,11 +69,12 @@ var ( DefaultLoggerConfig = LoggerConfig{ Skipper: DefaultSkipper, Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}",` + - `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` + + `"method":"${method}","uri":"${uri}","status":${status},"error":"${error}","latency":${latency},` + `"latency_human":"${latency_human}","bytes_in":${bytes_in},` + `"bytes_out":${bytes_out}}` + "\n", - Output: os.Stdout, - colorer: color.New(), + CustomTimeFormat: "2006-01-02 15:04:05.00000", + Output: os.Stdout, + colorer: color.New(), } ) @@ -126,6 +133,8 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { return buf.WriteString(time.Now().Format(time.RFC3339)) case "time_rfc3339_nano": return buf.WriteString(time.Now().Format(time.RFC3339Nano)) + case "time_custom": + return buf.WriteString(time.Now().Format(config.CustomTimeFormat)) case "id": id := req.Header.Get(echo.HeaderXRequestID) if id == "" { @@ -146,6 +155,8 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { p = "/" } return buf.WriteString(p) + case "protocol": + return buf.WriteString(req.Proto) case "referer": return buf.WriteString(req.Referer()) case "user_agent": @@ -162,6 +173,10 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { s = config.colorer.Cyan(n) } return buf.WriteString(s) + case "error": + if err != nil { + return buf.WriteString(err.Error()) + } case "latency": l := stop.Sub(start) return buf.WriteString(strconv.FormatInt(int64(l), 10)) @@ -183,6 +198,11 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { return buf.Write([]byte(c.QueryParam(tag[6:]))) case strings.HasPrefix(tag, "form:"): return buf.Write([]byte(c.FormValue(tag[5:]))) + case strings.HasPrefix(tag, "cookie:"): + cookie, err := c.Cookie(tag[7:]) + if err == nil { + return buf.Write([]byte(cookie.Value)) + } } } return 0, nil diff --git a/vendor/github.com/labstack/echo/middleware/middleware.go b/vendor/github.com/labstack/echo/middleware/middleware.go index efcbab913..febbdae95 100644 --- a/vendor/github.com/labstack/echo/middleware/middleware.go +++ b/vendor/github.com/labstack/echo/middleware/middleware.go @@ -1,13 +1,37 @@ package middleware -import "github.com/labstack/echo" +import ( + "regexp" + "strconv" + "strings" + + "github.com/labstack/echo" +) type ( // Skipper defines a function to skip middleware. Returning true skips processing // the middleware. - Skipper func(c echo.Context) bool + Skipper func(echo.Context) bool + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc func(echo.Context) ) +func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer { + groups := pattern.FindAllStringSubmatch(input, -1) + if groups == nil { + return nil + } + values := groups[0][1:] + replace := make([]string, 2*len(values)) + for i, v := range values { + j := 2 * i + replace[j] = "$" + strconv.Itoa(i+1) + replace[j+1] = v + } + return strings.NewReplacer(replace...) +} + // DefaultSkipper returns false which processes the middleware. func DefaultSkipper(echo.Context) bool { return false diff --git a/vendor/github.com/labstack/echo/middleware/proxy.go b/vendor/github.com/labstack/echo/middleware/proxy.go new file mode 100644 index 000000000..f61477372 --- /dev/null +++ b/vendor/github.com/labstack/echo/middleware/proxy.go @@ -0,0 +1,252 @@ +package middleware + +import ( + "fmt" + "io" + "math/rand" + "net" + "net/http" + "net/http/httputil" + "net/url" + "regexp" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/labstack/echo" +) + +// TODO: Handle TLS proxy + +type ( + // ProxyConfig defines the config for Proxy middleware. + ProxyConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Balancer defines a load balancing technique. + // Required. + Balancer ProxyBalancer + + // Rewrite defines URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Examples: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + Rewrite map[string]string + + rewriteRegex map[*regexp.Regexp]string + } + + // ProxyTarget defines the upstream target. + ProxyTarget struct { + Name string + URL *url.URL + } + + // ProxyBalancer defines an interface to implement a load balancing technique. + ProxyBalancer interface { + AddTarget(*ProxyTarget) bool + RemoveTarget(string) bool + Next() *ProxyTarget + } + + commonBalancer struct { + targets []*ProxyTarget + mutex sync.RWMutex + } + + // RandomBalancer implements a random load balancing technique. + randomBalancer struct { + *commonBalancer + random *rand.Rand + } + + // RoundRobinBalancer implements a round-robin load balancing technique. + roundRobinBalancer struct { + *commonBalancer + i uint32 + } +) + +var ( + // DefaultProxyConfig is the default Proxy middleware config. + DefaultProxyConfig = ProxyConfig{ + Skipper: DefaultSkipper, + } +) + +func proxyHTTP(t *ProxyTarget) http.Handler { + return httputil.NewSingleHostReverseProxy(t.URL) +} + +func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + in, _, err := c.Response().Hijack() + if err != nil { + c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err)) + return + } + defer in.Close() + + out, err := net.Dial("tcp", t.URL.Host) + if err != nil { + he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)) + c.Error(he) + return + } + defer out.Close() + + // Write header + err = r.Write(out) + if err != nil { + he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)) + c.Error(he) + return + } + + errCh := make(chan error, 2) + cp := func(dst io.Writer, src io.Reader) { + _, err = io.Copy(dst, src) + errCh <- err + } + + go cp(out, in) + go cp(in, out) + err = <-errCh + if err != nil && err != io.EOF { + c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err) + } + }) +} + +// NewRandomBalancer returns a random proxy balancer. +func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer { + b := &randomBalancer{commonBalancer: new(commonBalancer)} + b.targets = targets + return b +} + +// NewRoundRobinBalancer returns a round-robin proxy balancer. +func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer { + b := &roundRobinBalancer{commonBalancer: new(commonBalancer)} + b.targets = targets + return b +} + +// AddTarget adds an upstream target to the list. +func (b *commonBalancer) AddTarget(target *ProxyTarget) bool { + for _, t := range b.targets { + if t.Name == target.Name { + return false + } + } + b.mutex.Lock() + defer b.mutex.Unlock() + b.targets = append(b.targets, target) + return true +} + +// RemoveTarget removes an upstream target from the list. +func (b *commonBalancer) RemoveTarget(name string) bool { + b.mutex.Lock() + defer b.mutex.Unlock() + for i, t := range b.targets { + if t.Name == name { + b.targets = append(b.targets[:i], b.targets[i+1:]...) + return true + } + } + return false +} + +// Next randomly returns an upstream target. +func (b *randomBalancer) Next() *ProxyTarget { + if b.random == nil { + b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + } + b.mutex.RLock() + defer b.mutex.RUnlock() + return b.targets[b.random.Intn(len(b.targets))] +} + +// Next returns an upstream target using round-robin technique. +func (b *roundRobinBalancer) Next() *ProxyTarget { + b.i = b.i % uint32(len(b.targets)) + t := b.targets[b.i] + atomic.AddUint32(&b.i, 1) + return t +} + +// Proxy returns a Proxy middleware. +// +// Proxy middleware forwards the request to upstream server using a configured load balancing technique. +func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc { + c := DefaultProxyConfig + c.Balancer = balancer + return ProxyWithConfig(c) +} + +// ProxyWithConfig returns a Proxy middleware with config. +// See: `Proxy()` +func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultLoggerConfig.Skipper + } + if config.Balancer == nil { + panic("echo: proxy middleware requires balancer") + } + config.rewriteRegex = map[*regexp.Regexp]string{} + + // Initialize + for k, v := range config.Rewrite { + k = strings.Replace(k, "*", "(\\S*)", -1) + config.rewriteRegex[regexp.MustCompile(k)] = v + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + tgt := config.Balancer.Next() + + // Rewrite + for k, v := range config.rewriteRegex { + replacer := captureTokens(k, req.URL.Path) + if replacer != nil { + req.URL.Path = replacer.Replace(v) + } + } + + // Fix header + if req.Header.Get(echo.HeaderXRealIP) == "" { + req.Header.Set(echo.HeaderXRealIP, c.RealIP()) + } + if req.Header.Get(echo.HeaderXForwardedProto) == "" { + req.Header.Set(echo.HeaderXForwardedProto, c.Scheme()) + } + if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy. + req.Header.Set(echo.HeaderXForwardedFor, c.RealIP()) + } + + // Proxy + switch { + case c.IsWebSocket(): + proxyRaw(tgt, c).ServeHTTP(res, req) + case req.Header.Get(echo.HeaderAccept) == "text/event-stream": + default: + proxyHTTP(tgt).ServeHTTP(res, req) + } + + return + } + } +} diff --git a/vendor/github.com/labstack/echo/middleware/recover.go b/vendor/github.com/labstack/echo/middleware/recover.go index 96fa62c90..2a42c5b18 100644 --- a/vendor/github.com/labstack/echo/middleware/recover.go +++ b/vendor/github.com/labstack/echo/middleware/recover.go @@ -5,7 +5,6 @@ import ( "runtime" "github.com/labstack/echo" - "github.com/labstack/gommon/color" ) type ( @@ -16,16 +15,16 @@ type ( // Size of the stack to be printed. // Optional. Default value 4KB. - StackSize int `json:"stack_size"` + StackSize int `yaml:"stack_size"` // DisableStackAll disables formatting stack traces of all other goroutines // into buffer after the trace for the current goroutine. // Optional. Default value false. - DisableStackAll bool `json:"disable_stack_all"` + DisableStackAll bool `yaml:"disable_stack_all"` // DisablePrintStack disables printing stack trace. // Optional. Default value as false. - DisablePrintStack bool `json:"disable_print_stack"` + DisablePrintStack bool `yaml:"disable_print_stack"` } ) @@ -64,17 +63,14 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { defer func() { if r := recover(); r != nil { - var err error - switch r := r.(type) { - case error: - err = r - default: + err, ok := r.(error) + if !ok { err = fmt.Errorf("%v", r) } stack := make([]byte, config.StackSize) length := runtime.Stack(stack, !config.DisableStackAll) if !config.DisablePrintStack { - c.Logger().Printf("[%s] %s %s\n", color.Red("PANIC RECOVER"), err, stack[:length]) + c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length]) } c.Error(err) } diff --git a/vendor/github.com/labstack/echo/middleware/redirect.go b/vendor/github.com/labstack/echo/middleware/redirect.go index b87dab091..422263dec 100644 --- a/vendor/github.com/labstack/echo/middleware/redirect.go +++ b/vendor/github.com/labstack/echo/middleware/redirect.go @@ -6,29 +6,28 @@ import ( "github.com/labstack/echo" ) -type ( - // RedirectConfig defines the config for Redirect middleware. - RedirectConfig struct { - // Skipper defines a function to skip middleware. - Skipper Skipper +// RedirectConfig defines the config for Redirect middleware. +type RedirectConfig struct { + // Skipper defines a function to skip middleware. + Skipper - // Status code to be used when redirecting the request. - // Optional. Default value http.StatusMovedPermanently. - Code int `json:"code"` - } -) + // Status code to be used when redirecting the request. + // Optional. Default value http.StatusMovedPermanently. + Code int `yaml:"code"` +} -const ( - www = "www" -) +// redirectLogic represents a function that given a scheme, host and uri +// can both: 1) determine if redirect is needed (will set ok accordingly) and +// 2) return the appropriate redirect url. +type redirectLogic func(scheme, host, uri string) (ok bool, url string) -var ( - // DefaultRedirectConfig is the default Redirect middleware config. - DefaultRedirectConfig = RedirectConfig{ - Skipper: DefaultSkipper, - Code: http.StatusMovedPermanently, - } -) +const www = "www" + +// DefaultRedirectConfig is the default Redirect middleware config. +var DefaultRedirectConfig = RedirectConfig{ + Skipper: DefaultSkipper, + Code: http.StatusMovedPermanently, +} // HTTPSRedirect redirects http requests to https. // For example, http://labstack.com will be redirect to https://labstack.com. @@ -41,29 +40,12 @@ func HTTPSRedirect() echo.MiddlewareFunc { // HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSRedirect()`. func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { - // Defaults - if config.Skipper == nil { - config.Skipper = DefaultTrailingSlashConfig.Skipper - } - if config.Code == 0 { - config.Code = DefaultRedirectConfig.Code - } - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if config.Skipper(c) { - return next(c) - } - - req := c.Request() - host := req.Host - uri := req.RequestURI - if !c.IsTLS() { - return c.Redirect(config.Code, "https://"+host+uri) - } - return next(c) + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https"; ok { + url = "https://" + host + uri } - } + return + }) } // HTTPSWWWRedirect redirects http requests to https www. @@ -77,29 +59,12 @@ func HTTPSWWWRedirect() echo.MiddlewareFunc { // HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSWWWRedirect()`. func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { - // Defaults - if config.Skipper == nil { - config.Skipper = DefaultTrailingSlashConfig.Skipper - } - if config.Code == 0 { - config.Code = DefaultRedirectConfig.Code - } - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if config.Skipper(c) { - return next(c) - } - - req := c.Request() - host := req.Host - uri := req.RequestURI - if !c.IsTLS() && host[:3] != www { - return c.Redirect(config.Code, "https://www."+host+uri) - } - return next(c) + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https" && host[:3] != www; ok { + url = "https://www." + host + uri } - } + return + }) } // HTTPSNonWWWRedirect redirects http requests to https non www. @@ -113,32 +78,15 @@ func HTTPSNonWWWRedirect() echo.MiddlewareFunc { // HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `HTTPSNonWWWRedirect()`. func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { - // Defaults - if config.Skipper == nil { - config.Skipper = DefaultTrailingSlashConfig.Skipper - } - if config.Code == 0 { - config.Code = DefaultRedirectConfig.Code - } - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if config.Skipper(c) { - return next(c) + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = scheme != "https"; ok { + if host[:3] == www { + host = host[4:] } - - req := c.Request() - host := req.Host - uri := req.RequestURI - if !c.IsTLS() { - if host[:3] == www { - return c.Redirect(config.Code, "https://"+host[4:]+uri) - } - return c.Redirect(config.Code, "https://"+host+uri) - } - return next(c) + url = "https://" + host + uri } - } + return + }) } // WWWRedirect redirects non www requests to www. @@ -152,30 +100,12 @@ func WWWRedirect() echo.MiddlewareFunc { // WWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `WWWRedirect()`. func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { - // Defaults - if config.Skipper == nil { - config.Skipper = DefaultTrailingSlashConfig.Skipper - } - if config.Code == 0 { - config.Code = DefaultRedirectConfig.Code - } - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if config.Skipper(c) { - return next(c) - } - - req := c.Request() - scheme := c.Scheme() - host := req.Host - if host[:3] != www { - uri := req.RequestURI - return c.Redirect(config.Code, scheme+"://www."+host+uri) - } - return next(c) + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = host[:3] != www; ok { + url = scheme + "://www." + host + uri } - } + return + }) } // NonWWWRedirect redirects www requests to non www. @@ -189,6 +119,15 @@ func NonWWWRedirect() echo.MiddlewareFunc { // NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // See `NonWWWRedirect()`. func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if ok = host[:3] == www; ok { + url = scheme + "://" + host[4:] + uri + } + return + }) +} + +func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc { if config.Skipper == nil { config.Skipper = DefaultTrailingSlashConfig.Skipper } @@ -202,13 +141,12 @@ func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { return next(c) } - req := c.Request() - scheme := c.Scheme() + req, scheme := c.Request(), c.Scheme() host := req.Host - if host[:3] == www { - uri := req.RequestURI - return c.Redirect(config.Code, scheme+"://"+host[4:]+uri) + if ok, url := cb(scheme, host, req.RequestURI); ok { + return c.Redirect(config.Code, url) } + return next(c) } } diff --git a/vendor/github.com/labstack/echo/middleware/rewrite.go b/vendor/github.com/labstack/echo/middleware/rewrite.go new file mode 100644 index 000000000..1b322126d --- /dev/null +++ b/vendor/github.com/labstack/echo/middleware/rewrite.go @@ -0,0 +1,84 @@ +package middleware + +import ( + "regexp" + "strings" + + "github.com/labstack/echo" +) + +type ( + // RewriteConfig defines the config for Rewrite middleware. + RewriteConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Rules defines the URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Example: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + // Required. + Rules map[string]string `yaml:"rules"` + + rulesRegex map[*regexp.Regexp]string + } +) + +var ( + // DefaultRewriteConfig is the default Rewrite middleware config. + DefaultRewriteConfig = RewriteConfig{ + Skipper: DefaultSkipper, + } +) + +// Rewrite returns a Rewrite middleware. +// +// Rewrite middleware rewrites the URL path based on the provided rules. +func Rewrite(rules map[string]string) echo.MiddlewareFunc { + c := DefaultRewriteConfig + c.Rules = rules + return RewriteWithConfig(c) +} + +// RewriteWithConfig returns a Rewrite middleware with config. +// See: `Rewrite()`. +func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc { + // Defaults + if config.Rules == nil { + panic("echo: rewrite middleware requires url path rewrite rules") + } + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + config.rulesRegex = map[*regexp.Regexp]string{} + + // Initialize + for k, v := range config.Rules { + k = strings.Replace(k, "*", "(.*)", -1) + k = k + "$" + config.rulesRegex[regexp.MustCompile(k)] = v + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + + // Rewrite + for k, v := range config.rulesRegex { + replacer := captureTokens(k, req.URL.Path) + if replacer != nil { + req.URL.Path = replacer.Replace(v) + break + } + } + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/middleware/secure.go b/vendor/github.com/labstack/echo/middleware/secure.go index 0125e74a7..188c0c406 100644 --- a/vendor/github.com/labstack/echo/middleware/secure.go +++ b/vendor/github.com/labstack/echo/middleware/secure.go @@ -15,12 +15,12 @@ type ( // XSSProtection provides protection against cross-site scripting attack (XSS) // by setting the `X-XSS-Protection` header. // Optional. Default value "1; mode=block". - XSSProtection string `json:"xss_protection"` + XSSProtection string `yaml:"xss_protection"` // ContentTypeNosniff provides protection against overriding Content-Type // header by setting the `X-Content-Type-Options` header. // Optional. Default value "nosniff". - ContentTypeNosniff string `json:"content_type_nosniff"` + ContentTypeNosniff string `yaml:"content_type_nosniff"` // XFrameOptions can be used to indicate whether or not a browser should // be allowed to render a page in a ,