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 , or . @@ -32,27 +32,27 @@ type ( // - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself. // - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so. // - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin. - XFrameOptions string `json:"x_frame_options"` + XFrameOptions string `yaml:"x_frame_options"` // HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how // long (in seconds) browsers should remember that this site is only to // be accessed using HTTPS. This reduces your exposure to some SSL-stripping // man-in-the-middle (MITM) attacks. // Optional. Default value 0. - HSTSMaxAge int `json:"hsts_max_age"` + HSTSMaxAge int `yaml:"hsts_max_age"` // HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security` // header, excluding all subdomains from security policy. It has no effect // unless HSTSMaxAge is set to a non-zero value. // Optional. Default value false. - HSTSExcludeSubdomains bool `json:"hsts_exclude_subdomains"` + HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"` // ContentSecurityPolicy sets the `Content-Security-Policy` header providing // security against cross-site scripting (XSS), clickjacking and other code // injection attacks resulting from execution of malicious content in the // trusted web page context. // Optional. Default value "". - ContentSecurityPolicy string `json:"content_security_policy"` + ContentSecurityPolicy string `yaml:"content_security_policy"` } ) diff --git a/vendor/github.com/labstack/echo/middleware/slash.go b/vendor/github.com/labstack/echo/middleware/slash.go index 1114d7226..9af56cafe 100644 --- a/vendor/github.com/labstack/echo/middleware/slash.go +++ b/vendor/github.com/labstack/echo/middleware/slash.go @@ -12,7 +12,7 @@ type ( // Status code to be used when redirecting the request. // Optional, but when provided the request is redirected using this code. - RedirectCode int `json:"redirect_code"` + RedirectCode int `yaml:"redirect_code"` } ) diff --git a/vendor/github.com/labstack/echo/middleware/static.go b/vendor/github.com/labstack/echo/middleware/static.go index e715c1c4d..55485f34d 100644 --- a/vendor/github.com/labstack/echo/middleware/static.go +++ b/vendor/github.com/labstack/echo/middleware/static.go @@ -2,12 +2,16 @@ package middleware import ( "fmt" + "html/template" + "net/http" + "net/url" "os" "path" "path/filepath" "strings" "github.com/labstack/echo" + "github.com/labstack/gommon/bytes" ) type ( @@ -18,23 +22,95 @@ type ( // Root directory from where the static content is served. // Required. - Root string `json:"root"` + Root string `yaml:"root"` // Index file for serving a directory. // Optional. Default value "index.html". - Index string `json:"index"` + Index string `yaml:"index"` // Enable HTML5 mode by forwarding all not-found requests to root so that // SPA (single-page application) can handle the routing. // Optional. Default value false. - HTML5 bool `json:"html5"` + HTML5 bool `yaml:"html5"` // Enable directory browsing. // Optional. Default value false. - Browse bool `json:"browse"` + Browse bool `yaml:"browse"` } ) +const html = ` + + + + + + + {{ .Name }} + + + + + {{ .Name }} + + + {{ range .Files }} + + {{ if .Dir }} + {{ $name := print .Name "/" }} + {{ $name }} + {{ else }} + {{ .Name }} + {{ .Size }} + {{ end }} + + {{ end }} + + + +` + var ( // DefaultStaticConfig is the default Static middleware config. DefaultStaticConfig = StaticConfig{ @@ -65,8 +141,14 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { config.Index = DefaultStaticConfig.Index } + // Index template + t, err := template.New("index").Parse(html) + if err != nil { + panic(fmt.Sprintf("echo: %v", err)) + } + return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c echo.Context) (err error) { if config.Skipper(c) { return next(c) } @@ -75,17 +157,25 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`. p = c.Param("*") } + p, err = url.PathUnescape(p) + if err != nil { + return + } name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security fi, err := os.Stat(name) if err != nil { if os.IsNotExist(err) { - if config.HTML5 && path.Ext(p) == "" { - return c.File(filepath.Join(config.Root, config.Index)) + if err = next(c); err != nil { + if he, ok := err.(*echo.HTTPError); ok { + if config.HTML5 && he.Code == http.StatusNotFound { + return c.File(filepath.Join(config.Root, config.Index)) + } + } + return } - return next(c) } - return err + return } if fi.IsDir() { @@ -94,12 +184,12 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { if err != nil { if config.Browse { - return listDir(name, c.Response()) + return listDir(t, name, c.Response()) } if os.IsNotExist(err) { return next(c) } - return err + return } return c.File(index) @@ -110,32 +200,30 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { } } -func listDir(name string, res *echo.Response) error { - dir, err := os.Open(name) +func listDir(t *template.Template, name string, res *echo.Response) (err error) { + file, err := os.Open(name) if err != nil { - return err + return } - dirs, err := dir.Readdir(-1) + files, err := file.Readdir(-1) if err != nil { - return err + return } - // Create a directory index + // Create directory index res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) - if _, err = fmt.Fprintf(res, "\n"); err != nil { - return err + data := struct { + Name string + Files []interface{} + }{ + Name: name, } - for _, d := range dirs { - name := d.Name() - color := "#212121" - if d.IsDir() { - color = "#e91e63" - name += "/" - } - if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil { - return err - } + for _, f := range files { + data.Files = append(data.Files, struct { + Name string + Dir bool + Size string + }{f.Name(), f.IsDir(), bytes.Format(f.Size())}) } - _, err = fmt.Fprintf(res, "\n") - return err + return t.Execute(res, data) } diff --git a/vendor/github.com/labstack/echo/response.go b/vendor/github.com/labstack/echo/response.go index 2c70d213f..6244783b0 100644 --- a/vendor/github.com/labstack/echo/response.go +++ b/vendor/github.com/labstack/echo/response.go @@ -11,11 +11,13 @@ type ( // by an HTTP handler to construct an HTTP response. // See: https://golang.org/pkg/net/http/#ResponseWriter Response struct { - Writer http.ResponseWriter - Status int - Size int64 - Committed bool - echo *Echo + echo *Echo + beforeFuncs []func() + afterFuncs []func() + Writer http.ResponseWriter + Status int + Size int64 + Committed bool } ) @@ -34,6 +36,17 @@ func (r *Response) Header() http.Header { return r.Writer.Header() } +// Before registers a function which is called just before the response is written. +func (r *Response) Before(fn func()) { + r.beforeFuncs = append(r.beforeFuncs, fn) +} + +// After registers a function which is called just after the response is written. +// If the `Content-Length` is unknown, none of the after function is executed. +func (r *Response) After(fn func()) { + r.afterFuncs = append(r.afterFuncs, fn) +} + // WriteHeader sends an HTTP response header with status code. If WriteHeader is // not called explicitly, the first call to Write will trigger an implicit // WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly @@ -43,6 +56,9 @@ func (r *Response) WriteHeader(code int) { r.echo.Logger.Warn("response already committed") return } + for _, fn := range r.beforeFuncs { + fn() + } r.Status = code r.Writer.WriteHeader(code) r.Committed = true @@ -55,6 +71,9 @@ func (r *Response) Write(b []byte) (n int, err error) { } n, err = r.Writer.Write(b) r.Size += int64(n) + for _, fn := range r.afterFuncs { + fn() + } return } @@ -82,6 +101,8 @@ func (r *Response) CloseNotify() <-chan bool { } func (r *Response) reset(w http.ResponseWriter) { + r.beforeFuncs = nil + r.afterFuncs = nil r.Writer = w r.Size = 0 r.Status = http.StatusOK diff --git a/vendor/github.com/labstack/echo/router.go b/vendor/github.com/labstack/echo/router.go index 876fc5d8a..ff53da87f 100644 --- a/vendor/github.com/labstack/echo/router.go +++ b/vendor/github.com/labstack/echo/router.go @@ -1,13 +1,11 @@ package echo -import "strings" - type ( // Router is the registry of all registered routes for an `Echo` instance for // request matching and URL path parameter parsing. Router struct { tree *node - routes map[string]Route + routes map[string]*Route echo *Echo } node struct { @@ -23,15 +21,16 @@ type ( kind uint8 children []*node methodHandler struct { - connect HandlerFunc - delete HandlerFunc - get HandlerFunc - head HandlerFunc - options HandlerFunc - patch HandlerFunc - post HandlerFunc - put HandlerFunc - trace HandlerFunc + connect HandlerFunc + delete HandlerFunc + get HandlerFunc + head HandlerFunc + options HandlerFunc + patch HandlerFunc + post HandlerFunc + propfind HandlerFunc + put HandlerFunc + trace HandlerFunc } ) @@ -47,7 +46,7 @@ func NewRouter(e *Echo) *Router { tree: &node{ methodHandler: new(methodHandler), }, - routes: map[string]Route{}, + routes: map[string]*Route{}, echo: e, } } @@ -61,8 +60,8 @@ func (r *Router) Add(method, path string, h HandlerFunc) { if path[0] != '/' { path = "/" + path } - ppath := path // Pristine path pnames := []string{} // Param names + ppath := path // Pristine path for i, l := 0, len(path); i < l; i++ { if path[i] == ':' { @@ -101,7 +100,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string cn := r.tree // Current node as root if cn == nil { - panic("echo ⇛ invalid method") + panic("echo: invalid method") } search := path @@ -175,12 +174,6 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string if len(cn.pnames) == 0 { // Issue #729 cn.pnames = pnames } - for i, n := range pnames { - // Param name aliases - if i < len(cn.pnames) && !strings.Contains(cn.pnames[i], n) { - cn.pnames[i] += "," + n - } - } } } return @@ -233,22 +226,24 @@ func (n *node) findChildByKind(t kind) *node { func (n *node) addHandler(method string, h HandlerFunc) { switch method { - case GET: - n.methodHandler.get = h - case POST: - n.methodHandler.post = h - case PUT: - n.methodHandler.put = h - case DELETE: - n.methodHandler.delete = h - case PATCH: - n.methodHandler.patch = h - case OPTIONS: - n.methodHandler.options = h - case HEAD: - n.methodHandler.head = h case CONNECT: n.methodHandler.connect = h + case DELETE: + n.methodHandler.delete = h + case GET: + n.methodHandler.get = h + case HEAD: + n.methodHandler.head = h + case OPTIONS: + n.methodHandler.options = h + case PATCH: + n.methodHandler.patch = h + case POST: + n.methodHandler.post = h + case PROPFIND: + n.methodHandler.propfind = h + case PUT: + n.methodHandler.put = h case TRACE: n.methodHandler.trace = h } @@ -256,22 +251,24 @@ func (n *node) addHandler(method string, h HandlerFunc) { func (n *node) findHandler(method string) HandlerFunc { switch method { - case GET: - return n.methodHandler.get - case POST: - return n.methodHandler.post - case PUT: - return n.methodHandler.put - case DELETE: - return n.methodHandler.delete - case PATCH: - return n.methodHandler.patch - case OPTIONS: - return n.methodHandler.options - case HEAD: - return n.methodHandler.head case CONNECT: return n.methodHandler.connect + case DELETE: + return n.methodHandler.delete + case GET: + return n.methodHandler.get + case HEAD: + return n.methodHandler.head + case OPTIONS: + return n.methodHandler.options + case PATCH: + return n.methodHandler.patch + case POST: + return n.methodHandler.post + case PROPFIND: + return n.methodHandler.propfind + case PUT: + return n.methodHandler.put case TRACE: return n.methodHandler.trace default: @@ -394,7 +391,7 @@ func (r *Router) Find(method, path string, c Context) { if cn = cn.findChildByKind(akind); cn == nil { if nn != nil { cn = nn - nn = nil // Next + nn = cn.parent // Next (Issue #954) search = ns if nk == pkind { goto Param diff --git a/vendor/github.com/labstack/gommon/bytes/bytes.go b/vendor/github.com/labstack/gommon/bytes/bytes.go index fd97e6d12..746436cbb 100644 --- a/vendor/github.com/labstack/gommon/bytes/bytes.go +++ b/vendor/github.com/labstack/gommon/bytes/bytes.go @@ -7,12 +7,12 @@ import ( ) type ( - Bytes struct { - } + // Bytes struct + Bytes struct{} ) const ( - B = 1 << (10 * iota) + _ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier KB MB GB @@ -22,7 +22,7 @@ const ( ) var ( - pattern = regexp.MustCompile(`(?i)^(-?\d+)([KMGTP]B?|B)$`) + pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)([KMGTPE]B?|B?)$`) global = New() ) @@ -38,29 +38,31 @@ func (*Bytes) Format(b int64) string { value := float64(b) switch { - case b < KB: - return strconv.FormatInt(b, 10) + "B" - case b < MB: - value /= KB - multiple = "KB" - case b < MB: - value /= KB - multiple = "KB" - case b < GB: - value /= MB - multiple = "MB" - case b < TB: - value /= GB - multiple = "GB" - case b < PB: - value /= TB - multiple = "TB" - case b < EB: + case b >= EB: + value /= EB + multiple = "EB" + case b >= PB: value /= PB multiple = "PB" + case b >= TB: + value /= TB + multiple = "TB" + case b >= GB: + value /= GB + multiple = "GB" + case b >= MB: + value /= MB + multiple = "MB" + case b >= KB: + value /= KB + multiple = "KB" + case b == 0: + return "0" + default: + return strconv.FormatInt(b, 10) + "B" } - return fmt.Sprintf("%.02f%s", value, multiple) + return fmt.Sprintf("%.2f%s", value, multiple) } // Parse parses human readable bytes string to bytes integer. @@ -72,27 +74,27 @@ func (*Bytes) Parse(value string) (i int64, err error) { } bytesString := parts[1] multiple := parts[2] - bytes, err := strconv.ParseInt(bytesString, 10, 64) + bytes, err := strconv.ParseFloat(bytesString, 64) if err != nil { return } switch multiple { - case "B": - return bytes * B, nil + default: + return int64(bytes), nil case "K", "KB": - return bytes * KB, nil + return int64(bytes * KB), nil case "M", "MB": - return bytes * MB, nil + return int64(bytes * MB), nil case "G", "GB": - return bytes * GB, nil + return int64(bytes * GB), nil case "T", "TB": - return bytes * TB, nil + return int64(bytes * TB), nil case "P", "PB": - return bytes * PB, nil + return int64(bytes * PB), nil + case "E", "EB": + return int64(bytes * EB), nil } - - return } // Format wraps global Bytes's Format function. diff --git a/vendor/github.com/labstack/gommon/log/log.go b/vendor/github.com/labstack/gommon/log/log.go index 1ac6c00ca..65af23acd 100644 --- a/vendor/github.com/labstack/gommon/log/log.go +++ b/vendor/github.com/labstack/gommon/log/log.go @@ -8,11 +8,10 @@ import ( "os" "path" "runtime" + "strconv" "sync" "time" - "strconv" - "github.com/mattn/go-isatty" "github.com/valyala/fasttemplate" @@ -343,7 +342,7 @@ func (l *Logger) log(v Lvl, format string, args ...interface{}) { buf := l.bufferPool.Get().(*bytes.Buffer) buf.Reset() defer l.bufferPool.Put(buf) - _, file, line, _ := runtime.Caller(3) + _, file, line, _ := runtime.Caller(2) if v >= l.level || v == 0 { message := "" diff --git a/vendor/github.com/labstack/gommon/random/random.go b/vendor/github.com/labstack/gommon/random/random.go index b1ae864a1..482d05248 100644 --- a/vendor/github.com/labstack/gommon/random/random.go +++ b/vendor/github.com/labstack/gommon/random/random.go @@ -13,13 +13,13 @@ type ( // Charsets const ( - Uppercase string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - Lowercase = "abcdefghijklmnopqrstuvwxyz" - Alphabetic = Uppercase + Lowercase - Numeric = "0123456789" - Alphanumeric = Alphabetic + Numeric - Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?` - Hex = Numeric + "abcdef" + Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + Lowercase = "abcdefghijklmnopqrstuvwxyz" + Alphabetic = Uppercase + Lowercase + Numeric = "0123456789" + Alphanumeric = Alphabetic + Numeric + Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?` + Hex = Numeric + "abcdef" ) var ( diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go index 15a014fd3..e17a5474e 100644 --- a/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -29,15 +29,6 @@ const ( backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) ) -const ( - genericRead = 0x80000000 - genericWrite = 0x40000000 -) - -const ( - consoleTextmodeBuffer = 0x1 -) - type wchar uint16 type short int16 type dword uint32 @@ -78,17 +69,14 @@ var ( procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") - procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") ) // Writer provide colorable Writer to the console type Writer struct { - out io.Writer - handle syscall.Handle - althandle syscall.Handle - oldattr word - oldpos coord - rest bytes.Buffer + out io.Writer + handle syscall.Handle + oldattr word + oldpos coord } // NewColorable return new instance of Writer which handle escape sequence from File. @@ -419,18 +407,7 @@ func (w *Writer) Write(data []byte) (n int, err error) { var csbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - handle := w.handle - - var er *bytes.Reader - if w.rest.Len() > 0 { - var rest bytes.Buffer - w.rest.WriteTo(&rest) - w.rest.Reset() - rest.Write(data) - er = bytes.NewReader(rest.Bytes()) - } else { - er = bytes.NewReader(data) - } + er := bytes.NewReader(data) var bw [1]byte loop: for { @@ -449,42 +426,28 @@ loop: } if c2 == ']' { - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 { + if err := doTitleSequence(er); err != nil { break loop } - er = bytes.NewReader(w.rest.Bytes()[2:]) - err := doTitleSequence(er) - if err != nil { - break loop - } - w.rest.Reset() continue } if c2 != 0x5b { continue } - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - var buf bytes.Buffer var m byte - for i, c := range w.rest.Bytes()[2:] { + for { + c, err := er.ReadByte() + if err != nil { + break loop + } if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { m = c - er = bytes.NewReader(w.rest.Bytes()[2+i+1:]) - w.rest.Reset() break } buf.Write([]byte(string(c))) } - if m == 0 { - break loop - } switch m { case 'A': @@ -492,64 +455,61 @@ loop: if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'B': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'C': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'D': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x -= short(n) - if csbi.cursorPosition.x < 0 { - csbi.cursorPosition.x = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'E': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = 0 csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'F': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = 0 csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'G': n, err = strconv.Atoi(buf.String()) if err != nil { continue } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = short(n - 1) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'H', 'f': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) if buf.Len() > 0 { token := strings.Split(buf.String(), ";") switch len(token) { @@ -574,7 +534,7 @@ loop: } else { csbi.cursorPosition.y = 0 } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'J': n := 0 if buf.Len() > 0 { @@ -585,7 +545,7 @@ loop: } var count, written dword var cursor coord - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) switch n { case 0: cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} @@ -597,8 +557,8 @@ loop: cursor = coord{x: csbi.window.left, y: csbi.window.top} count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) case 'K': n := 0 if buf.Len() > 0 { @@ -607,13 +567,13 @@ loop: continue } } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) var cursor coord var count, written dword switch n { case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) + cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x - 1) case 1: cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} count = dword(csbi.size.x - csbi.cursorPosition.x) @@ -621,14 +581,14 @@ loop: cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} count = dword(csbi.size.x) } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) case 'm': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) attr := csbi.attributes cs := buf.String() if cs == "" { - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr)) + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) continue } token := strings.Split(cs, ";") @@ -667,21 +627,6 @@ loop: attr |= n256foreAttr[n256] i += 2 } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= foregroundRed - } - if g > 127 { - attr |= foregroundGreen - } - if b > 127 { - attr |= foregroundBlue - } } else { attr = attr & (w.oldattr & backgroundMask) } @@ -709,21 +654,6 @@ loop: attr |= n256backAttr[n256] i += 2 } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= backgroundRed - } - if g > 127 { - attr |= backgroundGreen - } - if b > 127 { - attr |= backgroundBlue - } } else { attr = attr & (w.oldattr & foregroundMask) } @@ -755,52 +685,38 @@ loop: attr |= backgroundBlue } } - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr)) + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) } } case 'h': var ci consoleCursorInfo cs := buf.String() if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle == 0 { - h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0) - w.althandle = syscall.Handle(h) - if w.althandle != 0 { - handle = w.althandle - } - } + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) } case 'l': var ci consoleCursorInfo cs := buf.String() if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle != 0 { - syscall.CloseHandle(w.althandle) - w.althandle = 0 - handle = w.handle - } + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) } case 's': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) w.oldpos = csbi.cursorPosition case 'u': - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) } } diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go index e8388b083..1f4fb69ed 100644 --- a/vendor/golang.org/x/crypto/acme/acme.go +++ b/vendor/golang.org/x/crypto/acme/acme.go @@ -142,7 +142,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) { // // In the case where CA server does not provide the issued certificate in the response, // CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips. -// In such scenario the caller can cancel the polling with ctx. +// In such a scenario, the caller can cancel the polling with ctx. // // CreateCert returns an error if the CA's response or chain was unreasonably large. // Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features. @@ -257,7 +257,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, func AcceptTOS(tosURL string) bool { return true } // Register creates a new account registration by following the "new-reg" flow. -// It returns registered account. The account is not modified. +// It returns the registered account. The account is not modified. // // The registration may require the caller to agree to the CA's Terms of Service (TOS). // If so, and the account has not indicated the acceptance of the terms (see Account for details), @@ -400,7 +400,7 @@ func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { // WaitAuthorization polls an authorization at the given URL // until it is in one of the final states, StatusValid or StatusInvalid, -// or the context is done. +// the ACME CA responded with a 4xx error code, or the context is done. // // It returns a non-nil Authorization only if its Status is StatusValid. // In all other cases WaitAuthorization returns an error. @@ -412,6 +412,13 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat if err != nil { return nil, err } + if res.StatusCode >= 400 && res.StatusCode <= 499 { + // Non-retriable error. For instance, Let's Encrypt may return 404 Not Found + // when requesting an expired authorization. + defer res.Body.Close() + return nil, responseError(res) + } + retry := res.Header.Get("Retry-After") if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { res.Body.Close() @@ -995,6 +1002,7 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) { // tlsChallengeCert creates a temporary certificate for TLS-SNI challenges // with the given SANs and auto-generated public/private key pair. +// The Subject Common Name is set to the first SAN to aid debugging. // To create a cert with a custom key pair, specify WithKey option. func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { var ( @@ -1033,6 +1041,9 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { } } tmpl.DNSNames = san + if len(san) > 0 { + tmpl.Subject.CommonName = san[0] + } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) if err != nil { diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go index b10102017..263b29133 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go @@ -24,8 +24,9 @@ import ( "fmt" "io" mathrand "math/rand" + "net" "net/http" - "strconv" + "path" "strings" "sync" "time" @@ -80,8 +81,9 @@ func defaultHostPolicy(context.Context, string) error { } // Manager is a stateful certificate manager built on top of acme.Client. -// It obtains and refreshes certificates automatically, -// as well as providing them to a TLS server via tls.Config. +// It obtains and refreshes certificates automatically using "tls-sni-01", +// "tls-sni-02" and "http-01" challenge types, as well as providing them +// to a TLS server via tls.Config. // // You must specify a cache implementation, such as DirCache, // to reuse obtained certificates across program restarts. @@ -150,15 +152,26 @@ type Manager struct { stateMu sync.Mutex state map[string]*certState // keyed by domain name - // tokenCert is keyed by token domain name, which matches server name - // of ClientHello. Keys always have ".acme.invalid" suffix. - tokenCertMu sync.RWMutex - tokenCert map[string]*tls.Certificate - // renewal tracks the set of domains currently running renewal timers. // It is keyed by domain name. renewalMu sync.Mutex renewal map[string]*domainRenewal + + // tokensMu guards the rest of the fields: tryHTTP01, certTokens and httpTokens. + tokensMu sync.RWMutex + // tryHTTP01 indicates whether the Manager should try "http-01" challenge type + // during the authorization flow. + tryHTTP01 bool + // httpTokens contains response body values for http-01 challenges + // and is keyed by the URL path at which a challenge response is expected + // to be provisioned. + // The entries are stored for the duration of the authorization flow. + httpTokens map[string][]byte + // certTokens contains temporary certificates for tls-sni challenges + // and is keyed by token domain name, which matches server name of ClientHello. + // Keys always have ".acme.invalid" suffix. + // The entries are stored for the duration of the authorization flow. + certTokens map[string]*tls.Certificate } // GetCertificate implements the tls.Config.GetCertificate hook. @@ -185,14 +198,16 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, return nil, errors.New("acme/autocert: server name contains invalid character") } + // In the worst-case scenario, the timeout needs to account for caching, host policy, + // domain ownership verification and certificate issuance. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() // check whether this is a token cert requested for TLS-SNI challenge if strings.HasSuffix(name, ".acme.invalid") { - m.tokenCertMu.RLock() - defer m.tokenCertMu.RUnlock() - if cert := m.tokenCert[name]; cert != nil { + m.tokensMu.RLock() + defer m.tokensMu.RUnlock() + if cert := m.certTokens[name]; cert != nil { return cert, nil } if cert, err := m.cacheGet(ctx, name); err == nil { @@ -224,6 +239,68 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, return cert, nil } +// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses. +// It returns an http.Handler that responds to the challenges and must be +// running on port 80. If it receives a request that is not an ACME challenge, +// it delegates the request to the optional fallback handler. +// +// If fallback is nil, the returned handler redirects all GET and HEAD requests +// to the default TLS port 443 with 302 Found status code, preserving the original +// request path and query. It responds with 400 Bad Request to all other HTTP methods. +// The fallback is not protected by the optional HostPolicy. +// +// Because the fallback handler is run with unencrypted port 80 requests, +// the fallback should not serve TLS-only requests. +// +// If HTTPHandler is never called, the Manager will only use TLS SNI +// challenges for domain verification. +func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + m.tryHTTP01 = true + + if fallback == nil { + fallback = http.HandlerFunc(handleHTTPRedirect) + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") { + fallback.ServeHTTP(w, r) + return + } + // A reasonable context timeout for cache and host policy only, + // because we don't wait for a new certificate issuance here. + ctx, cancel := context.WithTimeout(r.Context(), time.Minute) + defer cancel() + if err := m.hostPolicy()(ctx, r.Host); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + data, err := m.httpToken(ctx, r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + w.Write(data) + }) +} + +func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(w, "Use HTTPS", http.StatusBadRequest) + return + } + target := "https://" + stripPort(r.Host) + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusFound) +} + +func stripPort(hostport string) string { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return hostport + } + return net.JoinHostPort(host, "443") +} + // cert returns an existing certificate either from m.state or cache. // If a certificate is found in cache but not in m.state, the latter will be filled // with the cached value. @@ -371,7 +448,7 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica // We are the first; state is locked. // Unblock the readers when domain ownership is verified - // and the we got the cert or the process failed. + // and we got the cert or the process failed. defer state.Unlock() state.locked = false @@ -439,16 +516,17 @@ func (m *Manager) certState(domain string) (*certState, error) { return state, nil } -// authorizedCert starts domain ownership verification process and requests a new cert upon success. +// authorizedCert starts the domain ownership verification process and requests a new cert upon success. // The key argument is the certificate private key. func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) { - if err := m.verify(ctx, domain); err != nil { - return nil, nil, err - } client, err := m.acmeClient(ctx) if err != nil { return nil, nil, err } + + if err := m.verify(ctx, client, domain); err != nil { + return nil, nil, err + } csr, err := certRequest(key, domain) if err != nil { return nil, nil, err @@ -464,98 +542,171 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain return der, leaf, nil } -// verify starts a new identifier (domain) authorization flow. -// It prepares a challenge response and then blocks until the authorization -// is marked as "completed" by the CA (either succeeded or failed). -// -// verify returns nil iff the verification was successful. -func (m *Manager) verify(ctx context.Context, domain string) error { - client, err := m.acmeClient(ctx) - if err != nil { - return err +// verify runs the identifier (domain) authorization flow +// using each applicable ACME challenge type. +func (m *Manager) verify(ctx context.Context, client *acme.Client, domain string) error { + // The list of challenge types we'll try to fulfill + // in this specific order. + challengeTypes := []string{"tls-sni-02", "tls-sni-01"} + m.tokensMu.RLock() + if m.tryHTTP01 { + challengeTypes = append(challengeTypes, "http-01") } + m.tokensMu.RUnlock() - // start domain authorization and get the challenge - authz, err := client.Authorize(ctx, domain) - if err != nil { - return err - } - // maybe don't need to at all - if authz.Status == acme.StatusValid { - return nil - } - - // pick a challenge: prefer tls-sni-02 over tls-sni-01 - // TODO: consider authz.Combinations - var chal *acme.Challenge - for _, c := range authz.Challenges { - if c.Type == "tls-sni-02" { - chal = c - break + var nextTyp int // challengeType index of the next challenge type to try + for { + // Start domain authorization and get the challenge. + authz, err := client.Authorize(ctx, domain) + if err != nil { + return err } - if c.Type == "tls-sni-01" { - chal = c + // No point in accepting challenges if the authorization status + // is in a final state. + switch authz.Status { + case acme.StatusValid: + return nil // already authorized + case acme.StatusInvalid: + return fmt.Errorf("acme/autocert: invalid authorization %q", authz.URI) + } + + // Pick the next preferred challenge. + var chal *acme.Challenge + for chal == nil && nextTyp < len(challengeTypes) { + chal = pickChallenge(challengeTypes[nextTyp], authz.Challenges) + nextTyp++ + } + if chal == nil { + return fmt.Errorf("acme/autocert: unable to authorize %q; tried %q", domain, challengeTypes) + } + cleanup, err := m.fulfill(ctx, client, chal) + if err != nil { + continue + } + defer cleanup() + if _, err := client.Accept(ctx, chal); err != nil { + continue + } + + // A challenge is fulfilled and accepted: wait for the CA to validate. + if _, err := client.WaitAuthorization(ctx, authz.URI); err == nil { + return nil } } - if chal == nil { - return errors.New("acme/autocert: no supported challenge type found") - } - - // create a token cert for the challenge response - var ( - cert tls.Certificate - name string - ) - switch chal.Type { - case "tls-sni-01": - cert, name, err = client.TLSSNI01ChallengeCert(chal.Token) - case "tls-sni-02": - cert, name, err = client.TLSSNI02ChallengeCert(chal.Token) - default: - err = fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) - } - if err != nil { - return err - } - m.putTokenCert(ctx, name, &cert) - defer func() { - // verification has ended at this point - // don't need token cert anymore - go m.deleteTokenCert(name) - }() - - // ready to fulfill the challenge - if _, err := client.Accept(ctx, chal); err != nil { - return err - } - // wait for the CA to validate - _, err = client.WaitAuthorization(ctx, authz.URI) - return err } -// putTokenCert stores the cert under the named key in both m.tokenCert map -// and m.Cache. -func (m *Manager) putTokenCert(ctx context.Context, name string, cert *tls.Certificate) { - m.tokenCertMu.Lock() - defer m.tokenCertMu.Unlock() - if m.tokenCert == nil { - m.tokenCert = make(map[string]*tls.Certificate) +// fulfill provisions a response to the challenge chal. +// The cleanup is non-nil only if provisioning succeeded. +func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge) (cleanup func(), err error) { + switch chal.Type { + case "tls-sni-01": + cert, name, err := client.TLSSNI01ChallengeCert(chal.Token) + if err != nil { + return nil, err + } + m.putCertToken(ctx, name, &cert) + return func() { go m.deleteCertToken(name) }, nil + case "tls-sni-02": + cert, name, err := client.TLSSNI02ChallengeCert(chal.Token) + if err != nil { + return nil, err + } + m.putCertToken(ctx, name, &cert) + return func() { go m.deleteCertToken(name) }, nil + case "http-01": + resp, err := client.HTTP01ChallengeResponse(chal.Token) + if err != nil { + return nil, err + } + p := client.HTTP01ChallengePath(chal.Token) + m.putHTTPToken(ctx, p, resp) + return func() { go m.deleteHTTPToken(p) }, nil } - m.tokenCert[name] = cert + return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) +} + +func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge { + for _, c := range chal { + if c.Type == typ { + return c + } + } + return nil +} + +// putCertToken stores the cert under the named key in both m.certTokens map +// and m.Cache. +func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + if m.certTokens == nil { + m.certTokens = make(map[string]*tls.Certificate) + } + m.certTokens[name] = cert m.cachePut(ctx, name, cert) } -// deleteTokenCert removes the token certificate for the specified domain name -// from both m.tokenCert map and m.Cache. -func (m *Manager) deleteTokenCert(name string) { - m.tokenCertMu.Lock() - defer m.tokenCertMu.Unlock() - delete(m.tokenCert, name) +// deleteCertToken removes the token certificate for the specified domain name +// from both m.certTokens map and m.Cache. +func (m *Manager) deleteCertToken(name string) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + delete(m.certTokens, name) if m.Cache != nil { m.Cache.Delete(context.Background(), name) } } +// httpToken retrieves an existing http-01 token value from an in-memory map +// or the optional cache. +func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) { + m.tokensMu.RLock() + defer m.tokensMu.RUnlock() + if v, ok := m.httpTokens[tokenPath]; ok { + return v, nil + } + if m.Cache == nil { + return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath) + } + return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath)) +} + +// putHTTPToken stores an http-01 token value using tokenPath as key +// in both in-memory map and the optional Cache. +// +// It ignores any error returned from Cache.Put. +func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + if m.httpTokens == nil { + m.httpTokens = make(map[string][]byte) + } + b := []byte(val) + m.httpTokens[tokenPath] = b + if m.Cache != nil { + m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b) + } +} + +// deleteHTTPToken removes an http-01 token value from both in-memory map +// and the optional Cache, ignoring any error returned from the latter. +// +// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout. +func (m *Manager) deleteHTTPToken(tokenPath string) { + m.tokensMu.Lock() + defer m.tokensMu.Unlock() + delete(m.httpTokens, tokenPath) + if m.Cache != nil { + m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath)) + } +} + +// httpTokenCacheKey returns a key at which an http-01 token value may be stored +// in the Manager's optional Cache. +func httpTokenCacheKey(tokenPath string) string { + return "http-01-" + path.Base(tokenPath) +} + // renew starts a cert renewal timer loop, one per domain. // // The loop is scheduled in two cases: @@ -790,16 +941,6 @@ func validCert(domain string, der [][]byte, key crypto.Signer) (leaf *x509.Certi return leaf, nil } -func retryAfter(v string) time.Duration { - if i, err := strconv.Atoi(v); err == nil { - return time.Duration(i) * time.Second - } - if t, err := http.ParseTime(v); err == nil { - return t.Sub(timeNow()) - } - return time.Second -} - type lockedMathRand struct { sync.Mutex rnd *mathrand.Rand diff --git a/vendor/golang.org/x/crypto/acme/autocert/renewal.go b/vendor/golang.org/x/crypto/acme/autocert/renewal.go index 6c5da2bc8..2a3a0a706 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/renewal.go +++ b/vendor/golang.org/x/crypto/acme/autocert/renewal.go @@ -102,7 +102,9 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) { if err != nil { return 0, err } - dr.m.cachePut(ctx, dr.domain, tlscert) + if err := dr.m.cachePut(ctx, dr.domain, tlscert); err != nil { + return 0, err + } dr.m.stateMu.Lock() defer dr.m.stateMu.Unlock() // m.state is guaranteed to be non-nil at this point diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go index 202fa8aff..aeb73f81a 100644 --- a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go +++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go @@ -241,11 +241,11 @@ func (p *hashed) Hash() []byte { n = 3 } arr[n] = '$' - n += 1 + n++ copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) n += 2 arr[n] = '$' - n += 1 + n++ copy(arr[n:], p.salt) n += encodedSaltSize copy(arr[n:], p.hash) diff --git a/vendor/modules.txt b/vendor/modules.txt index b73a339d3..04f6528ec 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -9,7 +9,7 @@ github.com/client9/misspell/cmd/misspell github.com/client9/misspell # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew -# github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a +# github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify @@ -93,10 +93,10 @@ github.com/karalabe/xgo github.com/kr/pretty # github.com/kr/text v0.1.0 github.com/kr/text -# github.com/labstack/echo v3.1.0+incompatible +# github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251 github.com/labstack/echo github.com/labstack/echo/middleware -# github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 +# github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec github.com/labstack/gommon/log github.com/labstack/gommon/color github.com/labstack/gommon/bytes @@ -107,9 +107,9 @@ github.com/magiconair/properties github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter github.com/mailru/easyjson/buffer -# github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd +# github.com/mattn/go-colorable v0.0.9 github.com/mattn/go-colorable -# github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee +# github.com/mattn/go-isatty v0.0.3 github.com/mattn/go-isatty # github.com/mattn/go-sqlite3 v1.9.0 github.com/mattn/go-sqlite3 @@ -140,7 +140,7 @@ github.com/toqueteos/webbrowser github.com/valyala/bytebufferpool # github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 github.com/valyala/fasttemplate -# golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 +# golang.org/x/crypto v0.0.0-20180312195533-182114d58262 golang.org/x/crypto/bcrypt golang.org/x/crypto/acme/autocert golang.org/x/crypto/blowfish
\n"); err != nil { - return err + data := struct { + Name string + Files []interface{} + }{ + Name: name, } - for _, d := range dirs { - name := d.Name() - color := "#212121" - if d.IsDir() { - color = "#e91e63" - name += "/" - } - if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil { - return err - } + for _, f := range files { + data.Files = append(data.Files, struct { + Name string + Dir bool + Size string + }{f.Name(), f.IsDir(), bytes.Format(f.Size())}) } - _, err = fmt.Fprintf(res, "