diff --git a/backend/Gopkg.lock b/backend/Gopkg.lock index 87006287..c6ba2956 100644 --- a/backend/Gopkg.lock +++ b/backend/Gopkg.lock @@ -135,12 +135,12 @@ version = "v0.2.0" [[projects]] - digest = "1:7b1f422f560b103f435f8501f854f238f1ce00d0f8146e76eacdfa71f2c8d8c0" + digest = "1:6734b6a2ee0f6ccf35d8f3f72ce8c1e4f11e7060b284443502668c0805ff6aa2" name = "github.com/go-pkgz/lgr" packages = ["."] pruneopts = "UT" - revision = "dbd940fec9650260d15aa071cab658d0502ed92f" - version = "v0.3.2" + revision = "6e5c31cef8b3f015033c6857e3368a08f97fa679" + version = "v0.4.0" [[projects]] digest = "1:62aeed92576ea10c25389334254f957594b4b5ae8ff71a53d3aa9df5dc6961eb" @@ -162,7 +162,7 @@ version = "v1.1.1" [[projects]] - digest = "1:27df73e1f59fffb9aaa2c28ba81561f177710576f18ad602564949355fac3e23" + digest = "1:74e7f0d5cf9c383695eabb20764c189c4ac898d89883ab31732d838874ac0998" name = "github.com/go-pkgz/rest" packages = [ ".", @@ -170,8 +170,8 @@ "logger", ] pruneopts = "UT" - revision = "27af5e3ba9439ec0df51c30794f473befe0a0e6d" - version = "v1.3.1" + revision = "f726ac4d98016b5009883c2d3c20a9c8ce873ee1" + version = "v1.4.0" [[projects]] digest = "1:92b44856ee15e8a98b91d751a60b512017e0ba227d1ed9d2c02ad13d67062ff8" diff --git a/backend/app/main.go b/backend/app/main.go index 3bf6c72a..de745613 100644 --- a/backend/app/main.go +++ b/backend/app/main.go @@ -62,10 +62,10 @@ func main() { func setupLog(dbg bool) { if dbg { - log.Setup(log.Debug, log.CallerFile, log.Msec, log.LevelBraces) + log.Setup(log.Debug, log.CallerFile, log.Msec, log.LevelBraces, log.CallerIgnore("logger")) return } - log.Setup(log.Msec, log.LevelBraces, log.CallerPkg, log.CallerIgnore("logger")) + log.Setup(log.Msec, log.LevelBraces, log.CallerPkg, log.CallerIgnore("logger", "rest")) } // getDump reads runtime stack and returns as a string diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index cfaaabe7..ea33d68f 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -183,13 +183,13 @@ func (s *Rest) routes() chi.Router { authHandler, avatarHandler := s.Authenticator.Handlers() router.Group(func(r chi.Router) { - l := logger.New(logger.Flags(logger.All), logger.Log(log.Default()), logger.IPfn(ipFn), logger.Prefix("[INFO]")) + l := logger.New(logger.Log(log.Default()), logger.WithBody, logger.IPfn(ipFn), logger.Prefix("[INFO]")) r.Use(l.Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(5, nil))) r.Mount("/auth", authHandler) }) router.Group(func(r chi.Router) { - r.Use(logger.New(logger.Flags(logger.None)).Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil))) + r.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil))) r.Mount("/avatar", avatarHandler) }) @@ -199,7 +199,7 @@ func (s *Rest) routes() chi.Router { router.Route("/api/v1", func(rapi chi.Router) { rapi.Group(func(rava chi.Router) { - rava.Use(logger.New(logger.Flags(logger.None)).Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil))) + rava.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(100, nil))) rava.Mount("/avatar", avatarHandler) }) @@ -207,7 +207,7 @@ func (s *Rest) routes() chi.Router { rapi.Group(func(ropen chi.Router) { ropen.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil))) ropen.Use(authMiddleware.Trace) - ropen.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()), + ropen.Use(logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[INFO]"), logger.IPfn(ipFn)).Handler) ropen.Get("/find", s.findCommentsCtrl) ropen.Get("/id/{id}", s.commentByIDCtrl) @@ -228,7 +228,7 @@ func (s *Rest) routes() chi.Router { rapi.Group(func(rauth chi.Router) { rauth.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil))) rauth.Use(authMiddleware.Auth) - rauth.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()), + rauth.Use(logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[INFO]"), logger.IPfn(ipFn)).Handler) rauth.Get("/user", s.userInfoCtrl) rauth.Get("/userdata", s.userAllDataCtrl) @@ -245,7 +245,7 @@ func (s *Rest) routes() chi.Router { } rauth.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(lmt, nil))) rauth.Use(authMiddleware.Auth) - rauth.Use(logger.New(logger.Flags(logger.All), logger.Log(log.Default()), + rauth.Use(logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[DEBUG]"), logger.IPfn(ipFn)).Handler) rauth.Put("/comment/{id}", s.updateCommentCtrl) diff --git a/backend/vendor/github.com/go-pkgz/lgr/.travis.yml b/backend/vendor/github.com/go-pkgz/lgr/.travis.yml index 3c6ef3e7..4e4d2ade 100644 --- a/backend/vendor/github.com/go-pkgz/lgr/.travis.yml +++ b/backend/vendor/github.com/go-pkgz/lgr/.travis.yml @@ -1,13 +1,13 @@ language: go go: - - "1.11.x" + - "1.12.x" install: true before_install: - export TZ=America/Chicago - - curl -L https://git.io/vp6lP | sh + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.13.2 - go get github.com/mattn/goveralls - export PATH=$(pwd)/bin:$PATH @@ -15,5 +15,5 @@ script: - GO111MODULE=on go get ./... - GO111MODULE=on go mod vendor - GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1; - - ./bin/gometalinter --deadline=120s --exclude=test --exclude=mock --exclude=vendor --exclude=_example --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=megacheck --enable=ineffassign --enable=varcheck --enable=unconvert --enable=deadcode --enable=interfacer --enable=gotype ./... || travis_terminate 1; + - golangci-lint run || travis_terminate 1; - $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci diff --git a/backend/vendor/github.com/go-pkgz/lgr/README.md b/backend/vendor/github.com/go-pkgz/lgr/README.md index 7521ed3c..4ac2c61e 100644 --- a/backend/vendor/github.com/go-pkgz/lgr/README.md +++ b/backend/vendor/github.com/go-pkgz/lgr/README.md @@ -2,20 +2,20 @@ ## install -`go get github/go-pkgz/lgr` +`go get github.com/go-pkgz/lgr` ## usage ```go l := lgr.New(lgr.Debug, lgr.CallerFile) // allow debug and caller file info - l.Logf("INFO some important err message, %v", err) - l.Logf("DEBUG some less important err message, %v", err) + l.Logf("INFO some important message, %v", err) + l.Logf("DEBUG some less important message, %v", err) ``` output looks like this: ``` -2018/01/07 13:02:34.000 INFO {svc/handler.go:101 h.MyFunc1} some important err message, can't open file` -2018/01/07 13:02:34.015 DEBUG {svc/handler.go:155 h.MyFunc2} some less important err message, file is too small` +2018/01/07 13:02:34.000 INFO {svc/handler.go:101 h.MyFunc1} some important message, can't open file` +2018/01/07 13:02:34.015 DEBUG {svc/handler.go:155 h.MyFunc2} some less important message, file is too small` ``` _Without `lgr.Caller*` it will drop `{caller}` part_ @@ -32,7 +32,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_ `lgr.New` call accepts functional options: -- `lgr.Debug` - turn debug mode on. This allows messages with "DEBUG" level (filtered overwise) +- `lgr.Debug` - turn debug mode on to allow messages with "DEBUG" level (filtered overwise) - `lgr.CallerFile` - adds the caller file info - `lgr.CallerFunc` - adds the caller function info - `lgr.CallerPkg` - adds the caller package @@ -48,11 +48,11 @@ _Without `lgr.Caller*` it will drop `{caller}` part_ - `DEBUG` will be filtered unless `lgr.Debug` option defined - `INFO` and `WARN` don't have any special behavior attached - `ERROR` sends messages to both out and err writers -- `PANIC` and `FATAL` send messages to both out and err writers. In addition sends dump of callers and runtime info to err only, and call `os.Exit(1)`. +- `PANIC` and `FATAL` send messages to both out and err writers. In addition sends dump of callers and runtime info to err only, and calls `os.Exit(1)`. ### global logger -Users should avoid global logger and pass the concrete logger as a dependency. However, in some cases global logger may be needed, for example migration from stdlib `log` to `lgr`. For such cases `log "github.com/go-pkgz/lgr"` can be imported instead of `log` package. +Users **should avoid** global logger and pass the concrete logger as a dependency. However, in some cases a global logger may be needed, for example migration from stdlib `log` to `lgr`. For such cases `log "github.com/go-pkgz/lgr"` can be imported instead of `log` package. -Global logger provides `lgr.Printf`, `lgr.Print` and `lgr.Fatalf` functions. User can customize the logger by calling `lgr.Setup(options ...)`. The instance of this logger can be retried with `lgr.Default()` - \ No newline at end of file +Global logger provides `lgr.Printf`, `lgr.Print` and `lgr.Fatalf` functions. User can customize the logger by calling `lgr.Setup(options ...)`. The instance of this logger can be retrieved with `lgr.Default()` + diff --git a/backend/vendor/github.com/go-pkgz/lgr/interface.go b/backend/vendor/github.com/go-pkgz/lgr/interface.go index e1a44026..80259cb3 100644 --- a/backend/vendor/github.com/go-pkgz/lgr/interface.go +++ b/backend/vendor/github.com/go-pkgz/lgr/interface.go @@ -43,7 +43,7 @@ func Fatalf(format string, args ...interface{}) { // Setup default logger with options func Setup(opts ...Option) { def = New(opts...) - def.skipCallers = 2 + def.callerSkip = 2 } // Default returns pre-constructed def logger (debug off, callers disabled) diff --git a/backend/vendor/github.com/go-pkgz/lgr/logger.go b/backend/vendor/github.com/go-pkgz/lgr/logger.go index ad12b974..d3346fd7 100644 --- a/backend/vendor/github.com/go-pkgz/lgr/logger.go +++ b/backend/vendor/github.com/go-pkgz/lgr/logger.go @@ -21,12 +21,13 @@ type Logger struct { callerFile bool callerFunc bool callerPkg bool + callerSkip int ignoredPkgCallers []string - now nowFn - fatal panicFn - skipCallers int - levelBraces bool - msec bool + + now nowFn + fatal panicFn + levelBraces bool + msec bool } type nowFn func() time.Time @@ -36,11 +37,11 @@ type panicFn func() // Two writers can be passed optionally - first for out and second for err func New(options ...Option) *Logger { res := Logger{ - now: time.Now, - fatal: func() { os.Exit(1) }, - stdout: os.Stdout, - stderr: os.Stderr, - skipCallers: 1, + now: time.Now, + fatal: func() { os.Exit(1) }, + stdout: os.Stdout, + stderr: os.Stderr, + callerSkip: 1, } for _, opt := range options { opt(&res) @@ -72,7 +73,7 @@ func (l *Logger) Logf(format string, args ...interface{}) { bld.WriteString(" ") if l.callerFile || l.callerFunc || l.callerPkg { - if pc, file, line, ok := runtime.Caller(l.skipCallers); ok { + if pc, file, line, ok := runtime.Caller(l.callerSkip); ok { funcName, fileInfo := "", "" @@ -217,6 +218,14 @@ func CallerIgnore(ignores ...string) Option { } } +// CallerSkip sets how many trace levels to skip. +// by default this value is 1 , i.e. skip logger level only +func CallerSkip(n int) Option { + return func(l *Logger) { + l.callerSkip = n + } +} + // LevelBraces adds [] to level func LevelBraces(l *Logger) { l.levelBraces = true diff --git a/backend/vendor/github.com/go-pkgz/rest/README.md b/backend/vendor/github.com/go-pkgz/rest/README.md index 8a6c6f05..0ee91e43 100644 --- a/backend/vendor/github.com/go-pkgz/rest/README.md +++ b/backend/vendor/github.com/go-pkgz/rest/README.md @@ -59,6 +59,16 @@ Metrics middleware responds to GET /metrics with list of [expvar](https://golang BlackWords middleware doesn't allow user-defined words in the request body. +### SizeLimit middleware + +SizeLimit middleware checks if body size is above the limit and returns `StatusRequestEntityTooLarge` (413) + +### Trace middleware + +It looks for `X-Request-ID` header and makes it as a random id + (if not found), then populates it to the result's header + and to the request's context. + ## Helpers - `rest.JSON` - map alias, just for convenience `type JSON map[string]interface{}` diff --git a/backend/vendor/github.com/go-pkgz/rest/cache/README.md b/backend/vendor/github.com/go-pkgz/rest/cache/README.md index 8b0cef8f..1cd1bfad 100644 --- a/backend/vendor/github.com/go-pkgz/rest/cache/README.md +++ b/backend/vendor/github.com/go-pkgz/rest/cache/README.md @@ -39,5 +39,5 @@ In addition to `Get` and `Flush` methods, memory cache also support limits for a return []byte("1234567890"), nil }) - lc.Flush("scope1") // invalidate cache for scope1 + lc.Flush(cache.Flusher("site1").Scopes("scope1")) // invalidate cache for scope1 ``` diff --git a/backend/vendor/github.com/go-pkgz/rest/cache/cache.go b/backend/vendor/github.com/go-pkgz/rest/cache/cache.go index 2270cd8a..921bf2bc 100644 --- a/backend/vendor/github.com/go-pkgz/rest/cache/cache.go +++ b/backend/vendor/github.com/go-pkgz/rest/cache/cache.go @@ -1,3 +1,9 @@ +// Package cache implements a wrapper on top of hashicorp/golang-lru with guava-style loader func. +// In addition for Get (i.e. load from cache if found and put to cache if absent) it adds a Key struct +// with record ID, site ID and invalidation scopes. Scopes used to evict matching records. +// Additional limits added for total cache size, mac number of keys and max size of the value. If exceeded it won't +// put to cache but will call loader func. +// Usually the cache involved on []byte response level, i.e. post-marshaling right before response's send. package cache import ( @@ -8,16 +14,16 @@ import ( // LoadingCache defines interface for caching type LoadingCache interface { - Get(key Key, fn func() ([]byte, error)) (data []byte, err error) - Flush(req FlusherRequest) + Get(key Key, fn func() ([]byte, error)) (data []byte, err error) // load from cache if found or put to cache and return + Flush(req FlusherRequest) // evict matched records } type cacheWithOpts interface { LoadingCache - setMaxValSize(max int) error - setMaxKeys(max int) error - setMaxCacheSize(max int64) error - setPostFlushFn(postFlushFn func()) error + setMaxValSize(max int) error // max value size, in bytes + setMaxKeys(max int) error // max number of keys + setMaxCacheSize(max int64) error // max cache size (total values) in bytes + setPostFlushFn(postFlushFn func()) error // optional callback after flush } // Key for cache diff --git a/backend/vendor/github.com/go-pkgz/rest/logger/logger.go b/backend/vendor/github.com/go-pkgz/rest/logger/logger.go index 3a9c7bd6..7c9d2ca7 100644 --- a/backend/vendor/github.com/go-pkgz/rest/logger/logger.go +++ b/backend/vendor/github.com/go-pkgz/rest/logger/logger.go @@ -4,41 +4,28 @@ import ( "bufio" "bytes" "fmt" + "io" "io/ioutil" "log" "net" "net/http" "net/url" "regexp" - "sort" "strings" "time" ) -var reMultWhtsp = regexp.MustCompile(`[\s\p{Zs}]{2,}`) - -// Middleware for logging rest requests +// Middleware is a logger for rest requests. type Middleware struct { prefix string + logBody bool maxBodySize int - flags []Flag ipFn func(ip string) string userFn func(r *http.Request) (string, error) subjFn func(r *http.Request) (string, error) log Backend } -// Flag type -type Flag int - -// logger flags enum -const ( - All Flag = iota - User - Body - None -) - // Backend is logging backend type Backend interface { Logf(format string, args ...interface{}) @@ -50,19 +37,18 @@ func (s stdBackend) Logf(format string, args ...interface{}) { log.Printf(format, args...) } -// Logger returns default logger middleware with REST prefix +// Logger is a default logger middleware with "REST" prefix func Logger(next http.Handler) http.Handler { l := New(Prefix("REST")) return l.Handler(next) } -// New makes rest Logger with given options +// New makes rest logger with given options func New(options ...Option) *Middleware { res := Middleware{ prefix: "", maxBodySize: 1024, - flags: []Flag{All}, log: stdBackend{}, } for _, opt := range options { @@ -75,28 +61,28 @@ func New(options ...Option) *Middleware { func (l *Middleware) Handler(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { + ww := newCustomResponseWriter(w) - if l.inLogFlags(None) { // skip logging - next.ServeHTTP(w, r) - return + user := "" + if l.userFn != nil { + if u, err := l.userFn(r); err == nil { + user = u + } } - ww := newCustomResponseWriter(w) - body, user := l.getBodyAndUser(r) + body := l.getBody(r) t1 := time.Now() defer func() { t2 := time.Now() - q := l.sanitizeQuery(r.URL.String()) - if qun, err := url.QueryUnescape(q); err == nil { - q = qun - } - - remoteIP := strings.Split(r.RemoteAddr, ":")[0] - if strings.HasPrefix(r.RemoteAddr, "[") { - remoteIP = strings.Split(r.RemoteAddr, "]:")[0] + "]" + u := *r.URL // shallow copy + u.RawQuery = l.sanitizeQuery(u.RawQuery) + rawurl := u.String() + if unescURL, err := url.QueryUnescape(rawurl); err == nil { + rawurl = unescURL } + remoteIP := l.remoteIP(r) if l.ipFn != nil { // mask ip with ipFn remoteIP = l.ipFn(remoteIP) } @@ -107,7 +93,7 @@ func (l *Middleware) Handler(next http.Handler) http.Handler { bld.WriteString(" ") } - bld.WriteString(fmt.Sprintf("%s - %s - %s - %d (%d) - %v", r.Method, q, remoteIP, ww.status, ww.size, t2.Sub(t1))) + bld.WriteString(fmt.Sprintf("%s - %s - %s - %d (%d) - %v", r.Method, rawurl, remoteIP, ww.status, ww.size, t2.Sub(t1))) if user != "" { bld.WriteString(" - ") @@ -139,84 +125,114 @@ func (l *Middleware) Handler(next http.Handler) http.Handler { return http.HandlerFunc(fn) } -func (l *Middleware) getBodyAndUser(r *http.Request) (body string, user string) { - ctx := r.Context() - if ctx == nil { - return "", "" +var reMultWhtsp = regexp.MustCompile(`[\s\p{Zs}]{2,}`) + +func (l *Middleware) getBody(r *http.Request) string { + if !l.logBody { + return "" } - if l.inLogFlags(Body) { - if content, err := ioutil.ReadAll(r.Body); err == nil { - body = string(content) - r.Body = ioutil.NopCloser(bytes.NewReader(content)) - - if len(body) > 0 { - body = strings.Replace(body, "\n", " ", -1) - body = reMultWhtsp.ReplaceAllString(body, " ") - } - - if len(body) > l.maxBodySize { - body = body[:l.maxBodySize] + "..." - } - } + reader, body, hasMore, err := peek(r.Body, int64(l.maxBodySize)) + if err != nil { + return "" } - if l.inLogFlags(User) && l.userFn != nil { - u, err := l.userFn(r) - if err == nil && u != "" { - user = u - } + // "The Server will close the request body. The ServeHTTP Handler does not need to." + // https://golang.org/pkg/net/http/#Request + // So we can use ioutil.NopCloser() to make io.ReadCloser. + // Note that below assignment is not approved by the docs: + // "Except for reading the body, handlers should not modify the provided Request." + // https://golang.org/pkg/net/http/#Handler + r.Body = ioutil.NopCloser(reader) + + if len(body) > 0 { + body = strings.Replace(body, "\n", " ", -1) + body = reMultWhtsp.ReplaceAllString(body, " ") } - return body, user + if hasMore { + body += "..." + } + + return body } -func (l *Middleware) inLogFlags(f Flag) bool { - for _, flg := range l.flags { - if (flg == All && f != None) || flg == f { - return true - } +// peek the first n bytes as string +func peek(r io.Reader, n int64) (reader io.Reader, s string, hasMore bool, err error) { + if n < 0 { + n = 0 } - return false + + buf := new(bytes.Buffer) + _, err = io.CopyN(buf, r, n+1) + if err == io.EOF { + return buf, buf.String(), false, nil + } + if err != nil { + return r, "", false, err + } + + // one extra byte is successfully read + s = buf.String() + s = s[:len(s)-1] + + return io.MultiReader(buf, r), s, true, nil } -var hideWords = []string{"password", "passwd", "secret", "credentials", "token"} +var keysToHide = []string{"password", "passwd", "secret", "credentials", "token"} -// hide query values for hideWords. May change order of query params -func (l *Middleware) sanitizeQuery(inp string) string { +// Hide query values for keysToHide. May change order of query params. +// May escape unescaped query params. +func (l *Middleware) sanitizeQuery(rawQuery string) string { + // note that we skip non-nil error further + query, err := url.ParseQuery(rawQuery) - inHiddenWords := func(str string) bool { - for _, w := range hideWords { - if strings.EqualFold(w, str) { + isHidden := func(key string) bool { + for _, k := range keysToHide { + if strings.EqualFold(k, key) { return true } } return false } - parts := strings.SplitN(inp, "?", 2) - if len(parts) < 2 { - return inp - } - - q, e := url.ParseQuery(parts[1]) - if e != nil || len(q) == 0 { - return inp - } - - res := []string{} - for k, v := range q { - if inHiddenWords(k) { - res = append(res, fmt.Sprintf("%s=********", k)) - } else { - res = append(res, fmt.Sprintf("%s=%v", k, v[0])) + present := false + for key, values := range query { + if isHidden(key) { + present = true + for i := range values { + values[i] = "********" + } } } - sort.Strings(res) // to make testing persistent - return parts[0] + "?" + strings.Join(res, "&") + + // short circuit + if (err == nil) && !present { + return rawQuery + } + + return query.Encode() } -// customResponseWriter implements ResponseWriter and keeping status and size +// remoteIP gets address from X-Forwarded-For and than from request's remote address +func (l *Middleware) remoteIP(r *http.Request) (remoteIP string) { + + if remoteIP = r.Header.Get("X-Forwarded-For"); remoteIP == "" { + remoteIP = r.RemoteAddr + } + remoteIP = strings.Split(remoteIP, ":")[0] + if strings.HasPrefix(remoteIP, "[") { + remoteIP = strings.Split(remoteIP, "]:")[0] + "]" + } + return remoteIP +} + +// customResponseWriter is an HTTP response logger that keeps HTTP status code and +// the number of bytes written. +// It implements http.ResponseWriter, http.Flusher and http.Hijacker. +// Note that type assertion from http.ResponseWriter(customResponseWriter) to +// http.Flusher and http.Hijacker is always succeed but underlying http.ResponseWriter +// may not implement them. type customResponseWriter struct { http.ResponseWriter status int @@ -230,27 +246,27 @@ func newCustomResponseWriter(w http.ResponseWriter) *customResponseWriter { } } -// WriteHeader implements ResponseWriter and saves status +// WriteHeader implements http.ResponseWriter and saves status func (c *customResponseWriter) WriteHeader(status int) { c.status = status c.ResponseWriter.WriteHeader(status) } -// WriteHeader implements ResponseWriter and tracking size +// Write implements http.ResponseWriter and tracks number of bytes written func (c *customResponseWriter) Write(b []byte) (int, error) { size, err := c.ResponseWriter.Write(b) c.size += size return size, err } -// Flush implements ResponseWriter +// Flush implements http.Flusher func (c *customResponseWriter) Flush() { if f, ok := c.ResponseWriter.(http.Flusher); ok { f.Flush() } } -// Hijack implements ResponseWriter +// Hijack implements http.Hijacker func (c *customResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if hj, ok := c.ResponseWriter.(http.Hijacker); ok { return hj.Hijack() diff --git a/backend/vendor/github.com/go-pkgz/rest/logger/options.go b/backend/vendor/github.com/go-pkgz/rest/logger/options.go index 57f6e0d1..21b36ea3 100644 --- a/backend/vendor/github.com/go-pkgz/rest/logger/options.go +++ b/backend/vendor/github.com/go-pkgz/rest/logger/options.go @@ -7,14 +7,12 @@ import ( // Option func type type Option func(l *Middleware) -// Flags functional option defines output modes -func Flags(flags ...Flag) Option { - return func(l *Middleware) { - l.flags = flags - } +// WithBody triggers request body logging. Body size is limited (default 1k) +func WithBody(l *Middleware) { + l.logBody = true } -// MaxBodySize functional option defines the largest body size to log. +// MaxBodySize sets size of the logged part of the request body. func MaxBodySize(max int) Option { return func(l *Middleware) { if max >= 0 { @@ -23,35 +21,35 @@ func MaxBodySize(max int) Option { } } -// Prefix functional option defines log line prefix. +// Prefix sets log line prefix. func Prefix(prefix string) Option { return func(l *Middleware) { l.prefix = prefix } } -// IPfn functional option defines ip masking function. +// IPfn sets IP masking function. If ipFn is nil then IP address will be logged as is. func IPfn(ipFn func(ip string) string) Option { return func(l *Middleware) { l.ipFn = ipFn } } -// UserFn functional option defines user name function. +// UserFn triggers user name logging if userFn is not nil. func UserFn(userFn func(r *http.Request) (string, error)) Option { return func(l *Middleware) { l.userFn = userFn } } -// SubjFn functional option defines subject function. -func SubjFn(userFn func(r *http.Request) (string, error)) Option { +// SubjFn triggers subject logging if subjFn is not nil. +func SubjFn(subjFn func(r *http.Request) (string, error)) Option { return func(l *Middleware) { - l.subjFn = userFn + l.subjFn = subjFn } } -// Log functional option defines loging backend. +// Log sets logging backend. func Log(log Backend) Option { return func(l *Middleware) { l.log = log diff --git a/backend/vendor/github.com/go-pkgz/rest/sizelimit.go b/backend/vendor/github.com/go-pkgz/rest/sizelimit.go new file mode 100644 index 00000000..1b6be67a --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/rest/sizelimit.go @@ -0,0 +1,39 @@ +package rest + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" +) + +// SizeLimit middleware checks if body size is above the limit and returns StatusRequestEntityTooLarge (413) +func SizeLimit(size int64) func(http.Handler) http.Handler { + + return func(h http.Handler) http.Handler { + + fn := func(w http.ResponseWriter, r *http.Request) { + + // check ContentLength + if r.ContentLength > size { + w.WriteHeader(http.StatusRequestEntityTooLarge) + return + } + + // check size of the actual body + content, err := ioutil.ReadAll(io.LimitReader(r.Body, size+1)) + if err != nil { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + if int64(len(content)) > size { + w.WriteHeader(http.StatusRequestEntityTooLarge) + return + } + r.Body = ioutil.NopCloser(bytes.NewReader(content)) + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) + } +} diff --git a/backend/vendor/github.com/go-pkgz/rest/trace.go b/backend/vendor/github.com/go-pkgz/rest/trace.go index d9701450..420ff941 100644 --- a/backend/vendor/github.com/go-pkgz/rest/trace.go +++ b/backend/vendor/github.com/go-pkgz/rest/trace.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/sha1" + "encoding/hex" "fmt" "net/http" "time" @@ -38,17 +39,10 @@ func GetTraceID(r *http.Request) string { } func randToken() string { - fallback := func() string { - return fmt.Sprintf("%x", time.Now().Nanosecond()) - } - b := make([]byte, 32) if _, err := rand.Read(b); err != nil { - return fallback() + return fmt.Sprintf("%x", time.Now().Nanosecond()) } - s := sha1.New() - if _, err := s.Write(b); err != nil { - return fallback() - } - return fmt.Sprintf("%x", s.Sum(nil)) + sum := sha1.Sum(b) + return hex.EncodeToString(sum[:]) }