diff --git a/go.mod b/go.mod
index c28c8d470b..0d1ebc1915 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.14
 require (
 	code.gitea.io/gitea-vet v0.2.1
 	code.gitea.io/sdk/gitea v0.13.1
+	gitea.com/go-chi/session v0.0.0-20201218134809-7209fa084f27
 	gitea.com/lunny/levelqueue v0.3.0
 	gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
 	gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b
@@ -33,7 +34,7 @@ require (
 	github.com/ethantkoenig/rupture v0.0.0-20181029165146-c3b3b810dc77
 	github.com/gliderlabs/ssh v0.3.1
 	github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
-	github.com/go-chi/chi v1.5.0
+	github.com/go-chi/chi v1.5.1
 	github.com/go-enry/go-enry/v2 v2.6.0
 	github.com/go-git/go-billy/v5 v5.0.0
 	github.com/go-git/go-git/v5 v5.2.0
@@ -78,7 +79,6 @@ require (
 	github.com/niklasfasching/go-org v1.3.2
 	github.com/oliamb/cutter v0.2.2
 	github.com/olivere/elastic/v7 v7.0.21
-	github.com/onsi/ginkgo v1.13.0 // indirect
 	github.com/pelletier/go-toml v1.8.1
 	github.com/pierrec/lz4/v4 v4.1.1 // indirect
 	github.com/pkg/errors v0.9.1
@@ -98,6 +98,7 @@ require (
 	github.com/unknwon/com v1.0.1
 	github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c
 	github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
+	github.com/unrolled/render v1.0.3
 	github.com/urfave/cli v1.22.5
 	github.com/willf/bitset v1.1.11 // indirect
 	github.com/xanzy/go-gitlab v0.39.0
@@ -114,7 +115,6 @@ require (
 	golang.org/x/text v0.3.4
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
 	golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9
-	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 	gopkg.in/ini.v1 v1.62.0
diff --git a/go.sum b/go.sum
index 66b8083e0e..ad51f4c67a 100644
--- a/go.sum
+++ b/go.sum
@@ -40,6 +40,8 @@ code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFj
 code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
 code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gitea.com/go-chi/session v0.0.0-20201218134809-7209fa084f27 h1:cdb1OTNXGLwQ55gg+9tIPWufdsnrHWcIq8Qs+j/E8JU=
+gitea.com/go-chi/session v0.0.0-20201218134809-7209fa084f27/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0=
 gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
 gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
 gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
@@ -227,6 +229,8 @@ github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+
 github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
 github.com/couchbase/gomemcached v0.1.0 h1:whUde87n8CScx8ckMp2En5liqAlcuG3aKy/BQeBPu84=
 github.com/couchbase/gomemcached v0.1.0/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
+github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc=
+github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
 github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4=
 github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
@@ -264,6 +268,7 @@ github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
@@ -283,6 +288,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
 github.com/editorconfig/editorconfig-core-go/v2 v2.3.9 h1:4vZN3UCLAUbT408wDutTKGZwOlgGMpV3vhahYufNbV8=
 github.com/editorconfig/editorconfig-core-go/v2 v2.3.9/go.mod h1:yoHDFR3nO8O5ssvhITSRsf0owQqIs0c9+nBTtarunPo=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
@@ -326,8 +333,8 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy
 github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
 github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
 github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-chi/chi v1.5.0 h1:2ZcJZozJ+rj6BA0c19ffBUGXEKAT/aOLOtQjD46vBRA=
-github.com/go-chi/chi v1.5.0/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
+github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
+github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
 github.com/go-enry/go-enry/v2 v2.6.0 h1:nbGWQBpO+D+cJuRxNgSDFnFY9QWz3QM/CeZxU7VAH20=
 github.com/go-enry/go-enry/v2 v2.6.0/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
@@ -422,6 +429,7 @@ github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDA
 github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
 github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
+github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@@ -519,6 +527,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
@@ -871,8 +880,8 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
-github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
+github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
+github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
@@ -880,6 +889,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
+github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@@ -977,7 +988,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -1086,6 +1096,8 @@ github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0
 github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
 github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs=
 github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae/go.mod h1:1fdkY6xxl6ExVs2QFv7R0F5IRZHKA8RahhB9fMC9RvM=
+github.com/unrolled/render v1.0.3 h1:baO+NG1bZSF2WR4zwh+0bMWauWky7DVrTOfvE2w+aFo=
+github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
@@ -1142,6 +1154,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -1258,6 +1271,7 @@ golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=
 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index 16ea9f15e3..1f4b9ec5be 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -9,13 +9,10 @@ import (
 	"reflect"
 	"strings"
 
-	"code.gitea.io/gitea/models"
-	"code.gitea.io/gitea/modules/auth/sso"
 	"code.gitea.io/gitea/modules/validation"
 
 	"gitea.com/macaron/binding"
 	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
 	"github.com/unknwon/com"
 )
 
@@ -24,28 +21,6 @@ func IsAPIPath(url string) bool {
 	return strings.HasPrefix(url, "/api/")
 }
 
-// SignedInUser returns the user object of signed user.
-// It returns a bool value to indicate whether user uses basic auth or not.
-func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool) {
-	if !models.HasEngine {
-		return nil, false
-	}
-
-	// Try to sign in with each of the enabled plugins
-	for _, ssoMethod := range sso.Methods() {
-		if !ssoMethod.IsEnabled() {
-			continue
-		}
-		user := ssoMethod.VerifyAuthData(ctx, sess)
-		if user != nil {
-			_, isBasic := ssoMethod.(*sso.Basic)
-			return user, isBasic
-		}
-	}
-
-	return nil, false
-}
-
 // Form form binding interface
 type Form interface {
 	binding.Validator
diff --git a/modules/auth/sso/basic.go b/modules/auth/sso/basic.go
index aab4eceebc..2db1147fc4 100644
--- a/modules/auth/sso/basic.go
+++ b/modules/auth/sso/basic.go
@@ -6,6 +6,7 @@
 package sso
 
 import (
+	"net/http"
 	"strings"
 
 	"code.gitea.io/gitea/models"
@@ -13,9 +14,6 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
-
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
 )
 
 // Ensure the struct implements the interface.
@@ -49,8 +47,8 @@ func (b *Basic) IsEnabled() bool {
 // "Authorization" header of the request and returns the corresponding user object for that
 // name/token on successful validation.
 // Returns nil if header is empty or validation fails.
-func (b *Basic) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User {
-	baHead := ctx.Req.Header.Get("Authorization")
+func (b *Basic) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User {
+	baHead := req.Header.Get("Authorization")
 	if len(baHead) == 0 {
 		return nil
 	}
@@ -75,7 +73,7 @@ func (b *Basic) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models
 	uid := CheckOAuthAccessToken(authToken)
 	if uid != 0 {
 		var err error
-		ctx.Data["IsApiToken"] = true
+		store.GetData()["IsApiToken"] = true
 
 		u, err = models.GetUserByID(uid)
 		if err != nil {
@@ -108,7 +106,7 @@ func (b *Basic) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models
 			return nil
 		}
 	} else {
-		ctx.Data["IsApiToken"] = true
+		store.GetData()["IsApiToken"] = true
 	}
 
 	return u
diff --git a/modules/auth/sso/interface.go b/modules/auth/sso/interface.go
index 89cc227e79..5f7089a2da 100644
--- a/modules/auth/sso/interface.go
+++ b/modules/auth/sso/interface.go
@@ -5,12 +5,23 @@
 package sso
 
 import (
-	"code.gitea.io/gitea/models"
+	"net/http"
 
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
+	"code.gitea.io/gitea/models"
 )
 
+// DataStore represents a data store
+type DataStore interface {
+	GetData() map[string]interface{}
+}
+
+// SessionStore represents a session store
+type SessionStore interface {
+	Get(interface{}) interface{}
+	Set(interface{}, interface{}) error
+	Delete(interface{}) error
+}
+
 // SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
 type SingleSignOn interface {
 	// Init should be called exactly once before using any of the other methods,
@@ -29,5 +40,5 @@ type SingleSignOn interface {
 	// or a new user object (with id = 0) populated with the information that was found
 	// in the authentication data (username or email).
 	// Returns nil if verification fails.
-	VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User
+	VerifyAuthData(http *http.Request, store DataStore, sess SessionStore) *models.User
 }
diff --git a/modules/auth/sso/oauth2.go b/modules/auth/sso/oauth2.go
index 3f530f036f..5c15ed4257 100644
--- a/modules/auth/sso/oauth2.go
+++ b/modules/auth/sso/oauth2.go
@@ -6,15 +6,13 @@
 package sso
 
 import (
+	"net/http"
 	"strings"
 	"time"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
-
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
 )
 
 // Ensure the struct implements the interface.
@@ -63,15 +61,15 @@ func (o *OAuth2) Free() error {
 }
 
 // userIDFromToken returns the user id corresponding to the OAuth token.
-func (o *OAuth2) userIDFromToken(ctx *macaron.Context) int64 {
+func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 {
 	// Check access token.
-	tokenSHA := ctx.Query("token")
+	tokenSHA := req.Form.Get("token")
 	if len(tokenSHA) == 0 {
-		tokenSHA = ctx.Query("access_token")
+		tokenSHA = req.Form.Get("access_token")
 	}
 	if len(tokenSHA) == 0 {
 		// Well, check with header again.
-		auHead := ctx.Req.Header.Get("Authorization")
+		auHead := req.Header.Get("Authorization")
 		if len(auHead) > 0 {
 			auths := strings.Fields(auHead)
 			if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") {
@@ -87,7 +85,7 @@ func (o *OAuth2) userIDFromToken(ctx *macaron.Context) int64 {
 	if strings.Contains(tokenSHA, ".") {
 		uid := CheckOAuthAccessToken(tokenSHA)
 		if uid != 0 {
-			ctx.Data["IsApiToken"] = true
+			store.GetData()["IsApiToken"] = true
 		}
 		return uid
 	}
@@ -102,7 +100,7 @@ func (o *OAuth2) userIDFromToken(ctx *macaron.Context) int64 {
 	if err = models.UpdateAccessToken(t); err != nil {
 		log.Error("UpdateAccessToken: %v", err)
 	}
-	ctx.Data["IsApiToken"] = true
+	store.GetData()["IsApiToken"] = true
 	return t.UID
 }
 
@@ -116,16 +114,16 @@ func (o *OAuth2) IsEnabled() bool {
 // or the "Authorization" header and returns the corresponding user object for that ID.
 // If verification is successful returns an existing user object.
 // Returns nil if verification fails.
-func (o *OAuth2) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User {
+func (o *OAuth2) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User {
 	if !models.HasEngine {
 		return nil
 	}
 
-	if isInternalPath(ctx) || !isAPIPath(ctx) && !isAttachmentDownload(ctx) {
+	if isInternalPath(req) || !isAPIPath(req) && !isAttachmentDownload(req) {
 		return nil
 	}
 
-	id := o.userIDFromToken(ctx)
+	id := o.userIDFromToken(req, store)
 	if id <= 0 {
 		return nil
 	}
diff --git a/modules/auth/sso/reverseproxy.go b/modules/auth/sso/reverseproxy.go
index 1b543ce104..c1bd4e3959 100644
--- a/modules/auth/sso/reverseproxy.go
+++ b/modules/auth/sso/reverseproxy.go
@@ -6,14 +6,13 @@
 package sso
 
 import (
+	"net/http"
 	"strings"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
 	gouuid "github.com/google/uuid"
 )
 
@@ -31,8 +30,8 @@ type ReverseProxy struct {
 }
 
 // getUserName extracts the username from the "setting.ReverseProxyAuthUser" header
-func (r *ReverseProxy) getUserName(ctx *macaron.Context) string {
-	webAuthUser := strings.TrimSpace(ctx.Req.Header.Get(setting.ReverseProxyAuthUser))
+func (r *ReverseProxy) getUserName(req *http.Request) string {
+	webAuthUser := strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser))
 	if len(webAuthUser) == 0 {
 		return ""
 	}
@@ -61,8 +60,8 @@ func (r *ReverseProxy) IsEnabled() bool {
 // If a username is available in the "setting.ReverseProxyAuthUser" header an existing
 // user object is returned (populated with username or email found in header).
 // Returns nil if header is empty.
-func (r *ReverseProxy) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User {
-	username := r.getUserName(ctx)
+func (r *ReverseProxy) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User {
+	username := r.getUserName(req)
 	if len(username) == 0 {
 		return nil
 	}
@@ -70,7 +69,7 @@ func (r *ReverseProxy) VerifyAuthData(ctx *macaron.Context, sess session.Store)
 	user, err := models.GetUserByName(username)
 	if err != nil {
 		if models.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() {
-			return r.newUser(ctx)
+			return r.newUser(req)
 		}
 		log.Error("GetUserByName: %v", err)
 		return nil
@@ -86,15 +85,15 @@ func (r *ReverseProxy) isAutoRegisterAllowed() bool {
 
 // newUser creates a new user object for the purpose of automatic registration
 // and populates its name and email with the information present in request headers.
-func (r *ReverseProxy) newUser(ctx *macaron.Context) *models.User {
-	username := r.getUserName(ctx)
+func (r *ReverseProxy) newUser(req *http.Request) *models.User {
+	username := r.getUserName(req)
 	if len(username) == 0 {
 		return nil
 	}
 
 	email := gouuid.New().String() + "@localhost"
 	if setting.Service.EnableReverseProxyEmail {
-		webAuthEmail := ctx.Req.Header.Get(setting.ReverseProxyAuthEmail)
+		webAuthEmail := req.Header.Get(setting.ReverseProxyAuthEmail)
 		if len(webAuthEmail) > 0 {
 			email = webAuthEmail
 		}
diff --git a/modules/auth/sso/session.go b/modules/auth/sso/session.go
index c9176b9c31..b64f477e02 100644
--- a/modules/auth/sso/session.go
+++ b/modules/auth/sso/session.go
@@ -5,10 +5,9 @@
 package sso
 
 import (
-	"code.gitea.io/gitea/models"
+	"net/http"
 
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
+	"code.gitea.io/gitea/models"
 )
 
 // Ensure the struct implements the interface.
@@ -40,7 +39,7 @@ func (s *Session) IsEnabled() bool {
 // VerifyAuthData checks if there is a user uid stored in the session and returns the user
 // object for that uid.
 // Returns nil if there is no user uid stored in the session.
-func (s *Session) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User {
+func (s *Session) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User {
 	user := SessionUser(sess)
 	if user != nil {
 		return user
diff --git a/modules/auth/sso/sso.go b/modules/auth/sso/sso.go
index c2e36f3f5e..d54310168e 100644
--- a/modules/auth/sso/sso.go
+++ b/modules/auth/sso/sso.go
@@ -7,15 +7,14 @@ package sso
 
 import (
 	"fmt"
+	"net/http"
 	"reflect"
 	"strings"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/middlewares"
 	"code.gitea.io/gitea/modules/setting"
-
-	"gitea.com/macaron/macaron"
-	"gitea.com/macaron/session"
 )
 
 // ssoMethods contains the list of SSO authentication plugins in the order they are expected to be
@@ -73,7 +72,7 @@ func Free() {
 }
 
 // SessionUser returns the user object corresponding to the "uid" session variable.
-func SessionUser(sess session.Store) *models.User {
+func SessionUser(sess SessionStore) *models.User {
 	// Get user ID
 	uid := sess.Get("uid")
 	if uid == nil {
@@ -96,22 +95,22 @@ func SessionUser(sess session.Store) *models.User {
 }
 
 // isAPIPath returns true if the specified URL is an API path
-func isAPIPath(ctx *macaron.Context) bool {
-	return strings.HasPrefix(ctx.Req.URL.Path, "/api/")
+func isAPIPath(req *http.Request) bool {
+	return strings.HasPrefix(req.URL.Path, "/api/")
 }
 
 // isInternalPath returns true if the specified URL is an internal API path
-func isInternalPath(ctx *macaron.Context) bool {
-	return strings.HasPrefix(ctx.Req.URL.Path, "/api/internal/")
+func isInternalPath(req *http.Request) bool {
+	return strings.HasPrefix(req.URL.Path, "/api/internal/")
 }
 
 // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
-func isAttachmentDownload(ctx *macaron.Context) bool {
-	return strings.HasPrefix(ctx.Req.URL.Path, "/attachments/") && ctx.Req.Method == "GET"
+func isAttachmentDownload(req *http.Request) bool {
+	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
 }
 
 // handleSignIn clears existing session variables and stores new ones for the specified user object
-func handleSignIn(ctx *macaron.Context, sess session.Store, user *models.User) {
+func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *models.User) {
 	_ = sess.Delete("openid_verified_uri")
 	_ = sess.Delete("openid_signin_remember")
 	_ = sess.Delete("openid_determined_email")
@@ -132,15 +131,16 @@ func handleSignIn(ctx *macaron.Context, sess session.Store, user *models.User) {
 	// Language setting of the user overwrites the one previously set
 	// If the user does not have a locale set, we save the current one.
 	if len(user.Language) == 0 {
-		user.Language = ctx.Locale.Language()
+		lc := middlewares.Locale(resp, req)
+		user.Language = lc.Language()
 		if err := models.UpdateUserCols(user, "language"); err != nil {
 			log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", user.ID, user.Language))
 			return
 		}
 	}
 
-	ctx.SetCookie("lang", user.Language, nil, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
+	middlewares.SetCookie(resp, "lang", user.Language, nil, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
 
 	// Clear whatever CSRF has right now, force to generate a new one
-	ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
+	middlewares.SetCookie(resp, setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
 }
diff --git a/modules/auth/sso/sspi_windows.go b/modules/auth/sso/sspi_windows.go
index 62013737ca..5850dcf6e2 100644
--- a/modules/auth/sso/sspi_windows.go
+++ b/modules/auth/sso/sspi_windows.go
@@ -6,6 +6,7 @@ package sso
 
 import (
 	"errors"
+	"net/http"
 	"reflect"
 	"strings"
 
@@ -64,7 +65,7 @@ func (s *SSPI) IsEnabled() bool {
 // If authentication is successful, returs the corresponding user object.
 // If negotiation should continue or authentication fails, immediately returns a 401 HTTP
 // response code, as required by the SPNEGO protocol.
-func (s *SSPI) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User {
+func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User {
 	if !s.shouldAuthenticate(ctx) {
 		return nil
 	}
@@ -75,7 +76,7 @@ func (s *SSPI) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.
 		return nil
 	}
 
-	userInfo, outToken, err := sspiAuth.Authenticate(ctx.Req.Request, ctx.Resp)
+	userInfo, outToken, err := sspiAuth.Authenticate(req, ctx.Resp)
 	if err != nil {
 		log.Warn("Authentication failed with error: %v\n", err)
 		sspiAuth.AppendAuthenticateHeader(ctx.Resp, outToken)
@@ -139,18 +140,18 @@ func (s *SSPI) getConfig() (*models.SSPIConfig, error) {
 	return sources[0].SSPI(), nil
 }
 
-func (s *SSPI) shouldAuthenticate(ctx *macaron.Context) (shouldAuth bool) {
+func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) {
 	shouldAuth = false
-	path := strings.TrimSuffix(ctx.Req.URL.Path, "/")
+	path := strings.TrimSuffix(req.URL.Path, "/")
 	if path == "/user/login" {
-		if ctx.Req.FormValue("user_name") != "" && ctx.Req.FormValue("password") != "" {
+		if req.FormValue("user_name") != "" && req.FormValue("password") != "" {
 			shouldAuth = false
 		} else if ctx.Req.FormValue("auth_with_sspi") == "1" {
 			shouldAuth = true
 		}
-	} else if isInternalPath(ctx) {
+	} else if isInternalPath(req) {
 		shouldAuth = false
-	} else if isAPIPath(ctx) || isAttachmentDownload(ctx) {
+	} else if isAPIPath(req) || isAttachmentDownload(req) {
 		shouldAuth = true
 	}
 	return
@@ -158,7 +159,7 @@ func (s *SSPI) shouldAuthenticate(ctx *macaron.Context) (shouldAuth bool) {
 
 // newUser creates a new user object for the purpose of automatic registration
 // and populates its name and email with the information present in request headers.
-func (s *SSPI) newUser(ctx *macaron.Context, username string, cfg *models.SSPIConfig) (*models.User, error) {
+func (s *SSPI) newUser(username string, cfg *models.SSPIConfig) (*models.User, error) {
 	email := gouuid.New().String() + "@localhost.localdomain"
 	user := &models.User{
 		Name:                         username,
diff --git a/modules/auth/sso/user.go b/modules/auth/sso/user.go
new file mode 100644
index 0000000000..69bbebccc7
--- /dev/null
+++ b/modules/auth/sso/user.go
@@ -0,0 +1,33 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package sso
+
+import (
+	"net/http"
+
+	"code.gitea.io/gitea/models"
+)
+
+// SignedInUser returns the user object of signed user.
+// It returns a bool value to indicate whether user uses basic auth or not.
+func SignedInUser(req *http.Request, ds DataStore, sess SessionStore) (*models.User, bool) {
+	if !models.HasEngine {
+		return nil, false
+	}
+
+	// Try to sign in with each of the enabled plugins
+	for _, ssoMethod := range Methods() {
+		if !ssoMethod.IsEnabled() {
+			continue
+		}
+		user := ssoMethod.VerifyAuthData(req, ds, sess)
+		if user != nil {
+			_, isBasic := ssoMethod.(*Basic)
+			return user, isBasic
+		}
+	}
+
+	return nil, false
+}
diff --git a/modules/context/context.go b/modules/context/context.go
index 2e43088ff8..c35a19480a 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -17,6 +17,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/auth"
+	"code.gitea.io/gitea/modules/auth/sso"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -48,6 +49,11 @@ type Context struct {
 	Org  *Organization
 }
 
+// GetData returns the data
+func (ctx *Context) GetData() map[string]interface{} {
+	return ctx.Data
+}
+
 // IsUserSiteAdmin returns true if current user is a site admin
 func (ctx *Context) IsUserSiteAdmin() bool {
 	return ctx.IsSigned && ctx.User.IsAdmin
@@ -303,7 +309,7 @@ func Contexter() macaron.Handler {
 		}
 
 		// Get user from session if logged in.
-		ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Context, ctx.Session)
+		ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, ctx, ctx.Session)
 
 		if ctx.User != nil {
 			ctx.IsSigned = true
diff --git a/modules/context/panic.go b/modules/context/panic.go
deleted file mode 100644
index e5ef233629..0000000000
--- a/modules/context/panic.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-// Copyright 2019 The Gitea Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package context
-
-import (
-	"fmt"
-
-	"code.gitea.io/gitea/modules/log"
-
-	"gitea.com/macaron/macaron"
-)
-
-// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
-// Although similar to macaron.Recovery() the main difference is that this error will be created
-// with the gitea 500 page.
-func Recovery() macaron.Handler {
-	return func(ctx *Context) {
-		defer func() {
-			if err := recover(); err != nil {
-				combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
-				ctx.ServerError("PANIC:", combinedErr)
-			}
-		}()
-
-		ctx.Next()
-	}
-}
diff --git a/modules/middlewares/cookie.go b/modules/middlewares/cookie.go
new file mode 100644
index 0000000000..80d0e3b453
--- /dev/null
+++ b/modules/middlewares/cookie.go
@@ -0,0 +1,104 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+import (
+	"net/http"
+	"net/url"
+	"time"
+
+	"code.gitea.io/gitea/modules/setting"
+)
+
+// NewCookie creates a cookie
+func NewCookie(name, value string, maxAge int) *http.Cookie {
+	return &http.Cookie{
+		Name:     name,
+		Value:    value,
+		HttpOnly: true,
+		Path:     setting.SessionConfig.CookiePath,
+		Domain:   setting.SessionConfig.Domain,
+		MaxAge:   maxAge,
+		Secure:   setting.SessionConfig.Secure,
+	}
+}
+
+// SetCookie set the cookies
+// TODO: Copied from gitea.com/macaron/macaron and should be improved after macaron removed.
+func SetCookie(resp http.ResponseWriter, name string, value string, others ...interface{}) {
+	cookie := http.Cookie{}
+	cookie.Name = name
+	cookie.Value = url.QueryEscape(value)
+
+	if len(others) > 0 {
+		switch v := others[0].(type) {
+		case int:
+			cookie.MaxAge = v
+		case int64:
+			cookie.MaxAge = int(v)
+		case int32:
+			cookie.MaxAge = int(v)
+		case func(*http.Cookie):
+			v(&cookie)
+		}
+	}
+
+	cookie.Path = "/"
+	if len(others) > 1 {
+		if v, ok := others[1].(string); ok && len(v) > 0 {
+			cookie.Path = v
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 2 {
+		if v, ok := others[2].(string); ok && len(v) > 0 {
+			cookie.Domain = v
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 3 {
+		switch v := others[3].(type) {
+		case bool:
+			cookie.Secure = v
+		case func(*http.Cookie):
+			v(&cookie)
+		default:
+			if others[3] != nil {
+				cookie.Secure = true
+			}
+		}
+	}
+
+	if len(others) > 4 {
+		if v, ok := others[4].(bool); ok && v {
+			cookie.HttpOnly = true
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 5 {
+		if v, ok := others[5].(time.Time); ok {
+			cookie.Expires = v
+			cookie.RawExpires = v.Format(time.UnixDate)
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 6 {
+		for _, other := range others[6:] {
+			if v, ok := other.(func(*http.Cookie)); ok {
+				v(&cookie)
+			}
+		}
+	}
+
+	resp.Header().Add("Set-Cookie", cookie.String())
+}
diff --git a/modules/middlewares/locale.go b/modules/middlewares/locale.go
new file mode 100644
index 0000000000..98af890cfd
--- /dev/null
+++ b/modules/middlewares/locale.go
@@ -0,0 +1,49 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+import (
+	"net/http"
+
+	"code.gitea.io/gitea/modules/translation"
+
+	"github.com/unknwon/i18n"
+	"golang.org/x/text/language"
+)
+
+// Locale handle locale
+func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
+	hasCookie := false
+
+	// 1. Check URL arguments.
+	lang := req.URL.Query().Get("lang")
+
+	// 2. Get language information from cookies.
+	if len(lang) == 0 {
+		ck, _ := req.Cookie("lang")
+		lang = ck.Value
+		hasCookie = true
+	}
+
+	// Check again in case someone modify by purpose.
+	if !i18n.IsExist(lang) {
+		lang = ""
+		hasCookie = false
+	}
+
+	// 3. Get language information from 'Accept-Language'.
+	// The first element in the list is chosen to be the default language automatically.
+	if len(lang) == 0 {
+		tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language"))
+		tag, _, _ := translation.Match(tags...)
+		lang = tag.String()
+	}
+
+	if !hasCookie {
+		req.AddCookie(NewCookie("lang", lang, 1<<31-1))
+	}
+
+	return translation.NewLocale(lang)
+}
diff --git a/modules/middlewares/redis.go b/modules/middlewares/redis.go
new file mode 100644
index 0000000000..ced1c1ee81
--- /dev/null
+++ b/modules/middlewares/redis.go
@@ -0,0 +1,217 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+// Copyright 2020 The Gitea Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package middlewares
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	"code.gitea.io/gitea/modules/nosql"
+
+	"gitea.com/go-chi/session"
+	"github.com/go-redis/redis/v7"
+)
+
+// RedisStore represents a redis session store implementation.
+// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
+type RedisStore struct {
+	c           redis.UniversalClient
+	prefix, sid string
+	duration    time.Duration
+	lock        sync.RWMutex
+	data        map[interface{}]interface{}
+}
+
+// NewRedisStore creates and returns a redis session store.
+func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
+	return &RedisStore{
+		c:        c,
+		prefix:   prefix,
+		sid:      sid,
+		duration: dur,
+		data:     kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *RedisStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *RedisStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *RedisStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *RedisStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *RedisStore) Release() error {
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Set(s.prefix+s.sid, string(data), s.duration).Err()
+}
+
+// Flush deletes all session data.
+func (s *RedisStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// RedisProvider represents a redis session provider implementation.
+type RedisProvider struct {
+	c        redis.UniversalClient
+	duration time.Duration
+	prefix   string
+}
+
+// Init initializes redis session provider.
+// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
+func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
+	p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
+	if err != nil {
+		return err
+	}
+
+	uri := nosql.ToRedisURI(configs)
+
+	for k, v := range uri.Query() {
+		switch k {
+		case "prefix":
+			p.prefix = v[0]
+		}
+	}
+
+	p.c = nosql.GetManager().GetRedisClient(uri.String())
+	return p.c.Ping().Err()
+}
+
+// Read returns raw session store by session ID.
+func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
+	psid := p.prefix + sid
+	if !p.Exist(sid) {
+		if err := p.c.Set(psid, "", p.duration).Err(); err != nil {
+			return nil, err
+		}
+	}
+
+	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get(psid).Result()
+	if err != nil {
+		return nil, err
+	}
+	if len(kvs) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob([]byte(kvs))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *RedisProvider) Exist(sid string) bool {
+	v, err := p.c.Exists(p.prefix + sid).Result()
+	return err == nil && v == 1
+}
+
+// Destroy deletes a session by session ID.
+func (p *RedisProvider) Destroy(sid string) error {
+	return p.c.Del(p.prefix + sid).Err()
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	poldsid := p.prefix + oldsid
+	psid := p.prefix + sid
+
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	} else if !p.Exist(oldsid) {
+		// Make a fake old session.
+		if err = p.c.Set(poldsid, "", p.duration).Err(); err != nil {
+			return nil, err
+		}
+	}
+
+	if err = p.c.Rename(poldsid, psid).Err(); err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get(psid).Result()
+	if err != nil {
+		return nil, err
+	}
+
+	if len(kvs) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob([]byte(kvs))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
+}
+
+// Count counts and returns number of sessions.
+func (p *RedisProvider) Count() int {
+	return int(p.c.DBSize().Val())
+}
+
+// GC calls GC to clean expired sessions.
+func (*RedisProvider) GC() {}
+
+func init() {
+	session.Register("redis", &RedisProvider{})
+}
diff --git a/modules/middlewares/virtual.go b/modules/middlewares/virtual.go
new file mode 100644
index 0000000000..70d780d65d
--- /dev/null
+++ b/modules/middlewares/virtual.go
@@ -0,0 +1,196 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+
+	"gitea.com/go-chi/session"
+	couchbase "gitea.com/go-chi/session/couchbase"
+	memcache "gitea.com/go-chi/session/memcache"
+	mysql "gitea.com/go-chi/session/mysql"
+	postgres "gitea.com/go-chi/session/postgres"
+)
+
+// VirtualSessionProvider represents a shadowed session provider implementation.
+// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
+type VirtualSessionProvider struct {
+	lock     sync.RWMutex
+	provider session.Provider
+}
+
+// Init initializes the cookie session provider with given root path.
+func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
+	var opts session.Options
+	if err := json.Unmarshal([]byte(config), &opts); err != nil {
+		return err
+	}
+	// Note that these options are unprepared so we can't just use NewManager here.
+	// Nor can we access the provider map in session.
+	// So we will just have to do this by hand.
+	// This is only slightly more wrong than modules/setting/session.go:23
+	switch opts.Provider {
+	case "memory":
+		o.provider = &session.MemProvider{}
+	case "file":
+		o.provider = &session.FileProvider{}
+	case "redis":
+		o.provider = &RedisProvider{}
+	case "mysql":
+		o.provider = &mysql.MysqlProvider{}
+	case "postgres":
+		o.provider = &postgres.PostgresProvider{}
+	case "couchbase":
+		o.provider = &couchbase.CouchbaseProvider{}
+	case "memcache":
+		o.provider = &memcache.MemcacheProvider{}
+	default:
+		return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
+	}
+	return o.provider.Init(gclifetime, opts.ProviderConfig)
+}
+
+// Read returns raw session store by session ID.
+func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
+	o.lock.RLock()
+	defer o.lock.RUnlock()
+	if o.provider.Exist(sid) {
+		return o.provider.Read(sid)
+	}
+	kv := make(map[interface{}]interface{})
+	kv["_old_uid"] = "0"
+	return NewVirtualStore(o, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (o *VirtualSessionProvider) Exist(sid string) bool {
+	return true
+}
+
+// Destroy deletes a session by session ID.
+func (o *VirtualSessionProvider) Destroy(sid string) error {
+	o.lock.Lock()
+	defer o.lock.Unlock()
+	return o.provider.Destroy(sid)
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
+	o.lock.Lock()
+	defer o.lock.Unlock()
+	return o.provider.Regenerate(oldsid, sid)
+}
+
+// Count counts and returns number of sessions.
+func (o *VirtualSessionProvider) Count() int {
+	o.lock.RLock()
+	defer o.lock.RUnlock()
+	return o.provider.Count()
+}
+
+// GC calls GC to clean expired sessions.
+func (o *VirtualSessionProvider) GC() {
+	o.provider.GC()
+}
+
+func init() {
+	session.Register("VirtualSession", &VirtualSessionProvider{})
+}
+
+// VirtualStore represents a virtual session store implementation.
+type VirtualStore struct {
+	p        *VirtualSessionProvider
+	sid      string
+	lock     sync.RWMutex
+	data     map[interface{}]interface{}
+	released bool
+}
+
+// NewVirtualStore creates and returns a virtual session store.
+func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
+	return &VirtualStore{
+		p:    p,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *VirtualStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *VirtualStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *VirtualStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *VirtualStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *VirtualStore) Release() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	// Now need to lock the provider
+	s.p.lock.Lock()
+	defer s.p.lock.Unlock()
+	if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
+		// Now ensure that we don't exist!
+		realProvider := s.p.provider
+
+		if !s.released && realProvider.Exist(s.sid) {
+			// This is an error!
+			return fmt.Errorf("new sid '%s' already exists", s.sid)
+		}
+		realStore, err := realProvider.Read(s.sid)
+		if err != nil {
+			return err
+		}
+		if err := realStore.Flush(); err != nil {
+			return err
+		}
+		for key, value := range s.data {
+			if err := realStore.Set(key, value); err != nil {
+				return err
+			}
+		}
+		err = realStore.Release()
+		if err == nil {
+			s.released = true
+		}
+		return err
+	}
+	return nil
+}
+
+// Flush deletes all session data.
+func (s *VirtualStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
diff --git a/modules/templates/base.go b/modules/templates/base.go
new file mode 100644
index 0000000000..a9b6b2737c
--- /dev/null
+++ b/modules/templates/base.go
@@ -0,0 +1,82 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package templates
+
+import (
+	"os"
+	"strings"
+	"time"
+
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+)
+
+// Vars represents variables to be render in golang templates
+type Vars map[string]interface{}
+
+// Merge merges another vars to the current, another Vars will override the current
+func (vars Vars) Merge(another map[string]interface{}) Vars {
+	for k, v := range another {
+		vars[k] = v
+	}
+	return vars
+}
+
+// BaseVars returns all basic vars
+func BaseVars() Vars {
+	var startTime = time.Now()
+	return map[string]interface{}{
+		"IsLandingPageHome":          setting.LandingPageURL == setting.LandingPageHome,
+		"IsLandingPageExplore":       setting.LandingPageURL == setting.LandingPageExplore,
+		"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
+
+		"ShowRegistrationButton":      setting.Service.ShowRegistrationButton,
+		"ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage,
+		"ShowFooterBranding":          setting.ShowFooterBranding,
+		"ShowFooterVersion":           setting.ShowFooterVersion,
+
+		"EnableSwagger":      setting.API.EnableSwagger,
+		"EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn,
+		"PageStartTime":      startTime,
+		"TmplLoadTimes": func() string {
+			return time.Since(startTime).String()
+		},
+	}
+}
+
+func getDirAssetNames(dir string) []string {
+	var tmpls []string
+	f, err := os.Stat(dir)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return tmpls
+		}
+		log.Warn("Unable to check if templates dir %s is a directory. Error: %v", dir, err)
+		return tmpls
+	}
+	if !f.IsDir() {
+		log.Warn("Templates dir %s is a not directory.", dir)
+		return tmpls
+	}
+
+	files, err := util.StatDir(dir)
+	if err != nil {
+		log.Warn("Failed to read %s templates dir. %v", dir, err)
+		return tmpls
+	}
+	for _, filePath := range files {
+		if strings.HasPrefix(filePath, "mail/") {
+			continue
+		}
+
+		if !strings.HasSuffix(filePath, ".tmpl") {
+			continue
+		}
+
+		tmpls = append(tmpls, "templates/"+filePath)
+	}
+	return tmpls
+}
diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go
index 7f1a36e0b7..f7f05e9b7c 100644
--- a/modules/templates/dynamic.go
+++ b/modules/templates/dynamic.go
@@ -9,7 +9,9 @@ package templates
 import (
 	"html/template"
 	"io/ioutil"
+	"os"
 	"path"
+	"path/filepath"
 	"strings"
 	texttmpl "text/template"
 
@@ -25,6 +27,25 @@ var (
 	bodyTemplates    = template.New("")
 )
 
+// GetAsset returns asset content via name
+func GetAsset(name string) ([]byte, error) {
+	bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
+	if err != nil && !os.IsNotExist(err) {
+		return nil, err
+	} else if err == nil {
+		return bs, nil
+	}
+
+	return ioutil.ReadFile(filepath.Join(setting.StaticRootPath, name))
+}
+
+// GetAssetNames returns assets list
+func GetAssetNames() []string {
+	tmpls := getDirAssetNames(filepath.Join(setting.CustomPath, "templates"))
+	tmpls2 := getDirAssetNames(filepath.Join(setting.StaticRootPath, "templates"))
+	return append(tmpls, tmpls2...)
+}
+
 // HTMLRenderer implements the macaron handler for serving HTML templates.
 func HTMLRenderer() macaron.Handler {
 	return macaron.Renderer(macaron.RenderOptions{
diff --git a/modules/templates/static.go b/modules/templates/static.go
index 0c3d65ccf2..1dd3d217fc 100644
--- a/modules/templates/static.go
+++ b/modules/templates/static.go
@@ -12,7 +12,9 @@ import (
 	"html/template"
 	"io"
 	"io/ioutil"
+	"os"
 	"path"
+	"path/filepath"
 	"strings"
 	texttmpl "text/template"
 
@@ -46,6 +48,30 @@ func (templates templateFileSystem) Get(name string) (io.Reader, error) {
 	return nil, fmt.Errorf("file '%s' not found", name)
 }
 
+// GetAsset get a special asset, only for chi
+func GetAsset(name string) ([]byte, error) {
+	bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
+	if err != nil && !os.IsNotExist(err) {
+		return nil, err
+	} else if err == nil {
+		return bs, nil
+	}
+	return Asset(strings.TrimPrefix(name, "templates/"))
+}
+
+// GetAssetNames only for chi
+func GetAssetNames() []string {
+	realFS := Assets.(vfsgen۰FS)
+	var tmpls = make([]string, 0, len(realFS))
+	for k := range realFS {
+		tmpls = append(tmpls, "templates/"+k[1:])
+	}
+
+	customDir := path.Join(setting.CustomPath, "templates")
+	customTmpls := getDirAssetNames(customDir)
+	return append(tmpls, customTmpls...)
+}
+
 func NewTemplateFileSystem() templateFileSystem {
 	fs := templateFileSystem{}
 	fs.files = make([]macaron.TemplateFile, 0, 10)
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
new file mode 100644
index 0000000000..e39bf8b213
--- /dev/null
+++ b/modules/translation/translation.go
@@ -0,0 +1,92 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package translation
+
+import (
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/options"
+	"code.gitea.io/gitea/modules/setting"
+
+	macaron_i18n "gitea.com/macaron/i18n"
+	"github.com/unknwon/i18n"
+	"golang.org/x/text/language"
+)
+
+// Locale represents an interface to translation
+type Locale interface {
+	Language() string
+	Tr(string, ...interface{}) string
+}
+
+var (
+	matcher language.Matcher
+)
+
+// InitLocales loads the locales
+func InitLocales() {
+	localeNames, err := options.Dir("locale")
+
+	if err != nil {
+		log.Fatal("Failed to list locale files: %v", err)
+	}
+	localFiles := make(map[string][]byte)
+
+	for _, name := range localeNames {
+		localFiles[name], err = options.Locale(name)
+
+		if err != nil {
+			log.Fatal("Failed to load %s locale file. %v", name, err)
+		}
+	}
+
+	// These codes will be used once macaron removed
+	/*tags := make([]language.Tag, len(setting.Langs))
+	for i, lang := range setting.Langs {
+		tags[i] = language.Raw.Make(lang)
+	}
+	matcher = language.NewMatcher(tags)
+	for i, name := range setting.Names {
+		i18n.SetMessage(setting.Langs[i], localFiles[name])
+	}
+	i18n.SetDefaultLang("en-US")*/
+
+	// To be compatible with macaron, we now have to use macaron i18n, once macaron
+	// removed, we can use i18n directly
+	macaron_i18n.I18n(macaron_i18n.Options{
+		SubURL:       setting.AppSubURL,
+		Files:        localFiles,
+		Langs:        setting.Langs,
+		Names:        setting.Names,
+		DefaultLang:  "en-US",
+		Redirect:     false,
+		CookieDomain: setting.SessionConfig.Domain,
+	})
+}
+
+// Match matches accept languages
+func Match(tags ...language.Tag) (tag language.Tag, index int, c language.Confidence) {
+	return matcher.Match(tags...)
+}
+
+// locale represents the information of localization.
+type locale struct {
+	Lang string
+}
+
+// NewLocale return a locale
+func NewLocale(lang string) Locale {
+	return &locale{
+		Lang: lang,
+	}
+}
+
+func (l *locale) Language() string {
+	return l.Lang
+}
+
+// Tr translates content to target language.
+func (l *locale) Tr(format string, args ...interface{}) string {
+	return i18n.Tr(l.Lang, format, args...)
+}
diff --git a/routers/init.go b/routers/init.go
index ca8944bb2b..ff2ddcc0b7 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -26,19 +26,18 @@ import (
 	"code.gitea.io/gitea/modules/markup/external"
 	repo_migrations "code.gitea.io/gitea/modules/migrations"
 	"code.gitea.io/gitea/modules/notification"
-	"code.gitea.io/gitea/modules/options"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/ssh"
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/svg"
 	"code.gitea.io/gitea/modules/task"
+	"code.gitea.io/gitea/modules/translation"
 	"code.gitea.io/gitea/services/mailer"
 	mirror_service "code.gitea.io/gitea/services/mirror"
 	pull_service "code.gitea.io/gitea/services/pull"
 	"code.gitea.io/gitea/services/repository"
 	"code.gitea.io/gitea/services/webhook"
 
-	"gitea.com/macaron/i18n"
 	"gitea.com/macaron/macaron"
 )
 
@@ -93,33 +92,6 @@ func initDBEngine(ctx context.Context) (err error) {
 	return nil
 }
 
-// InitLocales loads the locales
-func InitLocales() {
-	localeNames, err := options.Dir("locale")
-
-	if err != nil {
-		log.Fatal("Failed to list locale files: %v", err)
-	}
-	localFiles := make(map[string][]byte)
-
-	for _, name := range localeNames {
-		localFiles[name], err = options.Locale(name)
-
-		if err != nil {
-			log.Fatal("Failed to load %s locale file. %v", name, err)
-		}
-	}
-	i18n.I18n(i18n.Options{
-		SubURL:       setting.AppSubURL,
-		Files:        localFiles,
-		Langs:        setting.Langs,
-		Names:        setting.Names,
-		DefaultLang:  "en-US",
-		Redirect:     false,
-		CookieDomain: setting.SessionConfig.Domain,
-	})
-}
-
 // PreInstallInit preloads the configuration to check if we need to run install
 func PreInstallInit(ctx context.Context) bool {
 	setting.NewContext()
@@ -129,7 +101,7 @@ func PreInstallInit(ctx context.Context) bool {
 		log.Trace("Custom path: %s", setting.CustomPath)
 		log.Trace("Log path: %s", setting.LogRootPath)
 		log.Trace("Preparing to run install page")
-		InitLocales()
+		translation.InitLocales()
 		if setting.EnableSQLite3 {
 			log.Info("SQLite3 Supported")
 		}
@@ -170,7 +142,7 @@ func GlobalInit(ctx context.Context) {
 	log.Trace("Log path: %s", setting.LogRootPath)
 
 	// Setup i18n
-	InitLocales()
+	translation.InitLocales()
 
 	NewServices()
 
diff --git a/routers/routes/chi.go b/routers/routes/chi.go
index 00689441b7..c0ac88957e 100644
--- a/routers/routes/chi.go
+++ b/routers/routes/chi.go
@@ -24,6 +24,7 @@ import (
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/routers"
 
+	"gitea.com/go-chi/session"
 	"github.com/go-chi/chi"
 	"github.com/go-chi/chi/middleware"
 	"github.com/prometheus/client_golang/prometheus"
@@ -37,7 +38,7 @@ type routerLoggerOptions struct {
 }
 
 // SignedUserName returns signed user's name via context
-// FIXME currently no any data stored on gin.Context but macaron.Context, so this will
+// FIXME currently no any data stored on chi.Context but macaron.Context, so this will
 // return "" before we remove macaron totally
 func SignedUserName(req *http.Request) string {
 	if v, ok := req.Context().Value("SignedUserName").(string); ok {
@@ -97,24 +98,6 @@ func LoggerHandler(level log.Level) func(next http.Handler) http.Handler {
 	}
 }
 
-// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
-// Although similar to macaron.Recovery() the main difference is that this error will be created
-// with the gitea 500 page.
-func Recovery() func(next http.Handler) http.Handler {
-	return func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-			defer func() {
-				if err := recover(); err != nil {
-					combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
-					http.Error(w, combinedErr, 500)
-				}
-			}()
-
-			next.ServeHTTP(w, req)
-		})
-	}
-}
-
 func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 		if storageSetting.ServeDirect {
@@ -202,6 +185,17 @@ func NewChi() chi.Router {
 			c.Use(LoggerHandler(setting.RouterLogLevel))
 		}
 	}
+	c.Use(session.Sessioner(session.Options{
+		Provider:       setting.SessionConfig.Provider,
+		ProviderConfig: setting.SessionConfig.ProviderConfig,
+		CookieName:     setting.SessionConfig.CookieName,
+		CookiePath:     setting.SessionConfig.CookiePath,
+		Gclifetime:     setting.SessionConfig.Gclifetime,
+		Maxlifetime:    setting.SessionConfig.Maxlifetime,
+		Secure:         setting.SessionConfig.Secure,
+		Domain:         setting.SessionConfig.Domain,
+	}))
+
 	c.Use(Recovery())
 	if setting.EnableAccessLog {
 		setupAccessLogger(c)
diff --git a/routers/routes/recovery.go b/routers/routes/recovery.go
new file mode 100644
index 0000000000..870a219c41
--- /dev/null
+++ b/routers/routes/recovery.go
@@ -0,0 +1,105 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package routes
+
+import (
+	"fmt"
+	"net/http"
+
+	"code.gitea.io/gitea/modules/auth/sso"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/middlewares"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
+
+	"gitea.com/go-chi/session"
+	"github.com/unrolled/render"
+)
+
+type dataStore struct {
+	Data map[string]interface{}
+}
+
+func (d *dataStore) GetData() map[string]interface{} {
+	return d.Data
+}
+
+// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
+// Although similar to macaron.Recovery() the main difference is that this error will be created
+// with the gitea 500 page.
+func Recovery() func(next http.Handler) http.Handler {
+	var isDevelopment = setting.RunMode != "prod"
+	return func(next http.Handler) http.Handler {
+		rnd := render.New(render.Options{
+			Extensions:    []string{".tmpl"},
+			Directory:     "templates",
+			Funcs:         templates.NewFuncMap(),
+			Asset:         templates.GetAsset,
+			AssetNames:    templates.GetAssetNames,
+			IsDevelopment: isDevelopment,
+		})
+
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+			defer func() {
+				// Why we need this? The first recover will try to render a beautiful
+				// error page for user, but the process can still panic again, then
+				// we have to just recover twice and send a simple error page that
+				// should not panic any more.
+				defer func() {
+					if err := recover(); err != nil {
+						combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+						log.Error(combinedErr)
+						if isDevelopment {
+							http.Error(w, combinedErr, 500)
+						} else {
+							http.Error(w, http.StatusText(500), 500)
+						}
+					}
+				}()
+
+				if err := recover(); err != nil {
+					combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+					log.Error("%v", combinedErr)
+
+					lc := middlewares.Locale(w, req)
+					sess := session.GetSession(req)
+
+					var store = dataStore{
+						Data: templates.Vars{
+							"Language":   lc.Language(),
+							"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
+							"i18n":       lc,
+						},
+					}
+
+					// Get user from session if logged in.
+					user, _ := sso.SignedInUser(req, &store, sess)
+					if user != nil {
+						store.Data["IsSigned"] = true
+						store.Data["SignedUser"] = user
+						store.Data["SignedUserID"] = user.ID
+						store.Data["SignedUserName"] = user.Name
+						store.Data["IsAdmin"] = user.IsAdmin
+					} else {
+						store.Data["SignedUserID"] = int64(0)
+						store.Data["SignedUserName"] = ""
+					}
+
+					w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
+
+					if setting.RunMode != "prod" {
+						store.Data["ErrMsg"] = combinedErr
+					}
+					err := rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data))
+					if err != nil {
+						log.Error("%v", err)
+					}
+				}
+			}()
+
+			next.ServeHTTP(w, req)
+		})
+	}
+}
diff --git a/vendor/gitea.com/go-chi/session/.drone.yml b/vendor/gitea.com/go-chi/session/.drone.yml
new file mode 100644
index 0000000000..d941e57505
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/.drone.yml
@@ -0,0 +1,37 @@
+---
+kind: pipeline
+name: test
+
+platform:
+  os: linux
+  arch: amd64
+
+clone:
+  disable: true
+
+workspace:
+  base: /go
+  path: src/session113
+
+steps:
+- name: git
+  pull: default
+  image: plugins/git:next
+  settings:
+    depth: 50
+    tags: true
+
+- name: test
+  pull: default
+  image: golang:1.13
+  environment:
+    GO111MODULE: on
+    GOPROXY: https://goproxy.cn
+  commands:
+    - go build -v
+    - go vet ./...
+    - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+  when:
+    event:
+    - push
+    - pull_request
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/.gitignore b/vendor/gitea.com/go-chi/session/.gitignore
new file mode 100644
index 0000000000..834722925c
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/.gitignore
@@ -0,0 +1,4 @@
+ledis/tmp.db
+nodb/tmp.db
+/vendor
+/.idea
diff --git a/vendor/gitea.com/go-chi/session/LICENSE b/vendor/gitea.com/go-chi/session/LICENSE
new file mode 100644
index 0000000000..8405e89a0b
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/README.md b/vendor/gitea.com/go-chi/session/README.md
new file mode 100644
index 0000000000..ed3258fcef
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/README.md
@@ -0,0 +1,17 @@
+# Session
+
+Middleware session provides session management which copied from [Macaron Session](https://gitea.com/go-chi/session) for [go-chi](https://github.com/go-chi/chi). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb.
+
+## Installation
+
+```
+go get gitea.com/go-chi/session
+```
+
+## Credits
+
+This package is a modified version of [go-macaron/session](github.com/go-macaron/session).
+
+## License
+
+This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/couchbase/couchbase.go b/vendor/gitea.com/go-chi/session/couchbase/couchbase.go
new file mode 100644
index 0000000000..31899a8574
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/couchbase/couchbase.go
@@ -0,0 +1,227 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"strings"
+	"sync"
+
+	"gitea.com/go-chi/session"
+	"github.com/couchbase/go-couchbase"
+)
+
+// CouchbaseSessionStore represents a couchbase session store implementation.
+type CouchbaseSessionStore struct {
+	b           *couchbase.Bucket
+	sid         string
+	lock        sync.RWMutex
+	data        map[interface{}]interface{}
+	maxlifetime int64
+}
+
+// Set sets value to given key in session.
+func (s *CouchbaseSessionStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *CouchbaseSessionStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *CouchbaseSessionStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *CouchbaseSessionStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *CouchbaseSessionStore) Release() error {
+	defer s.b.Close()
+
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	return s.b.Set(s.sid, int(s.maxlifetime), data)
+}
+
+// Flush deletes all session data.
+func (s *CouchbaseSessionStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// CouchbaseProvider represents a couchbase session provider implementation.
+type CouchbaseProvider struct {
+	maxlifetime int64
+	connStr     string
+	pool        string
+	bucket      string
+	b           *couchbase.Bucket
+}
+
+func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
+	c, err := couchbase.Connect(cp.connStr)
+	if err != nil {
+		return nil
+	}
+
+	pool, err := c.GetPool(cp.pool)
+	if err != nil {
+		return nil
+	}
+
+	bucket, err := pool.GetBucket(cp.bucket)
+	if err != nil {
+		return nil
+	}
+
+	return bucket
+}
+
+// Init initializes memory session provider.
+// connStr is couchbase server REST/JSON URL
+// e.g. http://host:port/, Pool, Bucket
+func (p *CouchbaseProvider) Init(maxlifetime int64, connStr string) error {
+	p.maxlifetime = maxlifetime
+	configs := strings.Split(connStr, ",")
+	if len(configs) > 0 {
+		p.connStr = configs[0]
+	}
+	if len(configs) > 1 {
+		p.pool = configs[1]
+	}
+	if len(configs) > 2 {
+		p.bucket = configs[2]
+	}
+
+	return nil
+}
+
+// Read returns raw session store by session ID.
+func (p *CouchbaseProvider) Read(sid string) (session.RawStore, error) {
+	p.b = p.getBucket()
+
+	var doc []byte
+
+	err := p.b.Get(sid, &doc)
+	var kv map[interface{}]interface{}
+	if doc == nil {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(doc)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
+	return cs, nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *CouchbaseProvider) Exist(sid string) bool {
+	p.b = p.getBucket()
+	defer p.b.Close()
+
+	var doc []byte
+
+	if err := p.b.Get(sid, &doc); err != nil || doc == nil {
+		return false
+	} else {
+		return true
+	}
+}
+
+// Destroy deletes a session by session ID.
+func (p *CouchbaseProvider) Destroy(sid string) error {
+	p.b = p.getBucket()
+	defer p.b.Close()
+
+	p.b.Delete(sid)
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *CouchbaseProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
+	p.b = p.getBucket()
+
+	var doc []byte
+	if err := p.b.Get(oldsid, &doc); err != nil || doc == nil {
+		p.b.Set(sid, int(p.maxlifetime), "")
+	} else {
+		err := p.b.Delete(oldsid)
+		if err != nil {
+			return nil, err
+		}
+		_, _ = p.b.Add(sid, int(p.maxlifetime), doc)
+	}
+
+	err := p.b.Get(sid, &doc)
+	if err != nil {
+		return nil, err
+	}
+	var kv map[interface{}]interface{}
+	if doc == nil {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(doc)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
+	return cs, nil
+}
+
+// Count counts and returns number of sessions.
+func (p *CouchbaseProvider) Count() int {
+	// FIXME
+	return 0
+}
+
+// GC calls GC to clean expired sessions.
+func (p *CouchbaseProvider) GC() {}
+
+func init() {
+	session.Register("couchbase", &CouchbaseProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/file.go b/vendor/gitea.com/go-chi/session/file.go
new file mode 100644
index 0000000000..ce915344fb
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/file.go
@@ -0,0 +1,274 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"github.com/unknwon/com"
+)
+
+// FileStore represents a file session store implementation.
+type FileStore struct {
+	p    *FileProvider
+	sid  string
+	lock sync.RWMutex
+	data map[interface{}]interface{}
+}
+
+// NewFileStore creates and returns a file session store.
+func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore {
+	return &FileStore{
+		p:    p,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *FileStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *FileStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *FileStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *FileStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *FileStore) Release() error {
+	s.p.lock.Lock()
+	defer s.p.lock.Unlock()
+
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(s.p.filepath(s.sid), data, 0600)
+}
+
+// Flush deletes all session data.
+func (s *FileStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// FileProvider represents a file session provider implementation.
+type FileProvider struct {
+	lock        sync.RWMutex
+	maxlifetime int64
+	rootPath    string
+}
+
+// Init initializes file session provider with given root path.
+func (p *FileProvider) Init(maxlifetime int64, rootPath string) error {
+	p.lock.Lock()
+	p.maxlifetime = maxlifetime
+	p.rootPath = rootPath
+	p.lock.Unlock()
+	return nil
+}
+
+func (p *FileProvider) filepath(sid string) string {
+	return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid)
+}
+
+// Read returns raw session store by session ID.
+func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
+	filename := p.filepath(sid)
+	if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
+		return nil, err
+	}
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	var f *os.File
+	ok := false
+	if com.IsFile(filename) {
+		modTime, err := com.FileMTime(filename)
+		if err != nil {
+			return nil, err
+		}
+		ok = (modTime + p.maxlifetime) >= time.Now().Unix()
+	}
+	if ok {
+		f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
+	} else {
+		f, err = os.Create(filename)
+	}
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, err
+	}
+	if len(data) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = DecodeGob(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return NewFileStore(p, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *FileProvider) Exist(sid string) bool {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+	return com.IsFile(p.filepath(sid))
+}
+
+// Destroy deletes a session by session ID.
+func (p *FileProvider) Destroy(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	return os.Remove(p.filepath(sid))
+}
+
+func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	filename := p.filepath(sid)
+	if com.IsExist(filename) {
+		return fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	oldname := p.filepath(oldsid)
+	if !com.IsFile(oldname) {
+		data, err := EncodeGob(make(map[interface{}]interface{}))
+		if err != nil {
+			return err
+		}
+		if err = os.MkdirAll(path.Dir(oldname), 0700); err != nil {
+			return err
+		}
+		if err = ioutil.WriteFile(oldname, data, 0600); err != nil {
+			return err
+		}
+	}
+
+	if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
+		return err
+	}
+	if err = os.Rename(oldname, filename); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
+	if err := p.regenerate(oldsid, sid); err != nil {
+		return nil, err
+	}
+
+	return p.Read(sid)
+}
+
+// Count counts and returns number of sessions.
+func (p *FileProvider) Count() int {
+	count := 0
+	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !fi.IsDir() {
+			count++
+		}
+		return nil
+	}); err != nil {
+		log.Printf("error counting session files: %v", err)
+		return 0
+	}
+	return count
+}
+
+// GC calls GC to clean expired sessions.
+func (p *FileProvider) GC() {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	if !com.IsExist(p.rootPath) {
+		return
+	}
+
+	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !fi.IsDir() &&
+			(fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() {
+			return os.Remove(path)
+		}
+		return nil
+	}); err != nil {
+		log.Printf("error garbage collecting session files: %v", err)
+	}
+}
+
+func init() {
+	Register("file", &FileProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/go.mod b/vendor/gitea.com/go-chi/session/go.mod
new file mode 100644
index 0000000000..6612080047
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/go.mod
@@ -0,0 +1,26 @@
+module gitea.com/go-chi/session
+
+go 1.11
+
+require (
+	github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
+	github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89
+	github.com/couchbase/gomemcached v0.1.1 // indirect
+	github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 // indirect
+	github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
+	github.com/edsrzf/mmap-go v1.0.0 // indirect
+	github.com/go-chi/chi v1.5.1
+	github.com/go-redis/redis/v8 v8.4.0
+	github.com/go-sql-driver/mysql v1.4.1
+	github.com/lib/pq v1.2.0
+	github.com/pelletier/go-toml v1.8.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
+	github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92
+	github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
+	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
+	github.com/syndtr/goleveldb v1.0.0 // indirect
+	github.com/unknwon/com v1.0.1
+	google.golang.org/appengine v1.6.7 // indirect
+	gopkg.in/ini.v1 v1.62.0
+)
diff --git a/vendor/gitea.com/go-chi/session/go.sum b/vendor/gitea.com/go-chi/session/go.sum
new file mode 100644
index 0000000000..e2c174b0c7
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/go.sum
@@ -0,0 +1,151 @@
+github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
+github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs=
+github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
+github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc=
+github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
+github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4=
+github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
+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/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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+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=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
+github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
+github.com/go-redis/redis/v8 v8.4.0 h1:J5NCReIgh3QgUJu398hUncxDExN4gMOHI11NVbVicGQ=
+github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
+github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
+github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
+github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 h1:qvsJwGToa8rxb42cDRhkbKeX2H5N8BH+s2aUikGt8mI=
+github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
+github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ=
+go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
+golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/vendor/gitea.com/go-chi/session/memcache/memcache.go b/vendor/gitea.com/go-chi/session/memcache/memcache.go
new file mode 100644
index 0000000000..ed08b915df
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/memcache/memcache.go
@@ -0,0 +1,203 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+
+	"gitea.com/go-chi/session"
+	"github.com/bradfitz/gomemcache/memcache"
+)
+
+// MemcacheStore represents a memcache session store implementation.
+type MemcacheStore struct {
+	c      *memcache.Client
+	sid    string
+	expire int32
+	lock   sync.RWMutex
+	data   map[interface{}]interface{}
+}
+
+// NewMemcacheStore creates and returns a memcache session store.
+func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore {
+	return &MemcacheStore{
+		c:      c,
+		sid:    sid,
+		expire: expire,
+		data:   kv,
+	}
+}
+
+func NewItem(sid string, data []byte, expire int32) *memcache.Item {
+	return &memcache.Item{
+		Key:        sid,
+		Value:      data,
+		Expiration: expire,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *MemcacheStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *MemcacheStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *MemcacheStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *MemcacheStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *MemcacheStore) Release() error {
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	return s.c.Set(NewItem(s.sid, data, s.expire))
+}
+
+// Flush deletes all session data.
+func (s *MemcacheStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// MemcacheProvider represents a memcache session provider implementation.
+type MemcacheProvider struct {
+	c      *memcache.Client
+	expire int32
+}
+
+// Init initializes memcache session provider.
+// connStrs: 127.0.0.1:9090;127.0.0.1:9091
+func (p *MemcacheProvider) Init(expire int64, connStrs string) error {
+	p.expire = int32(expire)
+	p.c = memcache.New(strings.Split(connStrs, ";")...)
+	return nil
+}
+
+// Read returns raw session store by session ID.
+func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) {
+	if !p.Exist(sid) {
+		if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil {
+			return nil, err
+		}
+	}
+
+	var kv map[interface{}]interface{}
+	item, err := p.c.Get(sid)
+	if err != nil {
+		return nil, err
+	}
+	if len(item.Value) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(item.Value)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewMemcacheStore(p.c, sid, p.expire, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *MemcacheProvider) Exist(sid string) bool {
+	_, err := p.c.Get(sid)
+	return err == nil
+}
+
+// Destroy deletes a session by session ID.
+func (p *MemcacheProvider) Destroy(sid string) error {
+	return p.c.Delete(sid)
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	item := NewItem(sid, []byte(""), p.expire)
+	if p.Exist(oldsid) {
+		item, err = p.c.Get(oldsid)
+		if err != nil {
+			return nil, err
+		} else if err = p.c.Delete(oldsid); err != nil {
+			return nil, err
+		}
+		item.Key = sid
+	}
+	if err = p.c.Set(item); err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	if len(item.Value) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(item.Value)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewMemcacheStore(p.c, sid, p.expire, kv), nil
+}
+
+// Count counts and returns number of sessions.
+func (p *MemcacheProvider) Count() int {
+	// FIXME: how come this library does not have Stats method?
+	return -1
+}
+
+// GC calls GC to clean expired sessions.
+func (p *MemcacheProvider) GC() {}
+
+func init() {
+	session.Register("memcache", &MemcacheProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/memcache/memcache.goconvey b/vendor/gitea.com/go-chi/session/memcache/memcache.goconvey
new file mode 100644
index 0000000000..8485e986e4
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/memcache/memcache.goconvey
@@ -0,0 +1 @@
+ignore
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/memory.go b/vendor/gitea.com/go-chi/session/memory.go
new file mode 100644
index 0000000000..d3a623c672
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/memory.go
@@ -0,0 +1,223 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"container/list"
+	"fmt"
+	"sync"
+	"time"
+)
+
+// MemStore represents a in-memory session store implementation.
+type MemStore struct {
+	sid        string
+	lock       sync.RWMutex
+	data       map[interface{}]interface{}
+	lastAccess time.Time
+}
+
+// NewMemStore creates and returns a memory session store.
+func NewMemStore(sid string) *MemStore {
+	return &MemStore{
+		sid:        sid,
+		data:       make(map[interface{}]interface{}),
+		lastAccess: time.Now(),
+	}
+}
+
+// Set sets value to given key in session.
+func (s *MemStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *MemStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete deletes a key from session.
+func (s *MemStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *MemStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (*MemStore) Release() error {
+	return nil
+}
+
+// Flush deletes all session data.
+func (s *MemStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// MemProvider represents a in-memory session provider implementation.
+type MemProvider struct {
+	lock        sync.RWMutex
+	maxLifetime int64
+	data        map[string]*list.Element
+	// A priority list whose lastAccess newer gets higer priority.
+	list *list.List
+}
+
+// Init initializes memory session provider.
+func (p *MemProvider) Init(maxLifetime int64, _ string) error {
+	p.lock.Lock()
+	p.list = list.New()
+	p.data = make(map[string]*list.Element)
+	p.maxLifetime = maxLifetime
+	p.lock.Unlock()
+	return nil
+}
+
+// update expands time of session store by given ID.
+func (p *MemProvider) update(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if e, ok := p.data[sid]; ok {
+		e.Value.(*MemStore).lastAccess = time.Now()
+		p.list.MoveToFront(e)
+		return nil
+	}
+	return nil
+}
+
+// Read returns raw session store by session ID.
+func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
+	p.lock.RLock()
+	e, ok := p.data[sid]
+	p.lock.RUnlock()
+
+	// Only restore if the session is still alive.
+	if ok && (e.Value.(*MemStore).lastAccess.Unix()+p.maxLifetime) >= time.Now().Unix() {
+		if err = p.update(sid); err != nil {
+			return nil, err
+		}
+		return e.Value.(*MemStore), nil
+	}
+
+	// Create a new session.
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	if ok {
+		p.list.Remove(e)
+		delete(p.data, sid)
+	}
+	s := NewMemStore(sid)
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *MemProvider) Exist(sid string) bool {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	_, ok := p.data[sid]
+	return ok
+}
+
+// Destroy deletes a session by session ID.
+func (p *MemProvider) Destroy(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	e, ok := p.data[sid]
+	if !ok {
+		return nil
+	}
+
+	p.list.Remove(e)
+	delete(p.data, sid)
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	s, err := p.Read(oldsid)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = p.Destroy(oldsid); err != nil {
+		return nil, err
+	}
+
+	s.(*MemStore).sid = sid
+
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Count counts and returns number of sessions.
+func (p *MemProvider) Count() int {
+	return p.list.Len()
+}
+
+// GC calls GC to clean expired sessions.
+func (p *MemProvider) GC() {
+	p.lock.RLock()
+	for {
+		// No session in the list.
+		e := p.list.Back()
+		if e == nil {
+			break
+		}
+
+		if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
+			p.lock.RUnlock()
+			p.lock.Lock()
+			p.list.Remove(e)
+			delete(p.data, e.Value.(*MemStore).sid)
+			p.lock.Unlock()
+			p.lock.RLock()
+		} else {
+			break
+		}
+	}
+	p.lock.RUnlock()
+}
+
+func init() {
+	Register("memory", &MemProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/mysql/mysql.go b/vendor/gitea.com/go-chi/session/mysql/mysql.go
new file mode 100644
index 0000000000..7ec647bf3a
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/mysql/mysql.go
@@ -0,0 +1,201 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"sync"
+	"time"
+
+	"gitea.com/go-chi/session"
+	_ "github.com/go-sql-driver/mysql"
+)
+
+// MysqlStore represents a mysql session store implementation.
+type MysqlStore struct {
+	c    *sql.DB
+	sid  string
+	lock sync.RWMutex
+	data map[interface{}]interface{}
+}
+
+// NewMysqlStore creates and returns a mysql session store.
+func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
+	return &MysqlStore{
+		c:    c,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *MysqlStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *MysqlStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *MysqlStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *MysqlStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *MysqlStore) Release() error {
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	_, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
+		data, time.Now().Unix(), s.sid)
+	return err
+}
+
+// Flush deletes all session data.
+func (s *MysqlStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// MysqlProvider represents a mysql session provider implementation.
+type MysqlProvider struct {
+	c      *sql.DB
+	expire int64
+}
+
+// Init initializes mysql session provider.
+// connStr: username:password@protocol(address)/dbname?param=value
+func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
+	p.expire = expire
+
+	p.c, err = sql.Open("mysql", connStr)
+	if err != nil {
+		return err
+	}
+	return p.c.Ping()
+}
+
+// Read returns raw session store by session ID.
+func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
+	now := time.Now().Unix()
+	var data []byte
+	expiry := now
+	err := p.c.QueryRow("SELECT data, expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry)
+	if err == sql.ErrNoRows {
+		_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
+			sid, "", now)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	if len(data) == 0 || expiry+p.expire <= now {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewMysqlStore(p.c, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *MysqlProvider) Exist(sid string) bool {
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
+	if err != nil && err != sql.ErrNoRows {
+		panic("session/mysql: error checking existence: " + err.Error())
+	}
+	return err != sql.ErrNoRows
+}
+
+// Destroy deletes a session by session ID.
+func (p *MysqlProvider) Destroy(sid string) error {
+	_, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid)
+	return err
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	if !p.Exist(oldsid) {
+		if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
+			oldsid, "", time.Now().Unix()); err != nil {
+			return nil, err
+		}
+	}
+
+	if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil {
+		return nil, err
+	}
+
+	return p.Read(sid)
+}
+
+// Count counts and returns number of sessions.
+func (p *MysqlProvider) Count() (total int) {
+	if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
+		panic("session/mysql: error counting records: " + err.Error())
+	}
+	return total
+}
+
+// GC calls GC to clean expired sessions.
+func (p *MysqlProvider) GC() {
+	if _, err := p.c.Exec("DELETE FROM session WHERE  expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
+		log.Printf("session/mysql: error garbage collecting: %v", err)
+	}
+}
+
+func init() {
+	session.Register("mysql", &MysqlProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/mysql/mysql.goconvey b/vendor/gitea.com/go-chi/session/mysql/mysql.goconvey
new file mode 100644
index 0000000000..8485e986e4
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/mysql/mysql.goconvey
@@ -0,0 +1 @@
+ignore
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/postgres/postgres.go b/vendor/gitea.com/go-chi/session/postgres/postgres.go
new file mode 100644
index 0000000000..383906d8d7
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/postgres/postgres.go
@@ -0,0 +1,202 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"sync"
+	"time"
+
+	"gitea.com/go-chi/session"
+	_ "github.com/lib/pq"
+)
+
+// PostgresStore represents a postgres session store implementation.
+type PostgresStore struct {
+	c    *sql.DB
+	sid  string
+	lock sync.RWMutex
+	data map[interface{}]interface{}
+}
+
+// NewPostgresStore creates and returns a postgres session store.
+func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore {
+	return &PostgresStore{
+		c:    c,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *PostgresStore) Set(key, value interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = value
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *PostgresStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *PostgresStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *PostgresStore) ID() string {
+	return s.sid
+}
+
+// save postgres session values to database.
+// must call this method to save values to database.
+func (s *PostgresStore) Release() error {
+	// Skip encoding if the data is empty
+	if len(s.data) == 0 {
+		return nil
+	}
+
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	_, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3",
+		data, time.Now().Unix(), s.sid)
+	return err
+}
+
+// Flush deletes all session data.
+func (s *PostgresStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// PostgresProvider represents a postgres session provider implementation.
+type PostgresProvider struct {
+	c           *sql.DB
+	maxlifetime int64
+}
+
+// Init initializes postgres session provider.
+// connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
+func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
+	p.maxlifetime = maxlifetime
+
+	p.c, err = sql.Open("postgres", connStr)
+	if err != nil {
+		return err
+	}
+	return p.c.Ping()
+}
+
+// Read returns raw session store by session ID.
+func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
+	now := time.Now().Unix()
+	var data []byte
+	expiry := now
+	err := p.c.QueryRow("SELECT data, expiry FROM session WHERE key=$1", sid).Scan(&data, &expiry)
+	if err == sql.ErrNoRows {
+		_, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
+			sid, "", now)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	if len(data) == 0 || expiry+p.maxlifetime <= now {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewPostgresStore(p.c, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *PostgresProvider) Exist(sid string) bool {
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
+	if err != nil && err != sql.ErrNoRows {
+		panic("session/postgres: error checking existence: " + err.Error())
+	}
+	return err != sql.ErrNoRows
+}
+
+// Destroy deletes a session by session ID.
+func (p *PostgresProvider) Destroy(sid string) error {
+	_, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid)
+	return err
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	if !p.Exist(oldsid) {
+		if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
+			oldsid, "", time.Now().Unix()); err != nil {
+			return nil, err
+		}
+	}
+
+	if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil {
+		return nil, err
+	}
+
+	return p.Read(sid)
+}
+
+// Count counts and returns number of sessions.
+func (p *PostgresProvider) Count() (total int) {
+	if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
+		panic("session/postgres: error counting records: " + err.Error())
+	}
+	return total
+}
+
+// GC calls GC to clean expired sessions.
+func (p *PostgresProvider) GC() {
+	if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil {
+		log.Printf("session/postgres: error garbage collecting: %v", err)
+	}
+}
+
+func init() {
+	session.Register("postgres", &PostgresProvider{})
+}
diff --git a/vendor/gitea.com/go-chi/session/postgres/postgres.goconvey b/vendor/gitea.com/go-chi/session/postgres/postgres.goconvey
new file mode 100644
index 0000000000..8485e986e4
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/postgres/postgres.goconvey
@@ -0,0 +1 @@
+ignore
\ No newline at end of file
diff --git a/vendor/gitea.com/go-chi/session/secret.go b/vendor/gitea.com/go-chi/session/secret.go
new file mode 100644
index 0000000000..e56a50615b
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/secret.go
@@ -0,0 +1,100 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package session
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/base64"
+	"errors"
+	"io"
+)
+
+// NewSecret creates a new secret
+func NewSecret() (string, error) {
+	return NewSecretWithLength(32)
+}
+
+// NewSecretWithLength creates a new secret for a given length
+func NewSecretWithLength(length int64) (string, error) {
+	return randomString(length)
+}
+
+func randomBytes(len int64) ([]byte, error) {
+	b := make([]byte, len)
+	if _, err := rand.Read(b); err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func randomString(len int64) (string, error) {
+	b, err := randomBytes(len)
+	return base64.URLEncoding.EncodeToString(b), err
+}
+
+// AesEncrypt encrypts text and given key with AES.
+func AesEncrypt(key, text []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	b := base64.StdEncoding.EncodeToString(text)
+	ciphertext := make([]byte, aes.BlockSize+len(b))
+	iv := ciphertext[:aes.BlockSize]
+	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+		return nil, err
+	}
+	cfb := cipher.NewCFBEncrypter(block, iv)
+	cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
+	return ciphertext, nil
+}
+
+// AesDecrypt decrypts text and given key with AES.
+func AesDecrypt(key, text []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	if len(text) < aes.BlockSize {
+		return nil, errors.New("ciphertext too short")
+	}
+	iv := text[:aes.BlockSize]
+	text = text[aes.BlockSize:]
+	cfb := cipher.NewCFBDecrypter(block, iv)
+	cfb.XORKeyStream(text, text)
+	data, err := base64.StdEncoding.DecodeString(string(text))
+	if err != nil {
+		return nil, err
+	}
+	return data, nil
+}
+
+// EncryptSecret encrypts a string with given key into a hex string
+func EncryptSecret(key string, str string) (string, error) {
+	keyHash := sha256.Sum256([]byte(key))
+	plaintext := []byte(str)
+	ciphertext, err := AesEncrypt(keyHash[:], plaintext)
+	if err != nil {
+		return "", err
+	}
+	return base64.StdEncoding.EncodeToString(ciphertext), nil
+}
+
+// DecryptSecret decrypts a previously encrypted hex string
+func DecryptSecret(key string, cipherhex string) (string, error) {
+	keyHash := sha256.Sum256([]byte(key))
+	ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
+	if err != nil {
+		return "", err
+	}
+	plaintext, err := AesDecrypt(keyHash[:], ciphertext)
+	if err != nil {
+		return "", err
+	}
+	return string(plaintext), nil
+}
diff --git a/vendor/gitea.com/go-chi/session/session.go b/vendor/gitea.com/go-chi/session/session.go
new file mode 100644
index 0000000000..97eb5ad365
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/session.go
@@ -0,0 +1,466 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package session a middleware that provides the session management of Macaron.
+package session
+
+import (
+	"context"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"time"
+)
+
+const version = "0.7.0"
+
+// Version returns the version
+func Version() string {
+	return version
+}
+
+// RawStore is the interface that operates the session data.
+type RawStore interface {
+	// Set sets value to given key in session.
+	Set(interface{}, interface{}) error
+	// Get gets value by given key in session.
+	Get(interface{}) interface{}
+	// Delete deletes a key from session.
+	Delete(interface{}) error
+	// ID returns current session ID.
+	ID() string
+	// Release releases session resource and save data to provider.
+	Release() error
+	// Flush deletes all session data.
+	Flush() error
+}
+
+// Store is the interface that contains all data for one session process with specific ID.
+type Store interface {
+	RawStore
+	// Read returns raw session store by session ID.
+	Read(string) (RawStore, error)
+	// Destroy deletes a session.
+	Destroy(http.ResponseWriter, *http.Request) error
+	// RegenerateID regenerates a session store from old session ID to new one.
+	RegenerateID(http.ResponseWriter, *http.Request) (RawStore, error)
+	// Count counts and returns number of sessions.
+	Count() int
+	// GC calls GC to clean expired sessions.
+	GC()
+}
+
+type store struct {
+	RawStore
+	*Manager
+}
+
+var _ Store = &store{}
+
+// Options represents a struct for specifying configuration options for the session middleware.
+type Options struct {
+	// Name of provider. Default is "memory".
+	Provider string
+	// Provider configuration, it's corresponding to provider.
+	ProviderConfig string
+	// Cookie name to save session ID. Default is "MacaronSession".
+	CookieName string
+	// Cookie path to store. Default is "/".
+	CookiePath string
+	// GC interval time in seconds. Default is 3600.
+	Gclifetime int64
+	// Max life time in seconds. Default is whatever GC interval time is.
+	Maxlifetime int64
+	// Use HTTPS only. Default is false.
+	Secure bool
+	// Cookie life time. Default is 0.
+	CookieLifeTime int
+	// SameSite set the cookie SameSite
+	SameSite http.SameSite
+	// Cookie domain name. Default is empty.
+	Domain string
+	// Session ID length. Default is 16.
+	IDLength int
+	// Ignore release for websocket. Default is false.
+	IgnoreReleaseForWebSocket bool
+	// FlashEncryptionKey sets the encryption key for flash messages
+	FlashEncryptionKey string
+}
+
+func prepareOptions(options []Options) Options {
+	var opt Options
+	if len(options) > 0 {
+		opt = options[0]
+	}
+
+	if len(opt.Provider) == 0 {
+		opt.Provider = "memory"
+	}
+	if len(opt.ProviderConfig) == 0 {
+		opt.ProviderConfig = "data/sessions"
+	}
+	if len(opt.CookieName) == 0 {
+		opt.CookieName = "MacaronSession"
+	}
+	if len(opt.CookiePath) == 0 {
+		opt.CookiePath = "/"
+	}
+	if opt.Gclifetime == 0 {
+		opt.Gclifetime = 3600
+	}
+	if opt.Maxlifetime == 0 {
+		opt.Maxlifetime = opt.Gclifetime
+	}
+	if !opt.Secure {
+		opt.Secure = false
+	}
+	if opt.IDLength == 0 {
+		opt.IDLength = 16
+	}
+	if len(opt.FlashEncryptionKey) == 0 {
+		opt.FlashEncryptionKey = ""
+	}
+	if len(opt.FlashEncryptionKey) == 0 {
+		opt.FlashEncryptionKey, _ = NewSecret()
+	}
+
+	return opt
+}
+
+// GetCookie returns given cookie value from request header.
+func GetCookie(req *http.Request, name string) string {
+	cookie, err := req.Cookie(name)
+	if err != nil {
+		return ""
+	}
+	val, _ := url.QueryUnescape(cookie.Value)
+	return val
+}
+
+// NewCookie creates cookie via given params and value.
+// FIXME: IE support? http://golanghome.com/post/620#reply2
+func NewCookie(name string, value string, others ...interface{}) *http.Cookie {
+	cookie := http.Cookie{}
+	cookie.Name = name
+	cookie.Value = url.QueryEscape(value)
+
+	if len(others) > 0 {
+		switch v := others[0].(type) {
+		case int:
+			cookie.MaxAge = v
+		case int64:
+			cookie.MaxAge = int(v)
+		case int32:
+			cookie.MaxAge = int(v)
+		case func(*http.Cookie):
+			v(&cookie)
+		}
+	}
+
+	cookie.Path = "/"
+	if len(others) > 1 {
+		if v, ok := others[1].(string); ok && len(v) > 0 {
+			cookie.Path = v
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 2 {
+		if v, ok := others[2].(string); ok && len(v) > 0 {
+			cookie.Domain = v
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 3 {
+		switch v := others[3].(type) {
+		case bool:
+			cookie.Secure = v
+		case func(*http.Cookie):
+			v(&cookie)
+		default:
+			if others[3] != nil {
+				cookie.Secure = true
+			}
+		}
+	}
+
+	if len(others) > 4 {
+		if v, ok := others[4].(bool); ok && v {
+			cookie.HttpOnly = true
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 5 {
+		if v, ok := others[5].(time.Time); ok {
+			cookie.Expires = v
+			cookie.RawExpires = v.Format(time.UnixDate)
+		} else if v, ok := others[1].(func(*http.Cookie)); ok {
+			v(&cookie)
+		}
+	}
+
+	if len(others) > 6 {
+		for _, other := range others[6:] {
+			if v, ok := other.(func(*http.Cookie)); ok {
+				v(&cookie)
+			}
+		}
+	}
+	return &cookie
+}
+
+// Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
+// An single variadic session.Options struct can be optionally provided to configure.
+func Sessioner(options ...Options) func(next http.Handler) http.Handler {
+	opt := prepareOptions(options)
+	manager, err := NewManager(opt.Provider, opt)
+	if err != nil {
+		panic(err)
+	}
+	go manager.startGC()
+
+	return func(next http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+			sess, err := manager.Start(w, req)
+			if err != nil {
+				panic("session(start): " + err.Error())
+			}
+
+			var s = store{
+				RawStore: sess,
+				Manager:  manager,
+			}
+
+			req = req.WithContext(context.WithValue(req.Context(), interface{}("Session"), &s))
+
+			next.ServeHTTP(w, req)
+
+			if manager.opt.IgnoreReleaseForWebSocket && req.Header.Get("Upgrade") == "websocket" {
+				return
+			}
+
+			if err = sess.Release(); err != nil {
+				panic("session(release): " + err.Error())
+			}
+		})
+	}
+}
+
+// GetSession returns session store
+func GetSession(req *http.Request) Store {
+	sessCtx := req.Context().Value("Session")
+	sess, _ := sessCtx.(*store)
+	return sess
+}
+
+// Provider is the interface that provides session manipulations.
+type Provider interface {
+	// Init initializes session provider.
+	Init(gclifetime int64, config string) error
+	// Read returns raw session store by session ID.
+	Read(sid string) (RawStore, error)
+	// Exist returns true if session with given ID exists.
+	Exist(sid string) bool
+	// Destroy deletes a session by session ID.
+	Destroy(sid string) error
+	// Regenerate regenerates a session store from old session ID to new one.
+	Regenerate(oldsid, sid string) (RawStore, error)
+	// Count counts and returns number of sessions.
+	Count() int
+	// GC calls GC to clean expired sessions.
+	GC()
+}
+
+var providers = make(map[string]Provider)
+
+// Register registers a provider.
+func Register(name string, provider Provider) {
+	if provider == nil {
+		panic("session: cannot register provider with nil value")
+	}
+	if _, dup := providers[name]; dup {
+		panic(fmt.Errorf("session: cannot register provider '%s' twice", name))
+	}
+	providers[name] = provider
+}
+
+//    _____
+//   /     \ _____    ____ _____     ____   ___________
+//  /  \ /  \\__  \  /    \\__  \   / ___\_/ __ \_  __ \
+// /    Y    \/ __ \|   |  \/ __ \_/ /_/  >  ___/|  | \/
+// \____|__  (____  /___|  (____  /\___  / \___  >__|
+//         \/     \/     \/     \//_____/      \/
+
+// Manager represents a struct that contains session provider and its configuration.
+type Manager struct {
+	provider Provider
+	opt      Options
+}
+
+// NewManager creates and returns a new session manager by given provider name and configuration.
+// It panics when given provider isn't registered.
+func NewManager(name string, opt Options) (*Manager, error) {
+	p, ok := providers[name]
+	if !ok {
+		return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
+	}
+	return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
+}
+
+// sessionID generates a new session ID with rand string, unix nano time, remote addr by hash function.
+func (m *Manager) sessionID() string {
+	return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
+}
+
+// validSessionID tests whether a provided session ID is a valid session ID.
+func (m *Manager) validSessionID(sid string) (bool, error) {
+	if len(sid) != m.opt.IDLength {
+		return false, fmt.Errorf("invalid 'sid': %s %d != %d", sid, len(sid), m.opt.IDLength)
+	}
+
+	for i := range sid {
+		switch {
+		case '0' <= sid[i] && sid[i] <= '9':
+		case 'a' <= sid[i] && sid[i] <= 'f':
+		default:
+			return false, errors.New("invalid 'sid': " + sid)
+		}
+	}
+	return true, nil
+}
+
+// Start starts a session by generating new one
+// or retrieve existence one by reading session ID from HTTP request if it's valid.
+func (m *Manager) Start(resp http.ResponseWriter, req *http.Request) (RawStore, error) {
+	sid := GetCookie(req, m.opt.CookieName)
+	valid, _ := m.validSessionID(sid)
+	if len(sid) > 0 && valid && m.provider.Exist(sid) {
+		return m.provider.Read(sid)
+	}
+
+	sid = m.sessionID()
+	sess, err := m.provider.Read(sid)
+	if err != nil {
+		return nil, err
+	}
+
+	cookie := &http.Cookie{
+		Name:     m.opt.CookieName,
+		Value:    sid,
+		Path:     m.opt.CookiePath,
+		HttpOnly: true,
+		Secure:   m.opt.Secure,
+		Domain:   m.opt.Domain,
+		SameSite: m.opt.SameSite,
+	}
+	if m.opt.CookieLifeTime >= 0 {
+		cookie.MaxAge = m.opt.CookieLifeTime
+	}
+	http.SetCookie(resp, cookie)
+	req.AddCookie(cookie)
+	return sess, nil
+}
+
+// Read returns raw session store by session ID.
+func (m *Manager) Read(sid string) (RawStore, error) {
+	// Ensure we're trying to read a valid session ID
+	if _, err := m.validSessionID(sid); err != nil {
+		return nil, err
+	}
+
+	return m.provider.Read(sid)
+}
+
+// Destroy deletes a session by given ID.
+func (m *Manager) Destroy(resp http.ResponseWriter, req *http.Request) error {
+	sid := GetCookie(req, m.opt.CookieName)
+	if len(sid) == 0 {
+		return nil
+	}
+
+	if _, err := m.validSessionID(sid); err != nil {
+		return err
+	}
+
+	if err := m.provider.Destroy(sid); err != nil {
+		return err
+	}
+	cookie := &http.Cookie{
+		Name:     m.opt.CookieName,
+		Path:     m.opt.CookiePath,
+		HttpOnly: true,
+		Expires:  time.Now(),
+		MaxAge:   -1,
+	}
+	http.SetCookie(resp, cookie)
+	return nil
+}
+
+// RegenerateID regenerates a session store from old session ID to new one.
+func (m *Manager) RegenerateID(resp http.ResponseWriter, req *http.Request) (sess RawStore, err error) {
+	sid := m.sessionID()
+	oldsid := GetCookie(req, m.opt.CookieName)
+	_, err = m.validSessionID(oldsid)
+	if err != nil {
+		return nil, err
+	}
+	sess, err = m.provider.Regenerate(oldsid, sid)
+	if err != nil {
+		return nil, err
+	}
+	cookie := &http.Cookie{
+		Name:     m.opt.CookieName,
+		Value:    sid,
+		Path:     m.opt.CookiePath,
+		HttpOnly: true,
+		Secure:   m.opt.Secure,
+		Domain:   m.opt.Domain,
+		SameSite: m.opt.SameSite,
+	}
+	if m.opt.CookieLifeTime >= 0 {
+		cookie.MaxAge = m.opt.CookieLifeTime
+	}
+	http.SetCookie(resp, cookie)
+	req.AddCookie(cookie)
+	return sess, nil
+}
+
+// Count counts and returns number of sessions.
+func (m *Manager) Count() int {
+	return m.provider.Count()
+}
+
+// GC starts GC job in a certain period.
+func (m *Manager) GC() {
+	m.provider.GC()
+}
+
+// startGC starts GC job in a certain period.
+func (m *Manager) startGC() {
+	m.GC()
+	time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() })
+}
+
+// SetSecure indicates whether to set cookie with HTTPS or not.
+func (m *Manager) SetSecure(secure bool) {
+	m.opt.Secure = secure
+}
diff --git a/vendor/gitea.com/go-chi/session/utils.go b/vendor/gitea.com/go-chi/session/utils.go
new file mode 100644
index 0000000000..001bae6d98
--- /dev/null
+++ b/vendor/gitea.com/go-chi/session/utils.go
@@ -0,0 +1,65 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/gob"
+	"io"
+
+	"github.com/unknwon/com"
+)
+
+func init() {
+	gob.Register([]interface{}{})
+	gob.Register(map[int]interface{}{})
+	gob.Register(map[string]interface{}{})
+	gob.Register(map[interface{}]interface{}{})
+	gob.Register(map[string]string{})
+	gob.Register(map[int]string{})
+	gob.Register(map[int]int{})
+	gob.Register(map[int]int64{})
+}
+
+// EncodeGob encodes obj with gob
+func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
+	for _, v := range obj {
+		gob.Register(v)
+	}
+	buf := bytes.NewBuffer(nil)
+	err := gob.NewEncoder(buf).Encode(obj)
+	return buf.Bytes(), err
+}
+
+// DecodeGob decodes bytes to obj
+func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
+	buf := bytes.NewBuffer(encoded)
+	err = gob.NewDecoder(buf).Decode(&out)
+	return out, err
+}
+
+// NOTE: A local copy in case of underlying package change
+var alphanum = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
+
+// generateRandomKey creates a random key with the given strength.
+func generateRandomKey(strength int) []byte {
+	k := make([]byte, strength)
+	if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
+		return com.RandomCreateBytes(strength, alphanum...)
+	}
+	return k
+}
diff --git a/vendor/github.com/couchbase/gomemcached/.gitignore b/vendor/github.com/couchbase/gomemcached/.gitignore
index f75d85a841..cd8acba17e 100644
--- a/vendor/github.com/couchbase/gomemcached/.gitignore
+++ b/vendor/github.com/couchbase/gomemcached/.gitignore
@@ -4,3 +4,4 @@
 *.swp
 /gocache/gocache
 c.out
+.idea
\ No newline at end of file
diff --git a/vendor/github.com/couchbase/gomemcached/client/collections_filter.go b/vendor/github.com/couchbase/gomemcached/client/collections_filter.go
index 4da8b8f426..a34d353fec 100644
--- a/vendor/github.com/couchbase/gomemcached/client/collections_filter.go
+++ b/vendor/github.com/couchbase/gomemcached/client/collections_filter.go
@@ -17,11 +17,7 @@ type CollectionsFilter struct {
 	ScopeId         uint32
 }
 
-type nonStreamIdNonResumeScopeMeta struct {
-	ScopeId string `json:"scope"`
-}
-
-type nonStreamIdResumeScopeMeta struct {
+type nonStreamIdNonCollectionsMeta struct {
 	ManifestId string `json:"uid"`
 }
 
@@ -29,7 +25,7 @@ type nonStreamIdNonResumeCollectionsMeta struct {
 	CollectionsList []string `json:"collections"`
 }
 
-type nonStreamIdResumeCollectionsMeta struct {
+type nonStreamIdCollectionsMeta struct {
 	ManifestId      string   `json:"uid"`
 	CollectionsList []string `json:"collections"`
 }
@@ -99,10 +95,19 @@ func (c *CollectionsFilter) ToStreamReqBody() ([]byte, error) {
 	case false:
 		switch c.UseManifestUid {
 		case true:
-			filter := &nonStreamIdResumeScopeMeta{
-				ManifestId: fmt.Sprintf("%x", c.ManifestUid),
+			switch len(c.CollectionsList) > 0 {
+			case true:
+				filter := &nonStreamIdCollectionsMeta{
+					ManifestId:      fmt.Sprintf("%x", c.ManifestUid),
+					CollectionsList: c.outputCollectionsFilterColList(),
+				}
+				output = *filter
+			case false:
+				filter := &nonStreamIdNonCollectionsMeta{
+					ManifestId: fmt.Sprintf("%x", c.ManifestUid),
+				}
+				output = *filter
 			}
-			output = *filter
 		case false:
 			switch len(c.CollectionsList) > 0 {
 			case true:
@@ -111,7 +116,7 @@ func (c *CollectionsFilter) ToStreamReqBody() ([]byte, error) {
 				}
 				output = *filter
 			case false:
-				output = nonStreamIdNonResumeScopeMeta{ScopeId: c.outputScopeId()}
+				return nil, fmt.Errorf("Specifying scopeID must require the use of streamId")
 			}
 		}
 	}
diff --git a/vendor/github.com/couchbase/gomemcached/client/mc.go b/vendor/github.com/couchbase/gomemcached/client/mc.go
index 16dd2f8f7c..208bacdd98 100644
--- a/vendor/github.com/couchbase/gomemcached/client/mc.go
+++ b/vendor/github.com/couchbase/gomemcached/client/mc.go
@@ -375,6 +375,25 @@ func (c *Client) setCollection(req *gomemcached.MCRequest, context ...*ClientCon
 	return nil
 }
 
+// Sets collection info in extras
+func (c *Client) setExtrasCollection(req *gomemcached.MCRequest, context ...*ClientContext) error {
+	collectionId := uint32(0)
+	if len(context) > 0 {
+		collectionId = context[0].CollId
+	}
+
+	// if the optional collection is specified, it must be default for clients that haven't turned on collections
+	if atomic.LoadUint32(&c.collectionsEnabled) == 0 {
+		if collectionId != 0 {
+			return fmt.Errorf("Client does not use collections but a collection was specified")
+		}
+	} else {
+		req.Extras = make([]byte, 4)
+		binary.BigEndian.PutUint32(req.Extras, collectionId)
+	}
+	return nil
+}
+
 func (c *Client) setVbSeqnoContext(req *gomemcached.MCRequest, context ...*ClientContext) error {
 	if len(context) == 0 || req == nil {
 		return nil
@@ -516,9 +535,14 @@ func (c *Client) Del(vb uint16, key string, context ...*ClientContext) (*gomemca
 
 // Get a random document
 func (c *Client) GetRandomDoc(context ...*ClientContext) (*gomemcached.MCResponse, error) {
-	return c.Send(&gomemcached.MCRequest{
+	req := &gomemcached.MCRequest{
 		Opcode: 0xB6,
-	})
+	}
+	err := c.setExtrasCollection(req, context...)
+	if err != nil {
+		return nil, err
+	}
+	return c.Send(req)
 }
 
 // AuthList lists SASL auth mechanisms.
diff --git a/vendor/github.com/couchbase/gomemcached/client/upr_event.go b/vendor/github.com/couchbase/gomemcached/client/upr_event.go
index 7ede0a128d..2d3454aecc 100644
--- a/vendor/github.com/couchbase/gomemcached/client/upr_event.go
+++ b/vendor/github.com/couchbase/gomemcached/client/upr_event.go
@@ -83,7 +83,8 @@ type UprEvent struct {
 	SystemEvent     SystemEventType         // Only valid if IsSystemEvent() is true
 	SysEventVersion uint8                   // Based on the version, the way Extra bytes is parsed is different
 	ValueLen        int                     // Cache it to avoid len() calls for performance
-	CollectionId    uint64                  // Valid if Collection is in use
+	CollectionId    uint32                  // Valid if Collection is in use
+	StreamId        *uint16                 // Nil if not in use
 }
 
 // FailoverLog containing vvuid and sequnce number
@@ -103,7 +104,7 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
 		DataType:     rq.DataType,
 		ValueLen:     len(rq.Body),
 		SystemEvent:  InvalidSysEvent,
-		CollectionId: math.MaxUint64,
+		CollectionId: math.MaxUint32,
 	}
 
 	event.PopulateFieldsBasedOnStreamType(rq, stream.StreamType)
@@ -153,6 +154,8 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
 		event.PopulateEvent(rq.Extras)
 	} else if event.IsSeqnoAdv() {
 		event.PopulateSeqnoAdv(rq.Extras)
+	} else if event.IsOsoSnapshot() {
+		event.PopulateOso(rq.Extras)
 	}
 
 	return event
@@ -160,6 +163,15 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
 
 func (event *UprEvent) PopulateFieldsBasedOnStreamType(rq gomemcached.MCRequest, streamType DcpStreamType) {
 	switch streamType {
+	case CollectionsStreamId:
+		for _, extra := range rq.FramingExtras {
+			streamId, streamIdErr := extra.GetStreamId()
+			if streamIdErr == nil {
+				event.StreamId = &streamId
+			}
+		}
+		// After parsing streamID, still need to populate regular collectionID
+		fallthrough
 	case CollectionsNonStreamId:
 		switch rq.Opcode {
 		// Only these will have CID encoded within the key
@@ -167,15 +179,12 @@ func (event *UprEvent) PopulateFieldsBasedOnStreamType(rq gomemcached.MCRequest,
 			gomemcached.UPR_DELETION,
 			gomemcached.UPR_EXPIRATION:
 			uleb128 := Uleb128(rq.Key)
-			result, bytesShifted := uleb128.ToUint64(rq.Keylen)
+			result, bytesShifted := uleb128.ToUint32(rq.Keylen)
 			event.CollectionId = result
 			event.Key = rq.Key[bytesShifted:]
 		default:
 			event.Key = rq.Key
 		}
-	case CollectionsStreamId:
-		// TODO - not implemented
-		fallthrough
 	case NonCollectionStream:
 		// Let default behavior be legacy stream type
 		fallthrough
@@ -208,6 +217,10 @@ func (event *UprEvent) IsSeqnoAdv() bool {
 	return event.Opcode == gomemcached.DCP_SEQNO_ADV
 }
 
+func (event *UprEvent) IsOsoSnapshot() bool {
+	return event.Opcode == gomemcached.DCP_OSO_SNAPSHOT
+}
+
 func (event *UprEvent) PopulateEvent(extras []byte) {
 	if len(extras) < dcpSystemEventExtraLen {
 		// Wrong length, don't parse
@@ -229,6 +242,14 @@ func (event *UprEvent) PopulateSeqnoAdv(extras []byte) {
 	event.Seqno = binary.BigEndian.Uint64(extras[:8])
 }
 
+func (event *UprEvent) PopulateOso(extras []byte) {
+	if len(extras) < dcpOsoExtraLen {
+		// Wrong length, don't parse
+		return
+	}
+	event.Flags = binary.BigEndian.Uint32(extras[:4])
+}
+
 func (event *UprEvent) GetSystemEventName() (string, error) {
 	switch event.SystemEvent {
 	case CollectionCreate:
@@ -345,15 +366,32 @@ func (event *UprEvent) GetMaxTTL() (uint32, error) {
 	}
 }
 
+// Only if error is nil:
+// Returns true if event states oso begins
+// Return false if event states oso ends
+func (event *UprEvent) GetOsoBegin() (bool, error) {
+	if !event.IsOsoSnapshot() {
+		return false, ErrorInvalidOp
+	}
+
+	if event.Flags == 1 {
+		return true, nil
+	} else if event.Flags == 2 {
+		return false, nil
+	} else {
+		return false, ErrorInvalidOp
+	}
+}
+
 type Uleb128 []byte
 
-func (u Uleb128) ToUint64(cachedLen int) (result uint64, bytesShifted int) {
+func (u Uleb128) ToUint32(cachedLen int) (result uint32, bytesShifted int) {
 	var shift uint = 0
 
 	for curByte := 0; curByte < cachedLen; curByte++ {
 		oneByte := u[curByte]
 		last7Bits := 0x7f & oneByte
-		result |= uint64(last7Bits) << shift
+		result |= uint32(last7Bits) << shift
 		bytesShifted++
 		if oneByte&0x80 == 0 {
 			break
diff --git a/vendor/github.com/couchbase/gomemcached/client/upr_feed.go b/vendor/github.com/couchbase/gomemcached/client/upr_feed.go
index be676aa71c..cdbed16bd3 100644
--- a/vendor/github.com/couchbase/gomemcached/client/upr_feed.go
+++ b/vendor/github.com/couchbase/gomemcached/client/upr_feed.go
@@ -26,6 +26,7 @@ const opaqueOpen = 0xBEAF0001
 const opaqueFailover = 0xDEADBEEF
 const opaqueGetSeqno = 0xDEADBEEF
 const uprDefaultNoopInterval = 120
+const dcpOsoExtraLen = 4
 
 // Counter on top of opaqueOpen that others can draw from for open and control msgs
 var opaqueOpenCtrlWell uint32 = opaqueOpen
@@ -117,6 +118,7 @@ type UprFeatures struct {
 	DcpPriority         PriorityType
 	EnableExpiry        bool
 	EnableStreamId      bool
+	EnableOso           bool
 }
 
 /**
@@ -601,6 +603,20 @@ func (feed *UprFeed) uprOpen(name string, sequence uint32, bufSize uint32, featu
 		activatedFeatures.EnableStreamId = true
 	}
 
+	if features.EnableOso {
+		rq := &gomemcached.MCRequest{
+			Opcode: gomemcached.UPR_CONTROL,
+			Key:    []byte("enable_out_of_order_snapshots"),
+			Body:   []byte("true"),
+			Opaque: getUprOpenCtrlOpaque(),
+		}
+		err = sendMcRequestSync(feed.conn, rq)
+		if err != nil {
+			return
+		}
+		activatedFeatures.EnableOso = true
+	}
+
 	// everything is ok so far, set upr feed to open state
 	feed.activatedFeatures = activatedFeatures
 	feed.setOpen()
@@ -976,6 +992,12 @@ loop:
 						break loop
 					}
 					event = makeUprEvent(pkt, stream, bytes)
+				case gomemcached.DCP_OSO_SNAPSHOT:
+					if stream == nil {
+						logging.Infof("Stream not found for vb %d: %#v", vb, pkt)
+						break loop
+					}
+					event = makeUprEvent(pkt, stream, bytes)
 				default:
 					logging.Infof("Recived an unknown response for vbucket %d", vb)
 				}
diff --git a/vendor/github.com/couchbase/gomemcached/go.mod b/vendor/github.com/couchbase/gomemcached/go.mod
new file mode 100644
index 0000000000..3355d4ea75
--- /dev/null
+++ b/vendor/github.com/couchbase/gomemcached/go.mod
@@ -0,0 +1,3 @@
+module github.com/couchbase/gomemcached
+
+go 1.13
diff --git a/vendor/github.com/couchbase/gomemcached/mc_constants.go b/vendor/github.com/couchbase/gomemcached/mc_constants.go
index 1dfe2febf2..19741f5a0d 100644
--- a/vendor/github.com/couchbase/gomemcached/mc_constants.go
+++ b/vendor/github.com/couchbase/gomemcached/mc_constants.go
@@ -104,6 +104,7 @@ const (
 
 	DCP_SYSTEM_EVENT = CommandCode(0x5f) // A system event has occurred
 	DCP_SEQNO_ADV    = CommandCode(0x64) // Sent when the vb seqno has advanced due to an unsubscribed event
+	DCP_OSO_SNAPSHOT = CommandCode(0x65) // Marks the begin and end of out-of-sequence-number stream
 )
 
 // command codes that are counted toward DCP control buffer
@@ -117,6 +118,7 @@ var BufferedCommandCodeMap = map[CommandCode]bool{
 	UPR_EXPIRATION:   true,
 	DCP_SYSTEM_EVENT: true,
 	DCP_SEQNO_ADV:    true,
+	DCP_OSO_SNAPSHOT: true,
 }
 
 // Status field for memcached response.
@@ -156,6 +158,9 @@ const (
 	SUBDOC_PATH_NOT_FOUND             = Status(0xc0)
 	SUBDOC_BAD_MULTI                  = Status(0xcc)
 	SUBDOC_MULTI_PATH_FAILURE_DELETED = Status(0xd3)
+
+	// Not a Memcached status
+	UNKNOWN_STATUS = Status(0xffff)
 )
 
 // for log redaction
@@ -174,6 +179,10 @@ var isFatal = map[Status]bool{
 	EACCESS:       true,
 	ENOMEM:        true,
 	NOT_SUPPORTED: true,
+
+	// consider statuses coming from outside couchbase (eg OS errors) as fatal for the connection
+	// as there might be unread data left over on the wire
+	UNKNOWN_STATUS: true,
 }
 
 // the producer/consumer bit in dcp flags
diff --git a/vendor/github.com/couchbase/gomemcached/mc_res.go b/vendor/github.com/couchbase/gomemcached/mc_res.go
index f6be989847..1e89020de2 100644
--- a/vendor/github.com/couchbase/gomemcached/mc_res.go
+++ b/vendor/github.com/couchbase/gomemcached/mc_res.go
@@ -38,7 +38,7 @@ func (res *MCResponse) Error() string {
 }
 
 func errStatus(e error) Status {
-	status := Status(0xffff)
+	status := UNKNOWN_STATUS
 	if res, ok := e.(*MCResponse); ok {
 		status = res.Status
 	}
diff --git a/vendor/github.com/go-chi/chi/.travis.yml b/vendor/github.com/go-chi/chi/.travis.yml
deleted file mode 100644
index 7b8e26bcee..0000000000
--- a/vendor/github.com/go-chi/chi/.travis.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-language: go
-
-go:
-  - 1.10.x
-  - 1.11.x
-  - 1.12.x
-  - 1.13.x
-  - 1.14.x
-
-script:
-  - go get -d -t ./...
-  - go vet ./...
-  - go test ./...
-  - >
-    go_version=$(go version);
-    if [ ${go_version:13:4} = "1.12" ]; then
-      go get -u golang.org/x/tools/cmd/goimports;
-      goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
-    fi
-
diff --git a/vendor/github.com/go-chi/chi/CHANGELOG.md b/vendor/github.com/go-chi/chi/CHANGELOG.md
index 9a64a72eec..2e337506a4 100644
--- a/vendor/github.com/go-chi/chi/CHANGELOG.md
+++ b/vendor/github.com/go-chi/chi/CHANGELOG.md
@@ -1,5 +1,66 @@
 # Changelog
 
+## v1.5.1 (2020-12-06)
+
+- Performance improvement: removing 1 allocation by foregoing context.WithValue, thank you @bouk for
+  your contribution (https://github.com/go-chi/chi/pull/555). Note: new benchmarks posted in README.
+- `middleware.CleanPath`: new middleware that clean's request path of double slashes
+- deprecate & remove `chi.ServerBaseContext` in favour of stdlib `http.Server#BaseContext`
+- plus other tiny improvements, see full commit history below
+- History of changes: see https://github.com/go-chi/chi/compare/v4.1.2...v1.5.1
+
+
+## v1.5.0 (2020-11-12) - now with go.mod support
+
+`chi` dates back to 2016 with it's original implementation as one of the first routers to adopt the newly introduced
+context.Context api to the stdlib -- set out to design a router that is faster, more modular and simpler than anything
+else out there -- while not introducing any custom handler types or dependencies. Today, `chi` still has zero dependencies,
+and in many ways is future proofed from changes, given it's minimal nature. Between versions, chi's iterations have been very
+incremental, with the architecture and api being the same today as it was originally designed in 2016. For this reason it 
+makes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years
+to who all help make chi better (total of 86 contributors to date -- thanks all!).
+
+Chi has been an labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance
+and simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size,
+and always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting
+middlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from
+companies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of
+joy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :)
+
+For me, the asthetics of chi's code and usage are very important. With the introduction of Go's module support 
+(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path
+of "github.com/go-chi/chi/v4", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462.
+Haha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt "/vXX" convention in the import
+path -- which isn't horrible in general -- but for chi, I'm unable to accept it as I strive for perfection in it's API design,
+aesthetics and simplicity. It just doesn't feel good to me given chi's simple nature -- I do not foresee a "v5" or "v6",
+and upgrading between versions in the future will also be just incremental.
+
+I do understand versioning is a part of the API design as well, which is why the solution for a while has been to "do nothing",
+as Go supports both old and new import paths with/out go.mod. However, now that Go module support has had time to iron out kinks and
+is adopted everywhere, it's time for chi to get with the times. Luckily, I've discovered a path forward that will make me happy,
+while also not breaking anyone's app who adopted a prior versioning from tags in v2/v3/v4. I've made an experimental release of
+v1.5.0 with go.mod silently, and tested it with new and old projects, to ensure the developer experience is preserved, and it's
+largely unnoticed. Fortunately, Go's toolchain will check the tags of a repo and consider the "latest" tag the one with go.mod.
+However, you can still request a specific older tag such as v4.1.2, and everything will "just work". But new users can just
+`go get github.com/go-chi/chi` or `go get github.com/go-chi/chi@latest` and they will get the latest version which contains
+go.mod support, which is v1.5.0+. `chi` will not change very much over the years, just like it hasn't changed much from 4 years ago.
+Therefore, we will stay on v1.x from here on, starting from v1.5.0. Any breaking changes will bump a "minor" release and
+backwards-compatible improvements/fixes will bump a "tiny" release.
+
+For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`,
+which will get you on the go.mod version line (as Go's mod cache may still remember v4.x). Brand new systems can run
+`go get -u github.com/go-chi/chi` or `go get -u github.com/go-chi/chi@latest` to install chi, which will install v1.5.0+
+built with go.mod support.
+
+My apologies to the developers who will disagree with the decisions above, but, hope you'll try it and see it's a very
+minor request which is backwards compatible and won't break your existing installations.
+
+Cheers all, happy coding!
+
+
+---
+
+
 ## v4.1.2 (2020-06-02)
 
 - fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution
@@ -23,7 +84,6 @@
 - middleware.Recoverer: a bit prettier
 - History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0
 
-
 ## v4.0.4 (2020-03-24)
 
 - middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496)
diff --git a/vendor/github.com/go-chi/chi/README.md b/vendor/github.com/go-chi/chi/README.md
index 3d53a61476..1b96d360d7 100644
--- a/vendor/github.com/go-chi/chi/README.md
+++ b/vendor/github.com/go-chi/chi/README.md
@@ -15,7 +15,8 @@ public API service, which in turn powers all of our client-side applications.
 The key considerations of chi's design are: project structure, maintainability, standard http
 handlers (stdlib-only), developer productivity, and deconstructing a large system into many small
 parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also
-included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
+included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render)
+and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
 
 ## Install
 
@@ -27,10 +28,11 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h
 * **Lightweight** - cloc'd in ~1000 LOC for the chi router
 * **Fast** - yes, see [benchmarks](#benchmarks)
 * **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
-* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
+* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and sub-router mounting
 * **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts
 * **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
 * **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
+* **Go.mod support** - v1.x of chi (starting from v1.5.0), now has go.mod support (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md#v150-2020-11-12---now-with-gomod-support))
 * **No external dependencies** - plain ol' Go stdlib + net/http
 
 
@@ -334,9 +336,12 @@ with `net/http` can be used with chi's mux.
 ----------------------------------------------------------------------------------------------------
 | chi/middleware Handler | description                                                             |
 | :--------------------- | :---------------------------------------------------------------------- |
+| [AllowContentEncoding] | Enforces a whitelist of request Content-Encoding headers                |
 | [AllowContentType]     | Explicit whitelist of accepted request Content-Types                    |
 | [BasicAuth]            | Basic HTTP authentication                                               |
 | [Compress]             | Gzip compression for clients that accept compressed responses           |
+| [ContentCharset]       | Ensure charset for Content-Type request headers                         |
+| [CleanPath]            | Clean double slashes from request path                                  |
 | [GetHead]              | Automatically route undefined HEAD requests to GET handlers             |
 | [Heartbeat]            | Monitoring endpoint to check the servers pulse                          |
 | [Logger]               | Logs the start and end of each request with the elapsed processing time |
@@ -346,6 +351,7 @@ with `net/http` can be used with chi's mux.
 | [Recoverer]            | Gracefully absorb panics and prints the stack trace                     |
 | [RequestID]            | Injects a request ID into the context of each request                   |
 | [RedirectSlashes]      | Redirect slashes on routing paths                                       |
+| [RouteHeaders]         | Route handling for request headers                                      |
 | [SetHeader]            | Short-hand middleware to set a response header key/value                |
 | [StripSlashes]         | Strip slashes on routing paths                                          |
 | [Throttle]             | Puts a ceiling on the number of concurrent requests                     |
@@ -359,20 +365,19 @@ with `net/http` can be used with chi's mux.
 [BasicAuth]: https://pkg.go.dev/github.com/go-chi/chi/middleware#BasicAuth
 [Compress]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compress
 [ContentCharset]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ContentCharset
+[CleanPath]: https://pkg.go.dev/github.com/go-chi/chi/middleware#CleanPath
 [GetHead]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetHead
 [GetReqID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetReqID
 [Heartbeat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Heartbeat
 [Logger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Logger
-[New]: https://pkg.go.dev/github.com/go-chi/chi/middleware#New
-[NextRequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NextRequestID
 [NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache
-[PrintPrettyStack]: https://pkg.go.dev/github.com/go-chi/chi/middleware#PrintPrettyStack
 [Profiler]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Profiler
 [RealIP]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RealIP
 [Recoverer]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Recoverer
 [RedirectSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RedirectSlashes
-[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID
 [RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger
+[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID
+[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders
 [SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
 [StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes
 [Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle
@@ -390,7 +395,6 @@ with `net/http` can be used with chi's mux.
 [LogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogEntry
 [LogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogFormatter
 [LoggerInterface]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LoggerInterface
-[Pattern]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Pattern
 [ThrottleOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleOpts
 [WrapResponseWriter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WrapResponseWriter
 
@@ -430,25 +434,25 @@ and..
 
 The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
 
-Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
+Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x
 
 ```shell
-BenchmarkChi_Param            3000000         475 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_Param5           2000000         696 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_Param20          1000000        1275 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_ParamWrite       3000000         505 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GithubStatic     3000000         508 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GithubParam      2000000         669 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GithubAll          10000      134627 ns/op     87699 B/op    609 allocs/op
-BenchmarkChi_GPlusStatic      3000000         402 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GPlusParam       3000000         500 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GPlus2Params     3000000         586 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_GPlusAll          200000        7237 ns/op      5616 B/op     39 allocs/op
-BenchmarkChi_ParseStatic      3000000         408 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_ParseParam       3000000         488 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_Parse2Params     3000000         551 ns/op       432 B/op      3 allocs/op
-BenchmarkChi_ParseAll          100000       13508 ns/op     11232 B/op     78 allocs/op
-BenchmarkChi_StaticAll          20000       81933 ns/op     67826 B/op    471 allocs/op
+BenchmarkChi_Param          	3075895	        384 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_Param5         	2116603	        566 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_Param20        	 964117	       1227 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_ParamWrite     	2863413	        420 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GithubStatic   	3045488	        395 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GithubParam    	2204115	        540 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GithubAll      	  10000	     113811 ns/op	    81203 B/op    406 allocs/op
+BenchmarkChi_GPlusStatic    	3337485	        359 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GPlusParam     	2825853	        423 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GPlus2Params   	2471697	        483 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_GPlusAll       	 194220	       5950 ns/op	     5200 B/op     26 allocs/op
+BenchmarkChi_ParseStatic    	3365324	        356 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_ParseParam     	2976614	        404 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_Parse2Params   	2638084	        439 ns/op	      400 B/op      2 allocs/op
+BenchmarkChi_ParseAll       	 109567	      11295 ns/op	    10400 B/op     52 allocs/op
+BenchmarkChi_StaticAll      	  16846	      71308 ns/op	    62802 B/op    314 allocs/op
 ```
 
 Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
@@ -459,6 +463,17 @@ on the duplicated (alloc'd) request and returns it the new request object. This
 how setting context on a request in Go works.
 
 
+## Go module support & note on chi's versioning
+
+* Go.mod support means we reset our versioning starting from v1.5 (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md#v150-2020-11-12---now-with-gomod-support))
+* All older tags are preserved, are backwards-compatible and will "just work" as they
+* Brand new systems can run `go get -u github.com/go-chi/chi` as normal, or `go get -u github.com/go-chi/chi@latest`
+to install chi, which will install v1.x+ built with go.mod support, starting from v1.5.0.
+* For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`,
+which will get you on the go.mod version line (as Go's mod cache may still remember v4.x).
+* Any breaking changes will bump a "minor" release and backwards-compatible improvements/fixes will bump a "tiny" release.
+
+
 ## Credits
 
 * Carl Jackson for https://github.com/zenazn/goji
diff --git a/vendor/github.com/go-chi/chi/context.go b/vendor/github.com/go-chi/chi/context.go
index 26c609ea2c..7dec3f0c01 100644
--- a/vendor/github.com/go-chi/chi/context.go
+++ b/vendor/github.com/go-chi/chi/context.go
@@ -2,9 +2,9 @@ package chi
 
 import (
 	"context"
-	"net"
 	"net/http"
 	"strings"
+	"time"
 )
 
 // URLParam returns the url parameter from a http.Request object.
@@ -30,26 +30,6 @@ func RouteContext(ctx context.Context) *Context {
 	return val
 }
 
-// ServerBaseContext wraps an http.Handler to set the request context to the
-// `baseCtx`.
-func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
-	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		ctx := r.Context()
-		baseCtx := baseCtx
-
-		// Copy over default net/http server context keys
-		if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
-			baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
-		}
-		if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
-			baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
-		}
-
-		h.ServeHTTP(w, r.WithContext(baseCtx))
-	})
-	return fn
-}
-
 // NewRouteContext returns a new routing Context object.
 func NewRouteContext() *Context {
 	return &Context{}
@@ -92,6 +72,11 @@ type Context struct {
 
 	// methodNotAllowed hint
 	methodNotAllowed bool
+
+	// parentCtx is the parent of this one, for using Context as a
+	// context.Context directly. This is an optimization that saves
+	// 1 allocation.
+	parentCtx context.Context
 }
 
 // Reset a routing context to its initial state.
@@ -107,6 +92,7 @@ func (x *Context) Reset() {
 	x.routeParams.Keys = x.routeParams.Keys[:0]
 	x.routeParams.Values = x.routeParams.Values[:0]
 	x.methodNotAllowed = false
+	x.parentCtx = nil
 }
 
 // URLParam returns the corresponding URL parameter value from the request
@@ -160,6 +146,32 @@ func (s *RouteParams) Add(key, value string) {
 	s.Values = append(s.Values, value)
 }
 
+// directContext provides direct access to the routing *Context object,
+// while implementing the context.Context interface, thereby allowing
+// us to saving 1 allocation during routing.
+type directContext Context
+
+var _ context.Context = (*directContext)(nil)
+
+func (d *directContext) Deadline() (deadline time.Time, ok bool) {
+	return d.parentCtx.Deadline()
+}
+
+func (d *directContext) Done() <-chan struct{} {
+	return d.parentCtx.Done()
+}
+
+func (d *directContext) Err() error {
+	return d.parentCtx.Err()
+}
+
+func (d *directContext) Value(key interface{}) interface{} {
+	if key == RouteCtxKey {
+		return (*Context)(d)
+	}
+	return d.parentCtx.Value(key)
+}
+
 // contextKey is a value for use with context.WithValue. It's used as
 // a pointer so it fits in an interface{} without allocation. This technique
 // for defining context keys was copied from Go 1.7's new use of context in net/http.
diff --git a/vendor/github.com/go-chi/chi/middleware/basic_auth.go b/vendor/github.com/go-chi/chi/middleware/basic_auth.go
index 87b2641a6a..a546c9e9e8 100644
--- a/vendor/github.com/go-chi/chi/middleware/basic_auth.go
+++ b/vendor/github.com/go-chi/chi/middleware/basic_auth.go
@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"crypto/subtle"
 	"fmt"
 	"net/http"
 )
@@ -16,7 +17,7 @@ func BasicAuth(realm string, creds map[string]string) func(next http.Handler) ht
 			}
 
 			credPass, credUserOk := creds[user]
-			if !credUserOk || pass != credPass {
+			if !credUserOk || subtle.ConstantTimeCompare([]byte(pass), []byte(credPass)) != 1 {
 				basicAuthFailed(w, realm)
 				return
 			}
diff --git a/vendor/github.com/go-chi/chi/middleware/clean_path.go b/vendor/github.com/go-chi/chi/middleware/clean_path.go
new file mode 100644
index 0000000000..d42bf28457
--- /dev/null
+++ b/vendor/github.com/go-chi/chi/middleware/clean_path.go
@@ -0,0 +1,28 @@
+package middleware
+
+import (
+	"net/http"
+	"path"
+
+	"github.com/go-chi/chi"
+)
+
+// CleanPath middleware will clean out double slash mistakes from a user's request path.
+// For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1
+func CleanPath(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		rctx := chi.RouteContext(r.Context())
+
+		routePath := rctx.RoutePath
+		if routePath == "" {
+			if r.URL.RawPath != "" {
+				routePath = r.URL.RawPath
+			} else {
+				routePath = r.URL.Path
+			}
+			rctx.RoutePath = path.Clean(routePath)
+		}
+
+		next.ServeHTTP(w, r)
+	})
+}
diff --git a/vendor/github.com/go-chi/chi/middleware/content_type.go b/vendor/github.com/go-chi/chi/middleware/content_type.go
index ee4957874f..023978fac0 100644
--- a/vendor/github.com/go-chi/chi/middleware/content_type.go
+++ b/vendor/github.com/go-chi/chi/middleware/content_type.go
@@ -19,9 +19,9 @@ func SetHeader(key, value string) func(next http.Handler) http.Handler {
 // AllowContentType enforces a whitelist of request Content-Types otherwise responds
 // with a 415 Unsupported Media Type status.
 func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler {
-	cT := []string{}
-	for _, t := range contentTypes {
-		cT = append(cT, strings.ToLower(t))
+	allowedContentTypes := make(map[string]struct{}, len(contentTypes))
+	for _, ctype := range contentTypes {
+		allowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{}
 	}
 
 	return func(next http.Handler) http.Handler {
@@ -37,11 +37,9 @@ func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handl
 				s = s[0:i]
 			}
 
-			for _, t := range cT {
-				if t == s {
-					next.ServeHTTP(w, r)
-					return
-				}
+			if _, ok := allowedContentTypes[s]; ok {
+				next.ServeHTTP(w, r)
+				return
 			}
 
 			w.WriteHeader(http.StatusUnsupportedMediaType)
diff --git a/vendor/github.com/go-chi/chi/middleware/logger.go b/vendor/github.com/go-chi/chi/middleware/logger.go
index 158a6a3905..66edc3dda8 100644
--- a/vendor/github.com/go-chi/chi/middleware/logger.go
+++ b/vendor/github.com/go-chi/chi/middleware/logger.go
@@ -6,6 +6,7 @@ import (
 	"log"
 	"net/http"
 	"os"
+	"runtime"
 	"time"
 )
 
@@ -16,7 +17,7 @@ var (
 	// DefaultLogger is called by the Logger middleware handler to log each request.
 	// Its made a package-level variable so that it can be reconfigured for custom
 	// logging configurations.
-	DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
+	DefaultLogger func(next http.Handler) http.Handler
 )
 
 // Logger is a middleware that logs the start and end of each request, along
@@ -27,6 +28,16 @@ var (
 //
 // Alternatively, look at https://github.com/goware/httplog for a more in-depth
 // http logger with structured logging support.
+//
+// IMPORTANT NOTE: Logger should go before any other middleware that may change
+// the response, such as `middleware.Recoverer`. Example:
+//
+// ```go
+// r := chi.NewRouter()
+// r.Use(middleware.Logger)        // <--<< Logger should come before Recoverer
+// r.Use(middleware.Recoverer)
+// r.Get("/", handler)
+// ```
 func Logger(next http.Handler) http.Handler {
 	return DefaultLogger(next)
 }
@@ -153,3 +164,11 @@ func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed t
 func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
 	PrintPrettyStack(v)
 }
+
+func init() {
+	color := true
+	if runtime.GOOS == "windows" {
+		color = false
+	}
+	DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: !color})
+}
diff --git a/vendor/github.com/go-chi/chi/middleware/strip.go b/vendor/github.com/go-chi/chi/middleware/strip.go
index 2b8b1842ab..1082d713ef 100644
--- a/vendor/github.com/go-chi/chi/middleware/strip.go
+++ b/vendor/github.com/go-chi/chi/middleware/strip.go
@@ -14,13 +14,18 @@ func StripSlashes(next http.Handler) http.Handler {
 	fn := func(w http.ResponseWriter, r *http.Request) {
 		var path string
 		rctx := chi.RouteContext(r.Context())
-		if rctx.RoutePath != "" {
+		if rctx != nil && rctx.RoutePath != "" {
 			path = rctx.RoutePath
 		} else {
 			path = r.URL.Path
 		}
 		if len(path) > 1 && path[len(path)-1] == '/' {
-			rctx.RoutePath = path[:len(path)-1]
+			newPath := path[:len(path)-1]
+			if rctx == nil {
+				r.URL.Path = newPath
+			} else {
+				rctx.RoutePath = newPath
+			}
 		}
 		next.ServeHTTP(w, r)
 	}
@@ -36,7 +41,7 @@ func RedirectSlashes(next http.Handler) http.Handler {
 	fn := func(w http.ResponseWriter, r *http.Request) {
 		var path string
 		rctx := chi.RouteContext(r.Context())
-		if rctx.RoutePath != "" {
+		if rctx != nil && rctx.RoutePath != "" {
 			path = rctx.RoutePath
 		} else {
 			path = r.URL.Path
@@ -47,7 +52,8 @@ func RedirectSlashes(next http.Handler) http.Handler {
 			} else {
 				path = path[:len(path)-1]
 			}
-			http.Redirect(w, r, path, 301)
+			redirectUrl := fmt.Sprintf("//%s%s", r.Host, path)
+			http.Redirect(w, r, redirectUrl, 301)
 			return
 		}
 		next.ServeHTTP(w, r)
diff --git a/vendor/github.com/go-chi/chi/middleware/url_format.go b/vendor/github.com/go-chi/chi/middleware/url_format.go
index 5749e4f32b..d8f04b7cb9 100644
--- a/vendor/github.com/go-chi/chi/middleware/url_format.go
+++ b/vendor/github.com/go-chi/chi/middleware/url_format.go
@@ -53,7 +53,7 @@ func URLFormat(next http.Handler) http.Handler {
 
 		if strings.Index(path, ".") > 0 {
 			base := strings.LastIndex(path, "/")
-			idx := strings.Index(path[base:], ".")
+			idx := strings.LastIndex(path[base:], ".")
 
 			if idx > 0 {
 				idx += base
diff --git a/vendor/github.com/go-chi/chi/mux.go b/vendor/github.com/go-chi/chi/mux.go
index 52950e97b5..c6fdb8a0f3 100644
--- a/vendor/github.com/go-chi/chi/mux.go
+++ b/vendor/github.com/go-chi/chi/mux.go
@@ -1,7 +1,6 @@
 package chi
 
 import (
-	"context"
 	"fmt"
 	"net/http"
 	"strings"
@@ -78,9 +77,10 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	rctx = mx.pool.Get().(*Context)
 	rctx.Reset()
 	rctx.Routes = mx
+	rctx.parentCtx = r.Context()
 
-	// NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation
-	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
+	// NOTE: r.WithContext() causes 2 allocations
+	r = r.WithContext((*directContext)(rctx))
 
 	// Serve the request and once its done, put the request context back in the sync pool
 	mx.handler.ServeHTTP(w, r)
@@ -227,7 +227,7 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
 	// Similarly as in handle(), we must build the mux handler once additional
 	// middleware registration isn't allowed for this stack, like now.
 	if !mx.inline && mx.handler == nil {
-		mx.buildRouteHandler()
+		mx.updateRouteHandler()
 	}
 
 	// Copy middlewares from parent inline muxs
@@ -261,10 +261,11 @@ func (mx *Mux) Group(fn func(r Router)) Router {
 // along the `pattern` as a subrouter. Effectively, this is a short-hand
 // call to Mount. See _examples/.
 func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
-	subRouter := NewRouter()
-	if fn != nil {
-		fn(subRouter)
+	if fn == nil {
+		panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern))
 	}
+	subRouter := NewRouter()
+	fn(subRouter)
 	mx.Mount(pattern, subRouter)
 	return subRouter
 }
@@ -277,6 +278,10 @@ func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
 // routing at the `handler`, which in most cases is another chi.Router. As a result,
 // if you define two Mount() routes on the exact same pattern the mount will panic.
 func (mx *Mux) Mount(pattern string, handler http.Handler) {
+	if handler == nil {
+		panic(fmt.Sprintf("chi: attempting to Mount() a nil handler on '%s'", pattern))
+	}
+
 	// Provide runtime safety for ensuring a pattern isn't mounted on an existing
 	// routing pattern.
 	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
@@ -294,7 +299,16 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
 
 	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		rctx := RouteContext(r.Context())
+
+		// shift the url path past the previous subrouter
 		rctx.RoutePath = mx.nextRoutePath(rctx)
+
+		// reset the wildcard URLParam which connects the subrouter
+		n := len(rctx.URLParams.Keys) - 1
+		if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n {
+			rctx.URLParams.Values[n] = ""
+		}
+
 		handler.ServeHTTP(w, r)
 	})
 
@@ -367,14 +381,6 @@ func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc {
 	return methodNotAllowedHandler
 }
 
-// buildRouteHandler builds the single mux handler that is a chain of the middleware
-// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
-// point, no other middlewares can be registered on this Mux's stack. But you can still
-// compose additional middlewares via Group()'s or using a chained middleware handler.
-func (mx *Mux) buildRouteHandler() {
-	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
-}
-
 // handle registers a http.Handler in the routing tree for a particular http method
 // and routing pattern.
 func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
@@ -384,7 +390,7 @@ func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *n
 
 	// Build the computed routing handler for this routing pattern.
 	if !mx.inline && mx.handler == nil {
-		mx.buildRouteHandler()
+		mx.updateRouteHandler()
 	}
 
 	// Build endpoint handler with inline middlewares for the route
@@ -458,6 +464,14 @@ func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
 	}
 }
 
+// updateRouteHandler builds the single mux handler that is a chain of the middleware
+// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
+// point, no other middlewares can be registered on this Mux's stack. But you can still
+// compose additional middlewares via Group()'s or using a chained middleware handler.
+func (mx *Mux) updateRouteHandler() {
+	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
+}
+
 // methodNotAllowedHandler is a helper function to respond with a 405,
 // method not allowed.
 func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/vendor/github.com/unrolled/render/.gitignore b/vendor/github.com/unrolled/render/.gitignore
new file mode 100644
index 0000000000..05f4eaf615
--- /dev/null
+++ b/vendor/github.com/unrolled/render/.gitignore
@@ -0,0 +1,27 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+
+
+*.pem
+.DS_Store
diff --git a/vendor/github.com/unrolled/render/.travis.yml b/vendor/github.com/unrolled/render/.travis.yml
new file mode 100644
index 0000000000..9ec8d547fe
--- /dev/null
+++ b/vendor/github.com/unrolled/render/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+
+go:
+  - 1.11.x
+  - 1.12.x
+  - tip
+
+env:
+  - GO111MODULE=on
+
+install:
+  - go mod download 
+
+script:
+  - go test -v -race -tags=integration
diff --git a/vendor/github.com/unrolled/render/LICENSE b/vendor/github.com/unrolled/render/LICENSE
new file mode 100644
index 0000000000..9c62063ec1
--- /dev/null
+++ b/vendor/github.com/unrolled/render/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Cory Jacobsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/unrolled/render/README.md b/vendor/github.com/unrolled/render/README.md
new file mode 100644
index 0000000000..5ffa2a7c1c
--- /dev/null
+++ b/vendor/github.com/unrolled/render/README.md
@@ -0,0 +1,508 @@
+# Render [![GoDoc](http://godoc.org/github.com/unrolled/render?status.svg)](http://godoc.org/github.com/unrolled/render) [![Build Status](https://travis-ci.org/unrolled/render.svg)](https://travis-ci.org/unrolled/render)
+
+Render is a package that provides functionality for easily rendering JSON, XML, text, binary data, and HTML templates. This package is based on the [Martini](https://github.com/go-martini/martini) [render](https://github.com/martini-contrib/render) work.
+
+## Block Deprecation Notice
+Go 1.6 introduces a new [block](https://github.com/golang/go/blob/release-branch.go1.6/src/html/template/example_test.go#L128) action. This conflicts with Render's included `block` template function. To provide an easy migration path, a new function was created called `partial`. It is a duplicate of the old `block` function. It is advised that all users of the `block` function update their code to avoid any issues in the future. Previous to Go 1.6, Render's `block` functionality will continue to work but a message will be logged urging you to migrate to the new `partial` function.
+
+## Usage
+Render can be used with pretty much any web framework providing you can access the `http.ResponseWriter` from your handler. The rendering functions simply wraps Go's existing functionality for marshaling and rendering data.
+
+- HTML: Uses the [html/template](http://golang.org/pkg/html/template/) package to render HTML templates.
+- JSON: Uses the [encoding/json](http://golang.org/pkg/encoding/json/) package to marshal data into a JSON-encoded response.
+- XML: Uses the [encoding/xml](http://golang.org/pkg/encoding/xml/) package to marshal data into an XML-encoded response.
+- Binary data: Passes the incoming data straight through to the `http.ResponseWriter`.
+- Text: Passes the incoming string straight through to the `http.ResponseWriter`.
+
+~~~ go
+// main.go
+package main
+
+import (
+    "encoding/xml"
+    "net/http"
+
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+type ExampleXml struct {
+    XMLName xml.Name `xml:"example"`
+    One     string   `xml:"one,attr"`
+    Two     string   `xml:"two,attr"`
+}
+
+func main() {
+    r := render.New()
+    mux := http.NewServeMux()
+
+    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+        w.Write([]byte("Welcome, visit sub pages now."))
+    })
+
+    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
+        r.Data(w, http.StatusOK, []byte("Some binary data here."))
+    })
+
+    mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
+        r.Text(w, http.StatusOK, "Plain text here")
+    })
+
+    mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
+    })
+
+    mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
+        r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
+    })
+
+    mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
+        r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
+    })
+
+    mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
+        // Assumes you have a template in ./templates called "example.tmpl"
+        // $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
+        r.HTML(w, http.StatusOK, "example", "World")
+    })
+
+    http.ListenAndServe("127.0.0.1:3000", mux)
+}
+~~~
+
+~~~ html
+<!-- templates/example.tmpl -->
+<h1>Hello {{.}}.</h1>
+~~~
+
+### Available Options
+Render comes with a variety of configuration options _(Note: these are not the default option values. See the defaults below.)_:
+
+~~~ go
+// ...
+r := render.New(render.Options{
+    Directory: "templates", // Specify what path to load the templates from.
+    FileSystem: &LocalFileSystem{}, // Specify filesystem from where files are loaded.
+    Asset: func(name string) ([]byte, error) { // Load from an Asset function instead of file.
+      return []byte("template content"), nil
+    },
+    AssetNames: func() []string { // Return a list of asset names for the Asset function
+      return []string{"filename.tmpl"}
+    },
+    Layout: "layout", // Specify a layout template. Layouts can call {{ yield }} to render the current template or {{ partial "css" }} to render a partial from the current template.
+    Extensions: []string{".tmpl", ".html"}, // Specify extensions to load for templates.
+    Funcs: []template.FuncMap{AppHelpers}, // Specify helper function maps for templates to access.
+    Delims: render.Delims{"{[{", "}]}"}, // Sets delimiters to the specified strings.
+    Charset: "UTF-8", // Sets encoding for content-types. Default is "UTF-8".
+    DisableCharset: true, // Prevents the charset from being appended to the content type header.
+    IndentJSON: true, // Output human readable JSON.
+    IndentXML: true, // Output human readable XML.
+    PrefixJSON: []byte(")]}',\n"), // Prefixes JSON responses with the given bytes.
+    PrefixXML: []byte("<?xml version='1.0' encoding='UTF-8'?>"), // Prefixes XML responses with the given bytes.
+    HTMLContentType: "application/xhtml+xml", // Output XHTML content type instead of default "text/html".
+    IsDevelopment: true, // Render will now recompile the templates on every HTML response.
+    UnEscapeHTML: true, // Replace ensure '&<>' are output correctly (JSON only).
+    StreamingJSON: true, // Streams the JSON response via json.Encoder.
+    RequirePartials: true, // Return an error if a template is missing a partial used in a layout.
+    DisableHTTPErrorRendering: true, // Disables automatic rendering of http.StatusInternalServerError when an error occurs.
+})
+// ...
+~~~
+
+### Default Options
+These are the preset options for Render:
+
+~~~ go
+r := render.New()
+
+// Is the same as the default configuration options:
+
+r := render.New(render.Options{
+    Directory: "templates",
+    FileSystem: &LocalFileSystem{},
+    Asset: nil,
+    AssetNames: nil,
+    Layout: "",
+    Extensions: []string{".tmpl"},
+    Funcs: []template.FuncMap{},
+    Delims: render.Delims{"{{", "}}"},
+    Charset: "UTF-8",
+    DisableCharset: false,
+    IndentJSON: false,
+    IndentXML: false,
+    PrefixJSON: []byte(""),
+    PrefixXML: []byte(""),
+    BinaryContentType: "application/octet-stream",
+    HTMLContentType: "text/html",
+    JSONContentType: "application/json",
+    JSONPContentType: "application/javascript",
+    TextContentType: "text/plain",
+    XMLContentType: "application/xhtml+xml",
+    IsDevelopment: false,
+    UnEscapeHTML: false,
+    StreamingJSON: false,
+    RequirePartials: false,
+    DisableHTTPErrorRendering: false,
+})
+~~~
+
+### JSON vs Streaming JSON
+By default, Render does **not** stream JSON to the `http.ResponseWriter`. It instead marshalls your object into a byte array, and if no errors occurred, writes that byte array to the `http.ResponseWriter`. If you would like to use the built it in streaming functionality (`json.Encoder`), you can set the `StreamingJSON` setting to `true`. This will stream the output directly to the `http.ResponseWriter`. Also note that streaming is only implemented in `render.JSON` and not `render.JSONP`, and the `UnEscapeHTML` and `Indent` options are ignored when streaming.
+
+### Loading Templates
+By default Render will attempt to load templates with a '.tmpl' extension from the "templates" directory. Templates are found by traversing the templates directory and are named by path and basename. For instance, the following directory structure:
+
+~~~
+templates/
+  |
+  |__ admin/
+  |      |
+  |      |__ index.tmpl
+  |      |
+  |      |__ edit.tmpl
+  |
+  |__ home.tmpl
+~~~
+
+Will provide the following templates:
+~~~
+admin/index
+admin/edit
+home
+~~~
+
+You can also load templates from memory by providing the Asset and AssetNames options,
+e.g. when generating an asset file using [go-bindata](https://github.com/jteeuwen/go-bindata).
+
+### Layouts
+Render provides `yield` and `partial` functions for layouts to access:
+~~~ go
+// ...
+r := render.New(render.Options{
+    Layout: "layout",
+})
+// ...
+~~~
+
+~~~ html
+<!-- templates/layout.tmpl -->
+<html>
+  <head>
+    <title>My Layout</title>
+    <!-- Render the partial template called `css-$current_template` here -->
+    {{ partial "css" }}
+  </head>
+  <body>
+    <!-- render the partial template called `header-$current_template` here -->
+    {{ partial "header" }}
+    <!-- Render the current template here -->
+    {{ yield }}
+    <!-- render the partial template called `footer-$current_template` here -->
+    {{ partial "footer" }}
+  </body>
+</html>
+~~~
+
+`current` can also be called to get the current template being rendered.
+~~~ html
+<!-- templates/layout.tmpl -->
+<html>
+  <head>
+    <title>My Layout</title>
+  </head>
+  <body>
+    This is the {{ current }} page.
+  </body>
+</html>
+~~~
+
+Partials are defined by individual templates as seen below. The partial template's
+name needs to be defined as "{partial name}-{template name}".
+~~~ html
+<!-- templates/home.tmpl -->
+{{ define "header-home" }}
+<h1>Home</h1>
+{{ end }}
+
+{{ define "footer-home"}}
+<p>The End</p>
+{{ end }}
+~~~
+
+By default, the template is not required to define all partials referenced in the
+layout. If you want an error to be returned when a template does not define a
+partial, set `Options.RequirePartials = true`.
+
+### Character Encodings
+Render will automatically set the proper Content-Type header based on which function you call. See below for an example of what the default settings would output (note that UTF-8 is the default, and binary data does not output the charset):
+~~~ go
+// main.go
+package main
+
+import (
+    "encoding/xml"
+    "net/http"
+
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+type ExampleXml struct {
+    XMLName xml.Name `xml:"example"`
+    One     string   `xml:"one,attr"`
+    Two     string   `xml:"two,attr"`
+}
+
+func main() {
+    r := render.New(render.Options{})
+    mux := http.NewServeMux()
+
+    // This will set the Content-Type header to "application/octet-stream".
+    // Note that this does not receive a charset value.
+    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
+        r.Data(w, http.StatusOK, []byte("Some binary data here."))
+    })
+
+    // This will set the Content-Type header to "application/json; charset=UTF-8".
+    mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
+    })
+
+    // This will set the Content-Type header to "text/xml; charset=UTF-8".
+    mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
+        r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
+    })
+
+    // This will set the Content-Type header to "text/plain; charset=UTF-8".
+    mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
+        r.Text(w, http.StatusOK, "Plain text here")
+    })
+
+    // This will set the Content-Type header to "text/html; charset=UTF-8".
+    mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
+        // Assumes you have a template in ./templates called "example.tmpl"
+        // $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
+        r.HTML(w, http.StatusOK, "example", "World")
+    })
+
+    http.ListenAndServe("127.0.0.1:3000", mux)
+}
+~~~
+
+In order to change the charset, you can set the `Charset` within the `render.Options` to your encoding value:
+~~~ go
+// main.go
+package main
+
+import (
+    "encoding/xml"
+    "net/http"
+
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+type ExampleXml struct {
+    XMLName xml.Name `xml:"example"`
+    One     string   `xml:"one,attr"`
+    Two     string   `xml:"two,attr"`
+}
+
+func main() {
+    r := render.New(render.Options{
+        Charset: "ISO-8859-1",
+    })
+    mux := http.NewServeMux()
+
+    // This will set the Content-Type header to "application/octet-stream".
+    // Note that this does not receive a charset value.
+    mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
+        r.Data(w, http.StatusOK, []byte("Some binary data here."))
+    })
+
+    // This will set the Content-Type header to "application/json; charset=ISO-8859-1".
+    mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
+    })
+
+    // This will set the Content-Type header to "text/xml; charset=ISO-8859-1".
+    mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
+        r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
+    })
+
+    // This will set the Content-Type header to "text/plain; charset=ISO-8859-1".
+    mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
+        r.Text(w, http.StatusOK, "Plain text here")
+    })
+
+    // This will set the Content-Type header to "text/html; charset=ISO-8859-1".
+    mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
+        // Assumes you have a template in ./templates called "example.tmpl"
+        // $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
+        r.HTML(w, http.StatusOK, "example", "World")
+    })
+
+    http.ListenAndServe("127.0.0.1:3000", mux)
+}
+~~~
+
+### Error Handling
+
+The rendering functions return any errors from the rendering engine.
+By default, they will also write the error to the HTTP response and set the status code to 500. You can disable
+this behavior so that you can handle errors yourself by setting
+`Options.DisableHTTPErrorRendering: true`.
+
+~~~go
+r := render.New(render.Options{
+  DisableHTTPErrorRendering: true,
+})
+
+//...
+
+err := r.HTML(w, http.StatusOK, "example", "World")
+if err != nil{
+  http.Redirect(w, r, "/my-custom-500", http.StatusFound)
+}
+~~~
+
+## Integration Examples
+
+### [Echo](https://github.com/labstack/echo)
+~~~ go
+// main.go
+package main
+
+import (
+    "io"
+    "net/http"
+
+    "github.com/labstack/echo"
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+type RenderWrapper struct { // We need to wrap the renderer because we need a different signature for echo.
+    rnd *render.Render
+}
+
+func (r *RenderWrapper) Render(w io.Writer, name string, data interface{},c echo.Context) error {
+    return r.rnd.HTML(w, 0, name, data) // The zero status code is overwritten by echo.
+}
+
+func main() {
+    r := &RenderWrapper{render.New()}
+
+    e := echo.New()
+
+    e.Renderer = r
+
+    e.GET("/", func(c echo.Context) error {
+        return c.Render(http.StatusOK, "TemplateName", "TemplateData")
+    })
+
+    e.Logger.Fatal(e.Start(":1323"))
+}
+~~~
+
+### [Gin](https://github.com/gin-gonic/gin)
+~~~ go
+// main.go
+package main
+
+import (
+    "net/http"
+
+    "github.com/gin-gonic/gin"
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+func main() {
+    r := render.New(render.Options{
+        IndentJSON: true,
+    })
+
+    router := gin.Default()
+
+    router.GET("/", func(c *gin.Context) {
+        r.JSON(c.Writer, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
+    })
+
+    router.Run(":3000")
+}
+~~~
+
+### [Goji](https://github.com/zenazn/goji)
+~~~ go
+// main.go
+package main
+
+import (
+    "net/http"
+
+    "github.com/zenazn/goji"
+    "github.com/zenazn/goji/web"
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+func main() {
+    r := render.New(render.Options{
+        IndentJSON: true,
+    })
+
+    goji.Get("/", func(c web.C, w http.ResponseWriter, req *http.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
+    })
+    goji.Serve()  // Defaults to ":8000".
+}
+~~~
+
+### [Negroni](https://github.com/codegangsta/negroni)
+~~~ go
+// main.go
+package main
+
+import (
+    "net/http"
+
+    "github.com/urfave/negroni"
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+func main() {
+    r := render.New(render.Options{
+        IndentJSON: true,
+    })
+    mux := http.NewServeMux()
+
+    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
+    })
+
+    n := negroni.Classic()
+    n.UseHandler(mux)
+    n.Run(":3000")
+}
+~~~
+
+### [Traffic](https://github.com/pilu/traffic)
+~~~ go
+// main.go
+package main
+
+import (
+    "net/http"
+
+    "github.com/pilu/traffic"
+    "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+)
+
+func main() {
+    r := render.New(render.Options{
+        IndentJSON: true,
+    })
+
+    router := traffic.New()
+    router.Get("/", func(w traffic.ResponseWriter, req *traffic.Request) {
+        r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
+    })
+
+    router.Run()
+}
+~~~
diff --git a/vendor/github.com/unrolled/render/buffer.go b/vendor/github.com/unrolled/render/buffer.go
new file mode 100644
index 0000000000..cdc92ffbde
--- /dev/null
+++ b/vendor/github.com/unrolled/render/buffer.go
@@ -0,0 +1,46 @@
+package render
+
+import "bytes"
+
+// bufPool represents a reusable buffer pool for executing templates into.
+var bufPool *BufferPool
+
+// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
+// Pulled from the github.com/oxtoacart/bpool package (Apache licensed).
+type BufferPool struct {
+	c chan *bytes.Buffer
+}
+
+// NewBufferPool creates a new BufferPool bounded to the given size.
+func NewBufferPool(size int) (bp *BufferPool) {
+	return &BufferPool{
+		c: make(chan *bytes.Buffer, size),
+	}
+}
+
+// Get gets a Buffer from the BufferPool, or creates a new one if none are
+// available in the pool.
+func (bp *BufferPool) Get() (b *bytes.Buffer) {
+	select {
+	case b = <-bp.c:
+	// reuse existing buffer
+	default:
+		// create new buffer
+		b = bytes.NewBuffer([]byte{})
+	}
+	return
+}
+
+// Put returns the given Buffer to the BufferPool.
+func (bp *BufferPool) Put(b *bytes.Buffer) {
+	b.Reset()
+	select {
+	case bp.c <- b:
+	default: // Discard the buffer if the pool is full.
+	}
+}
+
+// Initialize buffer pool for writing templates into.
+func init() {
+	bufPool = NewBufferPool(64)
+}
diff --git a/vendor/github.com/unrolled/render/doc.go b/vendor/github.com/unrolled/render/doc.go
new file mode 100644
index 0000000000..d3487ffba2
--- /dev/null
+++ b/vendor/github.com/unrolled/render/doc.go
@@ -0,0 +1,55 @@
+/*Package render is a package that provides functionality for easily rendering JSON, XML, binary data, and HTML templates.
+
+  package main
+
+  import (
+      "encoding/xml"
+      "net/http"
+
+      "github.com/unrolled/render"  // or "gopkg.in/unrolled/render.v1"
+  )
+
+  type ExampleXml struct {
+      XMLName xml.Name `xml:"example"`
+      One     string   `xml:"one,attr"`
+      Two     string   `xml:"two,attr"`
+  }
+
+  func main() {
+      r := render.New()
+      mux := http.NewServeMux()
+
+      mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+          w.Write([]byte("Welcome, visit sub pages now."))
+      })
+
+      mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
+          r.Data(w, http.StatusOK, []byte("Some binary data here."))
+      })
+
+      mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
+          r.Text(w, http.StatusOK, "Plain text here")
+      })
+
+      mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
+          r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
+      })
+
+      mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
+          r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
+      })
+
+      mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
+          r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
+      })
+
+      mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
+          // Assumes you have a template in ./templates called "example.tmpl".
+          // $ mkdir -p templates && echo "<h1>Hello HTML world.</h1>" > templates/example.tmpl
+          r.HTML(w, http.StatusOK, "example", nil)
+      })
+
+      http.ListenAndServe("0.0.0.0:3000", mux)
+  }
+*/
+package render
diff --git a/vendor/github.com/unrolled/render/engine.go b/vendor/github.com/unrolled/render/engine.go
new file mode 100644
index 0000000000..cdf1a1b16f
--- /dev/null
+++ b/vendor/github.com/unrolled/render/engine.go
@@ -0,0 +1,217 @@
+package render
+
+import (
+	"bytes"
+	"encoding/json"
+	"encoding/xml"
+	"html/template"
+	"io"
+	"net/http"
+)
+
+// Engine is the generic interface for all responses.
+type Engine interface {
+	Render(io.Writer, interface{}) error
+}
+
+// Head defines the basic ContentType and Status fields.
+type Head struct {
+	ContentType string
+	Status      int
+}
+
+// Data built-in renderer.
+type Data struct {
+	Head
+}
+
+// HTML built-in renderer.
+type HTML struct {
+	Head
+	Name      string
+	Templates *template.Template
+}
+
+// JSON built-in renderer.
+type JSON struct {
+	Head
+	Indent        bool
+	UnEscapeHTML  bool
+	Prefix        []byte
+	StreamingJSON bool
+}
+
+// JSONP built-in renderer.
+type JSONP struct {
+	Head
+	Indent   bool
+	Callback string
+}
+
+// Text built-in renderer.
+type Text struct {
+	Head
+}
+
+// XML built-in renderer.
+type XML struct {
+	Head
+	Indent bool
+	Prefix []byte
+}
+
+// Write outputs the header content.
+func (h Head) Write(w http.ResponseWriter) {
+	w.Header().Set(ContentType, h.ContentType)
+	w.WriteHeader(h.Status)
+}
+
+// Render a data response.
+func (d Data) Render(w io.Writer, v interface{}) error {
+	if hw, ok := w.(http.ResponseWriter); ok {
+		c := hw.Header().Get(ContentType)
+		if c != "" {
+			d.Head.ContentType = c
+		}
+		d.Head.Write(hw)
+	}
+
+	w.Write(v.([]byte))
+	return nil
+}
+
+// Render a HTML response.
+func (h HTML) Render(w io.Writer, binding interface{}) error {
+	// Retrieve a buffer from the pool to write to.
+	out := bufPool.Get()
+	err := h.Templates.ExecuteTemplate(out, h.Name, binding)
+	if err != nil {
+		return err
+	}
+
+	if hw, ok := w.(http.ResponseWriter); ok {
+		h.Head.Write(hw)
+	}
+	out.WriteTo(w)
+
+	// Return the buffer to the pool.
+	bufPool.Put(out)
+	return nil
+}
+
+// Render a JSON response.
+func (j JSON) Render(w io.Writer, v interface{}) error {
+	if j.StreamingJSON {
+		return j.renderStreamingJSON(w, v)
+	}
+
+	var result []byte
+	var err error
+
+	if j.Indent {
+		result, err = json.MarshalIndent(v, "", "  ")
+		result = append(result, '\n')
+	} else {
+		result, err = json.Marshal(v)
+	}
+	if err != nil {
+		return err
+	}
+
+	// Unescape HTML if needed.
+	if j.UnEscapeHTML {
+		result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1)
+		result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1)
+		result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1)
+	}
+
+	// JSON marshaled fine, write out the result.
+	if hw, ok := w.(http.ResponseWriter); ok {
+		j.Head.Write(hw)
+	}
+	if len(j.Prefix) > 0 {
+		w.Write(j.Prefix)
+	}
+	w.Write(result)
+	return nil
+}
+
+func (j JSON) renderStreamingJSON(w io.Writer, v interface{}) error {
+	if hw, ok := w.(http.ResponseWriter); ok {
+		j.Head.Write(hw)
+	}
+	if len(j.Prefix) > 0 {
+		w.Write(j.Prefix)
+	}
+
+	return json.NewEncoder(w).Encode(v)
+}
+
+// Render a JSONP response.
+func (j JSONP) Render(w io.Writer, v interface{}) error {
+	var result []byte
+	var err error
+
+	if j.Indent {
+		result, err = json.MarshalIndent(v, "", "  ")
+	} else {
+		result, err = json.Marshal(v)
+	}
+	if err != nil {
+		return err
+	}
+
+	// JSON marshaled fine, write out the result.
+	if hw, ok := w.(http.ResponseWriter); ok {
+		j.Head.Write(hw)
+	}
+	w.Write([]byte(j.Callback + "("))
+	w.Write(result)
+	w.Write([]byte(");"))
+
+	// If indenting, append a new line.
+	if j.Indent {
+		w.Write([]byte("\n"))
+	}
+	return nil
+}
+
+// Render a text response.
+func (t Text) Render(w io.Writer, v interface{}) error {
+	if hw, ok := w.(http.ResponseWriter); ok {
+		c := hw.Header().Get(ContentType)
+		if c != "" {
+			t.Head.ContentType = c
+		}
+		t.Head.Write(hw)
+	}
+
+	w.Write([]byte(v.(string)))
+	return nil
+}
+
+// Render an XML response.
+func (x XML) Render(w io.Writer, v interface{}) error {
+	var result []byte
+	var err error
+
+	if x.Indent {
+		result, err = xml.MarshalIndent(v, "", "  ")
+		result = append(result, '\n')
+	} else {
+		result, err = xml.Marshal(v)
+	}
+	if err != nil {
+		return err
+	}
+
+	// XML marshaled fine, write out the result.
+	if hw, ok := w.(http.ResponseWriter); ok {
+		x.Head.Write(hw)
+	}
+	if len(x.Prefix) > 0 {
+		w.Write(x.Prefix)
+	}
+	w.Write(result)
+	return nil
+}
diff --git a/vendor/github.com/unrolled/render/fs.go b/vendor/github.com/unrolled/render/fs.go
new file mode 100644
index 0000000000..3e60776253
--- /dev/null
+++ b/vendor/github.com/unrolled/render/fs.go
@@ -0,0 +1,21 @@
+package render
+
+import (
+	"io/ioutil"
+	"path/filepath"
+)
+
+type FileSystem interface {
+	Walk(root string, walkFn filepath.WalkFunc) error
+	ReadFile(filename string) ([]byte, error)
+}
+
+type LocalFileSystem struct{}
+
+func (LocalFileSystem) Walk(root string, walkFn filepath.WalkFunc) error {
+	return filepath.Walk(root, walkFn)
+}
+
+func (LocalFileSystem) ReadFile(filename string) ([]byte, error) {
+	return ioutil.ReadFile(filename)
+}
diff --git a/vendor/github.com/unrolled/render/go.mod b/vendor/github.com/unrolled/render/go.mod
new file mode 100644
index 0000000000..22d793cbcf
--- /dev/null
+++ b/vendor/github.com/unrolled/render/go.mod
@@ -0,0 +1,5 @@
+module github.com/unrolled/render
+
+go 1.12
+
+require github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
diff --git a/vendor/github.com/unrolled/render/go.sum b/vendor/github.com/unrolled/render/go.sum
new file mode 100644
index 0000000000..2199959075
--- /dev/null
+++ b/vendor/github.com/unrolled/render/go.sum
@@ -0,0 +1,2 @@
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
diff --git a/vendor/github.com/unrolled/render/helpers.go b/vendor/github.com/unrolled/render/helpers.go
new file mode 100644
index 0000000000..699508a491
--- /dev/null
+++ b/vendor/github.com/unrolled/render/helpers.go
@@ -0,0 +1,21 @@
+// +build go1.6
+
+package render
+
+import (
+	"fmt"
+	"html/template"
+)
+
+// Included helper functions for use when rendering HTML.
+var helperFuncs = template.FuncMap{
+	"yield": func() (string, error) {
+		return "", fmt.Errorf("yield called with no layout defined")
+	},
+	"partial": func() (string, error) {
+		return "", fmt.Errorf("block called with no layout defined")
+	},
+	"current": func() (string, error) {
+		return "", nil
+	},
+}
diff --git a/vendor/github.com/unrolled/render/helpers_pre16.go b/vendor/github.com/unrolled/render/helpers_pre16.go
new file mode 100644
index 0000000000..999d9af497
--- /dev/null
+++ b/vendor/github.com/unrolled/render/helpers_pre16.go
@@ -0,0 +1,26 @@
+// +build !go1.6
+
+package render
+
+import (
+	"fmt"
+	"html/template"
+)
+
+// Included helper functions for use when rendering HTML.
+var helperFuncs = template.FuncMap{
+	"yield": func() (string, error) {
+		return "", fmt.Errorf("yield called with no layout defined")
+	},
+	// `block` is deprecated! Use the `partial` below if you need this functionality still.
+	// Otherwise, checkout Go's `block` implementation introduced in 1.6
+	"block": func() (string, error) {
+		return "", fmt.Errorf("block called with no layout defined")
+	},
+	"partial": func() (string, error) {
+		return "", fmt.Errorf("block called with no layout defined")
+	},
+	"current": func() (string, error) {
+		return "", nil
+	},
+}
diff --git a/vendor/github.com/unrolled/render/render.go b/vendor/github.com/unrolled/render/render.go
new file mode 100644
index 0000000000..3259f620eb
--- /dev/null
+++ b/vendor/github.com/unrolled/render/render.go
@@ -0,0 +1,480 @@
+package render
+
+import (
+	"bytes"
+	"fmt"
+	"html/template"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+)
+
+const (
+	// ContentBinary header value for binary data.
+	ContentBinary = "application/octet-stream"
+	// ContentHTML header value for HTML data.
+	ContentHTML = "text/html"
+	// ContentJSON header value for JSON data.
+	ContentJSON = "application/json"
+	// ContentJSONP header value for JSONP data.
+	ContentJSONP = "application/javascript"
+	// ContentLength header constant.
+	ContentLength = "Content-Length"
+	// ContentText header value for Text data.
+	ContentText = "text/plain"
+	// ContentType header constant.
+	ContentType = "Content-Type"
+	// ContentXHTML header value for XHTML data.
+	ContentXHTML = "application/xhtml+xml"
+	// ContentXML header value for XML data.
+	ContentXML = "text/xml"
+	// Default character encoding.
+	defaultCharset = "UTF-8"
+)
+
+// helperFuncs had to be moved out. See helpers.go|helpers_pre16.go files.
+
+// Delims represents a set of Left and Right delimiters for HTML template rendering.
+type Delims struct {
+	// Left delimiter, defaults to {{.
+	Left string
+	// Right delimiter, defaults to }}.
+	Right string
+}
+
+// Options is a struct for specifying configuration options for the render.Render object.
+type Options struct {
+	// Directory to load templates. Default is "templates".
+	Directory string
+	// FileSystem to access files
+	FileSystem FileSystem
+	// Asset function to use in place of directory. Defaults to nil.
+	Asset func(name string) ([]byte, error)
+	// AssetNames function to use in place of directory. Defaults to nil.
+	AssetNames func() []string
+	// Layout template name. Will not render a layout if blank (""). Defaults to blank ("").
+	Layout string
+	// Extensions to parse template files from. Defaults to [".tmpl"].
+	Extensions []string
+	// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to empty map.
+	Funcs []template.FuncMap
+	// Delims sets the action delimiters to the specified strings in the Delims struct.
+	Delims Delims
+	// Appends the given character set to the Content-Type header. Default is "UTF-8".
+	Charset string
+	// If DisableCharset is set to true, it will not append the above Charset value to the Content-Type header. Default is false.
+	DisableCharset bool
+	// Outputs human readable JSON.
+	IndentJSON bool
+	// Outputs human readable XML. Default is false.
+	IndentXML bool
+	// Prefixes the JSON output with the given bytes. Default is false.
+	PrefixJSON []byte
+	// Prefixes the XML output with the given bytes.
+	PrefixXML []byte
+	// Allows changing the binary content type.
+	BinaryContentType string
+	// Allows changing the HTML content type.
+	HTMLContentType string
+	// Allows changing the JSON content type.
+	JSONContentType string
+	// Allows changing the JSONP content type.
+	JSONPContentType string
+	// Allows changing the Text content type.
+	TextContentType string
+	// Allows changing the XML content type.
+	XMLContentType string
+	// If IsDevelopment is set to true, this will recompile the templates on every request. Default is false.
+	IsDevelopment bool
+	// Unescape HTML characters "&<>" to their original values. Default is false.
+	UnEscapeHTML bool
+	// Streams JSON responses instead of marshalling prior to sending. Default is false.
+	StreamingJSON bool
+	// Require that all partials executed in the layout are implemented in all templates using the layout. Default is false.
+	RequirePartials bool
+	// Deprecated: Use the above `RequirePartials` instead of this. As of Go 1.6, blocks are built in. Default is false.
+	RequireBlocks bool
+	// Disables automatic rendering of http.StatusInternalServerError when an error occurs. Default is false.
+	DisableHTTPErrorRendering bool
+	// Enables using partials without the current filename suffix which allows use of the same template in multiple files. e.g {{ partial "carosuel" }} inside the home template will match carosel-home or carosel.
+	// ***NOTE*** - This option should be named RenderPartialsWithoutSuffix as that is what it does. "Prefix" is a typo. Maintaining the existing name for backwards compatibility.
+	RenderPartialsWithoutPrefix bool
+}
+
+// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
+type HTMLOptions struct {
+	// Layout template name. Overrides Options.Layout.
+	Layout string
+	// Funcs added to Options.Funcs.
+	Funcs template.FuncMap
+}
+
+// Render is a service that provides functions for easily writing JSON, XML,
+// binary data, and HTML templates out to a HTTP Response.
+type Render struct {
+	// Customize Secure with an Options struct.
+	opt             Options
+	templates       *template.Template
+	templatesLk     sync.Mutex
+	compiledCharset string
+}
+
+// New constructs a new Render instance with the supplied options.
+func New(options ...Options) *Render {
+	var o Options
+	if len(options) > 0 {
+		o = options[0]
+	}
+
+	r := Render{
+		opt: o,
+	}
+
+	r.prepareOptions()
+	r.compileTemplates()
+
+	return &r
+}
+
+func (r *Render) prepareOptions() {
+	// Fill in the defaults if need be.
+	if len(r.opt.Charset) == 0 {
+		r.opt.Charset = defaultCharset
+	}
+	if r.opt.DisableCharset == false {
+		r.compiledCharset = "; charset=" + r.opt.Charset
+	}
+
+	if len(r.opt.Directory) == 0 {
+		r.opt.Directory = "templates"
+	}
+	if r.opt.FileSystem == nil {
+		r.opt.FileSystem = &LocalFileSystem{}
+	}
+	if len(r.opt.Extensions) == 0 {
+		r.opt.Extensions = []string{".tmpl"}
+	}
+	if len(r.opt.BinaryContentType) == 0 {
+		r.opt.BinaryContentType = ContentBinary
+	}
+	if len(r.opt.HTMLContentType) == 0 {
+		r.opt.HTMLContentType = ContentHTML
+	}
+	if len(r.opt.JSONContentType) == 0 {
+		r.opt.JSONContentType = ContentJSON
+	}
+	if len(r.opt.JSONPContentType) == 0 {
+		r.opt.JSONPContentType = ContentJSONP
+	}
+	if len(r.opt.TextContentType) == 0 {
+		r.opt.TextContentType = ContentText
+	}
+	if len(r.opt.XMLContentType) == 0 {
+		r.opt.XMLContentType = ContentXML
+	}
+}
+
+func (r *Render) compileTemplates() {
+	if r.opt.Asset == nil || r.opt.AssetNames == nil {
+		r.compileTemplatesFromDir()
+		return
+	}
+	r.compileTemplatesFromAsset()
+}
+
+func (r *Render) compileTemplatesFromDir() {
+	dir := r.opt.Directory
+	r.templates = template.New(dir)
+	r.templates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
+
+	// Walk the supplied directory and compile any files that match our extension list.
+	r.opt.FileSystem.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
+		// These dirs should be excluded as they are not valid golang templates, but files under
+		// them should be treat as normal.
+		// If is a dir, return immediately (dir is not a valid golang template).
+		if info == nil || info.IsDir() {
+			return nil
+		}
+
+		rel, err := filepath.Rel(dir, path)
+		if err != nil {
+			return err
+		}
+
+		ext := ""
+		if strings.Index(rel, ".") != -1 {
+			ext = filepath.Ext(rel)
+		}
+
+		for _, extension := range r.opt.Extensions {
+			if ext == extension {
+				buf, err := r.opt.FileSystem.ReadFile(path)
+				if err != nil {
+					panic(err)
+				}
+
+				name := (rel[0 : len(rel)-len(ext)])
+				tmpl := r.templates.New(filepath.ToSlash(name))
+
+				// Add our funcmaps.
+				for _, funcs := range r.opt.Funcs {
+					tmpl.Funcs(funcs)
+				}
+
+				// Break out if this parsing fails. We don't want any silent server starts.
+				template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
+				break
+			}
+		}
+		return nil
+	})
+}
+
+func (r *Render) compileTemplatesFromAsset() {
+	dir := r.opt.Directory
+	r.templates = template.New(dir)
+	r.templates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
+
+	for _, path := range r.opt.AssetNames() {
+		if !strings.HasPrefix(path, dir) {
+			continue
+		}
+
+		rel, err := filepath.Rel(dir, path)
+		if err != nil {
+			panic(err)
+		}
+
+		ext := ""
+		if strings.Index(rel, ".") != -1 {
+			ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
+		}
+
+		for _, extension := range r.opt.Extensions {
+			if ext == extension {
+
+				buf, err := r.opt.Asset(path)
+				if err != nil {
+					panic(err)
+				}
+
+				name := (rel[0 : len(rel)-len(ext)])
+				tmpl := r.templates.New(filepath.ToSlash(name))
+
+				// Add our funcmaps.
+				for _, funcs := range r.opt.Funcs {
+					tmpl.Funcs(funcs)
+				}
+
+				// Break out if this parsing fails. We don't want any silent server starts.
+				template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
+				break
+			}
+		}
+	}
+}
+
+// TemplateLookup is a wrapper around template.Lookup and returns
+// the template with the given name that is associated with t, or nil
+// if there is no such template.
+func (r *Render) TemplateLookup(t string) *template.Template {
+	return r.templates.Lookup(t)
+}
+
+func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
+	buf := new(bytes.Buffer)
+	return buf, r.templates.ExecuteTemplate(buf, name, binding)
+}
+
+func (r *Render) layoutFuncs(name string, binding interface{}) template.FuncMap {
+	return template.FuncMap{
+		"yield": func() (template.HTML, error) {
+			buf, err := r.execute(name, binding)
+			// Return safe HTML here since we are rendering our own template.
+			return template.HTML(buf.String()), err
+		},
+		"current": func() (string, error) {
+			return name, nil
+		},
+		"block": func(partialName string) (template.HTML, error) {
+			log.Print("Render's `block` implementation is now depericated. Use `partial` as a drop in replacement.")
+			fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
+			if r.TemplateLookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
+				fullPartialName = partialName
+			}
+			if r.opt.RequireBlocks || r.TemplateLookup(fullPartialName) != nil {
+				buf, err := r.execute(fullPartialName, binding)
+				// Return safe HTML here since we are rendering our own template.
+				return template.HTML(buf.String()), err
+			}
+			return "", nil
+		},
+		"partial": func(partialName string) (template.HTML, error) {
+			fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
+			if r.TemplateLookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
+				fullPartialName = partialName
+			}
+			if r.opt.RequirePartials || r.TemplateLookup(fullPartialName) != nil {
+				buf, err := r.execute(fullPartialName, binding)
+				// Return safe HTML here since we are rendering our own template.
+				return template.HTML(buf.String()), err
+			}
+			return "", nil
+		},
+	}
+}
+
+func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
+	layout := r.opt.Layout
+	funcs := template.FuncMap{}
+
+	for _, tmp := range r.opt.Funcs {
+		for k, v := range tmp {
+			funcs[k] = v
+		}
+	}
+
+	if len(htmlOpt) > 0 {
+		opt := htmlOpt[0]
+		if len(opt.Layout) > 0 {
+			layout = opt.Layout
+		}
+
+		for k, v := range opt.Funcs {
+			funcs[k] = v
+		}
+	}
+
+	return HTMLOptions{
+		Layout: layout,
+		Funcs:  funcs,
+	}
+}
+
+// Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations.
+func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
+	err := e.Render(w, data)
+	if hw, ok := w.(http.ResponseWriter); err != nil && !r.opt.DisableHTTPErrorRendering && ok {
+		http.Error(hw, err.Error(), http.StatusInternalServerError)
+	}
+	return err
+}
+
+// Data writes out the raw bytes as binary data.
+func (r *Render) Data(w io.Writer, status int, v []byte) error {
+	head := Head{
+		ContentType: r.opt.BinaryContentType,
+		Status:      status,
+	}
+
+	d := Data{
+		Head: head,
+	}
+
+	return r.Render(w, d, v)
+}
+
+// HTML builds up the response from the specified template and bindings.
+func (r *Render) HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...HTMLOptions) error {
+	r.templatesLk.Lock()
+	defer r.templatesLk.Unlock()
+
+	// If we are in development mode, recompile the templates on every HTML request.
+	if r.opt.IsDevelopment {
+		r.compileTemplates()
+	}
+
+	opt := r.prepareHTMLOptions(htmlOpt)
+	if tpl := r.templates.Lookup(name); tpl != nil {
+		if len(opt.Layout) > 0 {
+			tpl.Funcs(r.layoutFuncs(name, binding))
+			name = opt.Layout
+		}
+
+		if len(opt.Funcs) > 0 {
+			tpl.Funcs(opt.Funcs)
+		}
+	}
+
+	head := Head{
+		ContentType: r.opt.HTMLContentType + r.compiledCharset,
+		Status:      status,
+	}
+
+	h := HTML{
+		Head:      head,
+		Name:      name,
+		Templates: r.templates,
+	}
+
+	return r.Render(w, h, binding)
+}
+
+// JSON marshals the given interface object and writes the JSON response.
+func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
+	head := Head{
+		ContentType: r.opt.JSONContentType + r.compiledCharset,
+		Status:      status,
+	}
+
+	j := JSON{
+		Head:          head,
+		Indent:        r.opt.IndentJSON,
+		Prefix:        r.opt.PrefixJSON,
+		UnEscapeHTML:  r.opt.UnEscapeHTML,
+		StreamingJSON: r.opt.StreamingJSON,
+	}
+
+	return r.Render(w, j, v)
+}
+
+// JSONP marshals the given interface object and writes the JSON response.
+func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}) error {
+	head := Head{
+		ContentType: r.opt.JSONPContentType + r.compiledCharset,
+		Status:      status,
+	}
+
+	j := JSONP{
+		Head:     head,
+		Indent:   r.opt.IndentJSON,
+		Callback: callback,
+	}
+
+	return r.Render(w, j, v)
+}
+
+// Text writes out a string as plain text.
+func (r *Render) Text(w io.Writer, status int, v string) error {
+	head := Head{
+		ContentType: r.opt.TextContentType + r.compiledCharset,
+		Status:      status,
+	}
+
+	t := Text{
+		Head: head,
+	}
+
+	return r.Render(w, t, v)
+}
+
+// XML marshals the given interface object and writes the XML response.
+func (r *Render) XML(w io.Writer, status int, v interface{}) error {
+	head := Head{
+		ContentType: r.opt.XMLContentType + r.compiledCharset,
+		Status:      status,
+	}
+
+	x := XML{
+		Head:   head,
+		Indent: r.opt.IndentXML,
+		Prefix: r.opt.PrefixXML,
+	}
+
+	return r.Render(w, x, v)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index ee584932d2..426b0dc947 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -7,6 +7,13 @@ code.gitea.io/gitea-vet/checks
 # code.gitea.io/sdk/gitea v0.13.1
 ## explicit
 code.gitea.io/sdk/gitea
+# gitea.com/go-chi/session v0.0.0-20201218134809-7209fa084f27
+## explicit
+gitea.com/go-chi/session
+gitea.com/go-chi/session/couchbase
+gitea.com/go-chi/session/memcache
+gitea.com/go-chi/session/mysql
+gitea.com/go-chi/session/postgres
 # gitea.com/lunny/levelqueue v0.3.0
 ## explicit
 gitea.com/lunny/levelqueue
@@ -188,7 +195,7 @@ github.com/cespare/xxhash/v2
 github.com/chris-ramon/douceur/parser
 # github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89
 github.com/couchbase/go-couchbase
-# github.com/couchbase/gomemcached v0.1.0
+# github.com/couchbase/gomemcached v0.1.1
 github.com/couchbase/gomemcached
 github.com/couchbase/gomemcached/client
 # github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67
@@ -257,7 +264,7 @@ github.com/gliderlabs/ssh
 github.com/glycerine/go-unsnap-stream
 # github.com/go-asn1-ber/asn1-ber v1.5.1
 github.com/go-asn1-ber/asn1-ber
-# github.com/go-chi/chi v1.5.0
+# github.com/go-chi/chi v1.5.1
 ## explicit
 github.com/go-chi/chi
 github.com/go-chi/chi/middleware
@@ -630,8 +637,6 @@ github.com/oliamb/cutter
 github.com/olivere/elastic/v7
 github.com/olivere/elastic/v7/config
 github.com/olivere/elastic/v7/uritemplates
-# github.com/onsi/ginkgo v1.13.0
-## explicit
 # github.com/pelletier/go-toml v1.8.1
 ## explicit
 github.com/pelletier/go-toml
@@ -750,6 +755,9 @@ github.com/unknwon/i18n
 # github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
 ## explicit
 github.com/unknwon/paginater
+# github.com/unrolled/render v1.0.3
+## explicit
+github.com/unrolled/render
 # github.com/urfave/cli v1.22.5
 ## explicit
 github.com/urfave/cli
@@ -916,7 +924,6 @@ golang.org/x/tools/internal/typesinternal
 golang.org/x/xerrors
 golang.org/x/xerrors/internal
 # google.golang.org/appengine v1.6.7
-## explicit
 google.golang.org/appengine
 google.golang.org/appengine/internal
 google.golang.org/appengine/internal/app_identity