Commit 6fde3632 authored by Matthew Holt's avatar Matthew Holt

Vendor all dependencies (Warning: Huge changeset.)

The vendor/ folder was created with the help of @FiloSottile's gvt and
vendorcheck.

Any dependencies of Caddy plugins outside this repo are not vendored.

We do not remove any unused, vendored packages because vendorcheck -u
only checks using the current build configuration; i.e. packages that
may be imported by files toggled by build tags of other systems.

CI tests have been updated to ignore the vendor/ folder. When Go 1.9 is
released, a few of the go commands should be revised to again use ./...
as it will ignore the vendor folder by default.
parent 90efff68
...@@ -37,9 +37,9 @@ Here are some of the expectations we have of contributors: ...@@ -37,9 +37,9 @@ Here are some of the expectations we have of contributors:
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass. - **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven to work better with benchmarks or profiling. - **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. - **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors can help maintain their change after it is merged. - **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors can help maintain their change after it is merged.
...@@ -126,7 +126,9 @@ Collabators have push rights to the repository. We grant this permission after o ...@@ -126,7 +126,9 @@ Collabators have push rights to the repository. We grant this permission after o
- **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too). - **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too).
- **Prefer squashed commits over a messy merge.** If there are many little commits, please squash the commits so we don't clutter the commit history. - **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy vendors all dependencies with the help of [gvt](https://github.com/FiloSottile/gvt). All external dependencies must be vendored, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddy` and `caddytls` packages especially. - **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddy` and `caddytls` packages especially.
......
language: go language: go
go: go:
- 1.8 - 1.8.3
- tip - tip
matrix: matrix:
...@@ -20,15 +20,20 @@ install: ...@@ -20,15 +20,20 @@ install:
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi
- go get -t ./... - go get -t ./...
- go get github.com/golang/lint/golint - go get github.com/golang/lint/golint
- go get github.com/gordonklaus/ineffassign - go get github.com/FiloSottile/vendorcheck
# Install gometalinter and certain linters
- go get github.com/alecthomas/gometalinter
- go get github.com/client9/misspell/cmd/misspell - go get github.com/client9/misspell/cmd/misspell
- go get github.com/gordonklaus/ineffassign
- go get golang.org/x/tools/cmd/goimports
- go get github.com/tsenart/deadcode
script: script:
- diff <(echo -n) <(gofmt -s -d .) - gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- ineffassign . - vendorcheck ./...
- misspell -error . # TODO: When Go 1.9 is released, replace $(go list) subcommand with ./... because vendor folder should be ignored
- go vet ./... - go test -race $(go list ./... | grep -v vendor)
- go test -race ./...
after_script: after_script:
- golint ./... # TODO: When Go 1.9 is released, replace $(go list) subcommand with ./... because vendor folder should be ignored
- golint $(go list ./... | grep -v vendor)
...@@ -123,6 +123,8 @@ The Caddy project does not officially maintain any system-specific integrations ...@@ -123,6 +123,8 @@ The Caddy project does not officially maintain any system-specific integrations
How you choose to run Caddy is up to you. Many users are satisfied with `nohup caddy &`. Others use `screen`. Users who need Caddy to come back up after reboots either do so in the script that caused the reboot, add a command to an init script, or configure a service with their OS. How you choose to run Caddy is up to you. Many users are satisfied with `nohup caddy &`. Others use `screen`. Users who need Caddy to come back up after reboots either do so in the script that caused the reboot, add a command to an init script, or configure a service with their OS.
If you have questions or concerns about Caddy' underlying crypto implementations, consult Go's [crypto packages](https://golang.org/pkg/crypto), starting with their documentation, then issues, then the code itself; as Caddy uses mainly those libraries.
## Contributing ## Contributing
......
...@@ -9,23 +9,31 @@ environment: ...@@ -9,23 +9,31 @@ environment:
install: install:
- rmdir c:\go /s /q - rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.windows-amd64.zip - appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.3.windows-amd64.zip
- 7z x go1.8.windows-amd64.zip -y -oC:\ > NUL - 7z x go1.8.3.windows-amd64.zip -y -oC:\ > NUL
- set PATH=%GOPATH%\bin;%PATH%
- go version - go version
- go env - go env
- go get -t ./... - go get -t ./...
- go get github.com/golang/lint/golint - go get github.com/golang/lint/golint
- go get github.com/FiloSottile/vendorcheck
# Install gometalinter and certain linters
- go get github.com/alecthomas/gometalinter
- go get github.com/client9/misspell/cmd/misspell
- go get github.com/gordonklaus/ineffassign - go get github.com/gordonklaus/ineffassign
- set PATH=%GOPATH%\bin;%PATH% - go get golang.org/x/tools/cmd/goimports
- go get github.com/tsenart/deadcode
build: off build: off
test_script: test_script:
- go vet ./... - gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- go test -race ./... - vendorcheck ./...
- ineffassign . # TODO: When Go 1.9 comes out, replace this whole line with `go test -race ./...` b/c vendor folder should be ignored
- for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do (go test -race %%G & IF ERRORLEVEL == 1 EXIT 1)
after_test: after_test:
- golint ./... # TODO: When Go 1.9 comes out, replace this whole line with `golint ./...` b/c vendor folder should be ignored
- for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do (golint %%G & IF ERRORLEVEL == 1 EXIT 1)
deploy: off deploy: off
...@@ -16,9 +16,9 @@ import ( ...@@ -16,9 +16,9 @@ import (
_ "github.com/mholt/caddy/caddyhttp/header" _ "github.com/mholt/caddy/caddyhttp/header"
_ "github.com/mholt/caddy/caddyhttp/index" _ "github.com/mholt/caddy/caddyhttp/index"
_ "github.com/mholt/caddy/caddyhttp/internalsrv" _ "github.com/mholt/caddy/caddyhttp/internalsrv"
_ "github.com/mholt/caddy/caddyhttp/limits"
_ "github.com/mholt/caddy/caddyhttp/log" _ "github.com/mholt/caddy/caddyhttp/log"
_ "github.com/mholt/caddy/caddyhttp/markdown" _ "github.com/mholt/caddy/caddyhttp/markdown"
_ "github.com/mholt/caddy/caddyhttp/maxrequestbody"
_ "github.com/mholt/caddy/caddyhttp/mime" _ "github.com/mholt/caddy/caddyhttp/mime"
_ "github.com/mholt/caddy/caddyhttp/pprof" _ "github.com/mholt/caddy/caddyhttp/pprof"
_ "github.com/mholt/caddy/caddyhttp/proxy" _ "github.com/mholt/caddy/caddyhttp/proxy"
......
...@@ -50,7 +50,9 @@ const ( ...@@ -50,7 +50,9 @@ const (
hasOp = "has" hasOp = "has"
notHasOp = "not_has" notHasOp = "not_has"
startsWithOp = "starts_with" startsWithOp = "starts_with"
notStartsWithOp = "not_starts_with"
endsWithOp = "ends_with" endsWithOp = "ends_with"
notEndsWithOp = "not_ends_with"
matchOp = "match" matchOp = "match"
notMatchOp = "not_match" notMatchOp = "not_match"
) )
...@@ -68,7 +70,9 @@ var ifConditions = map[string]ifCondition{ ...@@ -68,7 +70,9 @@ var ifConditions = map[string]ifCondition{
hasOp: hasFunc, hasOp: hasFunc,
notHasOp: notHasFunc, notHasOp: notHasFunc,
startsWithOp: startsWithFunc, startsWithOp: startsWithFunc,
notStartsWithOp: notStartsWithFunc,
endsWithOp: endsWithFunc, endsWithOp: endsWithFunc,
notEndsWithOp: notEndsWithFunc,
matchOp: matchFunc, matchOp: matchFunc,
notMatchOp: notMatchFunc, notMatchOp: notMatchFunc,
} }
...@@ -82,7 +86,7 @@ func isFunc(a, b string) bool { ...@@ -82,7 +86,7 @@ func isFunc(a, b string) bool {
// notFunc is condition for Not operator. // notFunc is condition for Not operator.
// It checks for inequality. // It checks for inequality.
func notFunc(a, b string) bool { func notFunc(a, b string) bool {
return a != b return !isFunc(a, b)
} }
// hasFunc is condition for Has operator. // hasFunc is condition for Has operator.
...@@ -94,7 +98,7 @@ func hasFunc(a, b string) bool { ...@@ -94,7 +98,7 @@ func hasFunc(a, b string) bool {
// notHasFunc is condition for NotHas operator. // notHasFunc is condition for NotHas operator.
// It checks if b is not a substring of a. // It checks if b is not a substring of a.
func notHasFunc(a, b string) bool { func notHasFunc(a, b string) bool {
return !strings.Contains(a, b) return !hasFunc(a, b)
} }
// startsWithFunc is condition for StartsWith operator. // startsWithFunc is condition for StartsWith operator.
...@@ -103,12 +107,24 @@ func startsWithFunc(a, b string) bool { ...@@ -103,12 +107,24 @@ func startsWithFunc(a, b string) bool {
return strings.HasPrefix(a, b) return strings.HasPrefix(a, b)
} }
// notStartsWithFunc is condition for NotStartsWith operator.
// It checks if b is not a prefix of a.
func notStartsWithFunc(a, b string) bool {
return !startsWithFunc(a, b)
}
// endsWithFunc is condition for EndsWith operator. // endsWithFunc is condition for EndsWith operator.
// It checks if b is a suffix of a. // It checks if b is a suffix of a.
func endsWithFunc(a, b string) bool { func endsWithFunc(a, b string) bool {
return strings.HasSuffix(a, b) return strings.HasSuffix(a, b)
} }
// notEndsWithFunc is condition for NotEndsWith operator.
// It checks if b is not a suffix of a.
func notEndsWithFunc(a, b string) bool {
return !endsWithFunc(a, b)
}
// matchFunc is condition for Match operator. // matchFunc is condition for Match operator.
// It does regexp matching of a against pattern in b // It does regexp matching of a against pattern in b
// and returns if they match. // and returns if they match.
...@@ -121,8 +137,7 @@ func matchFunc(a, b string) bool { ...@@ -121,8 +137,7 @@ func matchFunc(a, b string) bool {
// It does regexp matching of a against pattern in b // It does regexp matching of a against pattern in b
// and returns if they do not match. // and returns if they do not match.
func notMatchFunc(a, b string) bool { func notMatchFunc(a, b string) bool {
matched, _ := regexp.MatchString(b, a) return !matchFunc(a, b)
return !matched
} }
// ifCond is statement for a IfMatcher condition. // ifCond is statement for a IfMatcher condition.
......
...@@ -32,9 +32,15 @@ func TestConditions(t *testing.T) { ...@@ -32,9 +32,15 @@ func TestConditions(t *testing.T) {
{"bab starts_with bb", false}, {"bab starts_with bb", false},
{"bab starts_with ba", true}, {"bab starts_with ba", true},
{"bab starts_with bab", true}, {"bab starts_with bab", true},
{"bab not_starts_with bb", true},
{"bab not_starts_with ba", false},
{"bab not_starts_with bab", false},
{"bab ends_with bb", false}, {"bab ends_with bb", false},
{"bab ends_with bab", true}, {"bab ends_with bab", true},
{"bab ends_with ab", true}, {"bab ends_with ab", true},
{"bab not_ends_with bb", true},
{"bab not_ends_with ab", false},
{"bab not_ends_with bab", false},
{"a match *", false}, {"a match *", false},
{"a match a", true}, {"a match a", true},
{"a match .*", true}, {"a match .*", true},
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"sync" "sync"
"testing" "testing"
"gopkg.in/mcuadros/go-syslog.v2" syslog "gopkg.in/mcuadros/go-syslog.v2"
"gopkg.in/mcuadros/go-syslog.v2/format" "gopkg.in/mcuadros/go-syslog.v2/format"
) )
......
...@@ -326,24 +326,34 @@ func (info rawHelloInfo) looksLikeFirefox() bool { ...@@ -326,24 +326,34 @@ func (info rawHelloInfo) looksLikeFirefox() bool {
// EC point formats, and handshake compression methods." // EC point formats, and handshake compression methods."
// We check for the presence and order of the extensions. // We check for the presence and order of the extensions.
// Note: Sometimes padding (21) is present, sometimes not. // Note: Sometimes 0x15 (21, padding) is present, sometimes not.
// Note: Firefox 51+ does not advertise 0x3374 (13172, NPN). // Note: Firefox 51+ does not advertise 0x3374 (13172, NPN).
// Note: Firefox doesn't advertise 0x0 (0, SNI) when connecting to IP addresses. // Note: Firefox doesn't advertise 0x0 (0, SNI) when connecting to IP addresses.
requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 65283, 13} // Note: Firefox 55+ doesn't appear to advertise 0xFF03 (65283, short headers). It used to be between 5 and 13.
requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 13}
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) {
return false return false
} }
// We check for both presence of curves and their ordering. // We check for both presence of curves and their ordering.
expectedCurves := []tls.CurveID{29, 23, 24, 25} requiredCurves := []tls.CurveID{29, 23, 24, 25}
if len(info.curves) != len(expectedCurves) { if len(info.curves) < len(requiredCurves) {
return false return false
} }
for i := range expectedCurves { for i := range requiredCurves {
if info.curves[i] != expectedCurves[i] { if info.curves[i] != requiredCurves[i] {
return false return false
} }
} }
if len(info.curves) > len(requiredCurves) {
// newer Firefox (55 Nightly?) may have additional curves at end of list
allowedCurves := []tls.CurveID{256, 257}
for i := range allowedCurves {
if info.curves[len(requiredCurves)+i] != allowedCurves[i] {
return false
}
}
}
if hasGreaseCiphers(info.cipherSuites) { if hasGreaseCiphers(info.cipherSuites) {
return false return false
...@@ -353,6 +363,9 @@ func (info rawHelloInfo) looksLikeFirefox() bool { ...@@ -353,6 +363,9 @@ func (info rawHelloInfo) looksLikeFirefox() bool {
// according to the paper, cipher suites may be not be added // according to the paper, cipher suites may be not be added
// or reordered by the user, but they may be disabled. // or reordered by the user, but they may be disabled.
expectedCipherSuiteOrder := []uint16{ expectedCipherSuiteOrder := []uint16{
TLS_AES_128_GCM_SHA256, // 0x1301
TLS_CHACHA20_POLY1305_SHA256, // 0x1303
TLS_AES_256_GCM_SHA384, // 0x1302
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // 0xcca9 tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // 0xcca9
...@@ -402,7 +415,7 @@ func (info rawHelloInfo) looksLikeChrome() bool { ...@@ -402,7 +415,7 @@ func (info rawHelloInfo) looksLikeChrome() bool {
chromeCipherExclusions := map[uint16]struct{}{ chromeCipherExclusions := map[uint16]struct{}{
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: {}, // 0xc024 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: {}, // 0xc024
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {}, // 0xc023 tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: {}, // 0xc023
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: {}, // 0xc028 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: {}, // 0xc028
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: {}, // 0xc027 tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: {}, // 0xc027
TLS_RSA_WITH_AES_256_CBC_SHA256: {}, // 0x3d TLS_RSA_WITH_AES_256_CBC_SHA256: {}, // 0x3d
...@@ -511,7 +524,7 @@ func (info rawHelloInfo) looksLikeSafari() bool { ...@@ -511,7 +524,7 @@ func (info rawHelloInfo) looksLikeSafari() bool {
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, // 0xc024 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, // 0xc024
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // 0xc023 tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // 0xc023
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // 0xc00a tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // 0xc00a
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // 0xc009 tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // 0xc009
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030
...@@ -523,7 +536,7 @@ func (info rawHelloInfo) looksLikeSafari() bool { ...@@ -523,7 +536,7 @@ func (info rawHelloInfo) looksLikeSafari() bool {
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // 0x9d tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // 0x9d
tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // 0x9c tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // 0x9c
TLS_RSA_WITH_AES_256_CBC_SHA256, // 0x3d TLS_RSA_WITH_AES_256_CBC_SHA256, // 0x3d
TLS_RSA_WITH_AES_128_CBC_SHA256, // 0x3c tls.TLS_RSA_WITH_AES_128_CBC_SHA256, // 0x3c
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35 tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35
tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f
} }
...@@ -610,11 +623,17 @@ const ( ...@@ -610,11 +623,17 @@ const (
// cipher suites missing from the crypto/tls package, // cipher suites missing from the crypto/tls package,
// in no particular order here // in no particular order here
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc024 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc024
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xc023
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xc028 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xc028
TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x3c
TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x3d TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x3d
TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x33 TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x33
TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x39 TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x39
TLS_RSA_WITH_RC4_128_MD5 = 0x4 TLS_RSA_WITH_RC4_128_MD5 = 0x4
// new PSK ciphers introduced by TLS 1.3, not (yet) in crypto/tls
// https://tlswg.github.io/tls13-spec/#rfc.appendix.A.4)
TLS_AES_128_GCM_SHA256 = 0x1301
TLS_AES_256_GCM_SHA384 = 0x1302
TLS_CHACHA20_POLY1305_SHA256 = 0x1303
TLS_AES_128_CCM_SHA256 = 0x1304
TLS_AES_128_CCM_8_SHA256 = 0x1305
) )
...@@ -139,6 +139,22 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) { ...@@ -139,6 +139,22 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) {
helloHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`, helloHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`,
interception: false, interception: false,
}, },
{
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0",
helloHex: `010001fc0303c99d54ae0628bbb9fea3833a4244c6a712cac9d7738f4930b8b9d8e2f6bd578220f7936cedb48907981c9292fb08ceee6f59bd6fddb3d4271ccd7c12380c5038ab001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a01000195001500af000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b000201000023007886da2d41843ff42131b856982c19a545837b70e604325423a817d925e9d95bd084737682cea6b804dfb7cbe336a3b27b8d520d57520c29cfe5f4f3d3236183b84b05c18f0ca30bf598111e390086fea00d9631f1f78527277eb7838b86e73c4e5d15b55d086b1a4a8aa29f12a55126c6274bcd499bbeb23a0010000e000c02683208687474702f312e31000500050100000000000d0018001604030503060308040805080604010501060102030201`,
interception: false,
},
{
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0",
helloHex: `010000b1030365d899820b999245d571c2f7d6b850f63ad931d3c68ceb9cf5a508421a871dc500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100006a0000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0018001604030503060308040805080604010501060102030201`,
interception: false,
},
{
// this was a Nightly release at the time
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0",
helloHex: `010001fc030331e380b7d12018e1202ef3327607203df5c5732b4fa5ab5abaf0b60034c2fb662070c836b9b89123e37f4f1074d152df438fa8ee8a0f89b036fd952f4fcc0b994f001c130113031302c02bc02fcca9cca8c02cc030c013c014002f0035000a0100019700000014001200000f63616464797365727665722e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b0002010000230078c97e7716a041e2ea824571bef26a3dff2bf50a883cd15d904ab2d17deb514f6e0a079ee7c212c000178387ffafc2e530b6df6662f570aae134330f13c458a0eaad5a96a9696f572110918740b15db1143d19aaaa706942030b433a7e6150f62b443c0564e5b8f7ee9577bf3bf7faec8c67425b648ab54d880010000e000c02683208687474702f312e310005000501000000000028006b0069001d0020aee6e596155ee6f79f943e81ceabe0979d27fbbb8b9189ccb2ebc75226351f32001700410421875a44e510decac11ef1d7cfddd4dfe105d5cd3a2d42fba03ebde23e51e8ce65bda1b48be82d4848d1db2bfce68e94092e925a9ce0dbf5df35479558108489002b0009087f12030303020301000d0018001604030503060308040805080604010501060102030201002d000201010015002500000000000000000000000000000000000000000000000000000000000000000000000000`,
interception: false,
},
}, },
"Edge": { "Edge": {
{ {
......
...@@ -436,7 +436,7 @@ var directives = []string{ ...@@ -436,7 +436,7 @@ var directives = []string{
"root", "root",
"index", "index",
"bind", "bind",
"maxrequestbody", // TODO: 'limits' "limits",
"timeouts", "timeouts",
"tls", "tls",
...@@ -477,6 +477,7 @@ var directives = []string{ ...@@ -477,6 +477,7 @@ var directives = []string{
"pprof", "pprof",
"expvar", "expvar",
"push", "push",
"datadog", // github.com/payintech/caddy-datadog
"prometheus", // github.com/miekg/caddy-prometheus "prometheus", // github.com/miekg/caddy-prometheus
"proxy", "proxy",
"fastcgi", "fastcgi",
......
...@@ -302,7 +302,7 @@ func (r *replacer) getSubstitution(key string) string { ...@@ -302,7 +302,7 @@ func (r *replacer) getSubstitution(key string) string {
} }
_, err := ioutil.ReadAll(r.request.Body) _, err := ioutil.ReadAll(r.request.Body)
if err != nil { if err != nil {
if _, ok := err.(MaxBytesExceeded); ok { if err == ErrMaxBytesExceeded {
return r.emptyValue return r.emptyValue
} }
} }
......
...@@ -4,8 +4,8 @@ package httpserver ...@@ -4,8 +4,8 @@ package httpserver
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io"
"log" "log"
"net" "net"
"net/http" "net/http"
...@@ -66,6 +66,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) { ...@@ -66,6 +66,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
sites: group, sites: group,
connTimeout: GracefulTimeout, connTimeout: GracefulTimeout,
} }
s.Server = makeHTTPServerWithHeaderLimit(s.Server, group)
s.Server.Handler = s // this is weird, but whatever s.Server.Handler = s // this is weird, but whatever
// extract TLS settings from each site config to build // extract TLS settings from each site config to build
...@@ -127,6 +128,32 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) { ...@@ -127,6 +128,32 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
return s, nil return s, nil
} }
// makeHTTPServerWithHeaderLimit apply minimum header limit within a group to given http.Server
func makeHTTPServerWithHeaderLimit(s *http.Server, group []*SiteConfig) *http.Server {
var min int64
for _, cfg := range group {
limit := cfg.Limits.MaxRequestHeaderSize
if limit == 0 {
continue
}
// not set yet
if min == 0 {
min = limit
}
// find a better one
if limit < min {
min = limit
}
}
if min > 0 {
s.MaxHeaderBytes = int(min)
}
return s
}
// makeHTTPServerWithTimeouts makes an http.Server from the group of // makeHTTPServerWithTimeouts makes an http.Server from the group of
// configs in a way that configures timeouts (or, if not set, it uses // configs in a way that configures timeouts (or, if not set, it uses
// the default timeouts) by combining the configuration of each // the default timeouts) by combining the configuration of each
...@@ -359,20 +386,6 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error) ...@@ -359,20 +386,6 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
} }
} }
// Apply the path-based request body size limit
// The error returned by MaxBytesReader is meant to be handled
// by whichever middleware/plugin that receives it when calling
// .Read() or a similar method on the request body
// TODO: Make this middleware instead?
if r.Body != nil {
for _, pathlimit := range vhost.MaxRequestBodySizes {
if Path(r.URL.Path).Matches(pathlimit.Path) {
r.Body = MaxBytesReader(w, r.Body, pathlimit.Limit)
break
}
}
}
return vhost.middlewareChain.ServeHTTP(w, r) return vhost.middlewareChain.ServeHTTP(w, r)
} }
...@@ -465,73 +478,9 @@ func (ln tcpKeepAliveListener) File() (*os.File, error) { ...@@ -465,73 +478,9 @@ func (ln tcpKeepAliveListener) File() (*os.File, error) {
return ln.TCPListener.File() return ln.TCPListener.File()
} }
// MaxBytesExceeded is the error type returned by MaxBytesReader // ErrMaxBytesExceeded is the error returned by MaxBytesReader
// when the request body exceeds the limit imposed // when the request body exceeds the limit imposed
type MaxBytesExceeded struct{} var ErrMaxBytesExceeded = errors.New("http: request body too large")
func (err MaxBytesExceeded) Error() string {
return "http: request body too large"
}
// MaxBytesReader and its associated methods are borrowed from the
// Go Standard library (comments intact). The only difference is that
// it returns a MaxBytesExceeded error instead of a generic error message
// when the request body has exceeded the requested limit
func MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
return &maxBytesReader{w: w, r: r, n: n}
}
type maxBytesReader struct {
w http.ResponseWriter
r io.ReadCloser // underlying reader
n int64 // max bytes remaining
err error // sticky error
}
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
if l.err != nil {
return 0, l.err
}
if len(p) == 0 {
return 0, nil
}
// If they asked for a 32KB byte read but only 5 bytes are
// remaining, no need to read 32KB. 6 bytes will answer the
// question of the whether we hit the limit or go past it.
if int64(len(p)) > l.n+1 {
p = p[:l.n+1]
}
n, err = l.r.Read(p)
if int64(n) <= l.n {
l.n -= int64(n)
l.err = err
return n, err
}
n = int(l.n)
l.n = 0
// The server code and client code both use
// maxBytesReader. This "requestTooLarge" check is
// only used by the server code. To prevent binaries
// which only using the HTTP Client code (such as
// cmd/go) from also linking in the HTTP server, don't
// use a static type assertion to the server
// "*response" type. Check this interface instead:
type requestTooLarger interface {
requestTooLarge()
}
if res, ok := l.w.(requestTooLarger); ok {
res.requestTooLarge()
}
l.err = MaxBytesExceeded{}
return n, l.err
}
func (l *maxBytesReader) Close() error {
return l.r.Close()
}
// DefaultErrorFunc responds to an HTTP request with a simple description // DefaultErrorFunc responds to an HTTP request with a simple description
// of the specified HTTP status code. // of the specified HTTP status code.
......
...@@ -15,7 +15,7 @@ func TestAddress(t *testing.T) { ...@@ -15,7 +15,7 @@ func TestAddress(t *testing.T) {
} }
} }
func TestMakeHTTPServer(t *testing.T) { func TestMakeHTTPServerWithTimeouts(t *testing.T) {
for i, tc := range []struct { for i, tc := range []struct {
group []*SiteConfig group []*SiteConfig
expected Timeouts expected Timeouts
...@@ -111,3 +111,36 @@ func TestMakeHTTPServer(t *testing.T) { ...@@ -111,3 +111,36 @@ func TestMakeHTTPServer(t *testing.T) {
} }
} }
} }
func TestMakeHTTPServerWithHeaderLimit(t *testing.T) {
for name, c := range map[string]struct {
group []*SiteConfig
expect int
}{
"disable": {
group: []*SiteConfig{{}},
expect: 0,
},
"oneSite": {
group: []*SiteConfig{{Limits: Limits{
MaxRequestHeaderSize: 100,
}}},
expect: 100,
},
"multiSites": {
group: []*SiteConfig{
{Limits: Limits{MaxRequestHeaderSize: 100}},
{Limits: Limits{MaxRequestHeaderSize: 50}},
},
expect: 50,
},
} {
c := c
t.Run(name, func(t *testing.T) {
actual := makeHTTPServerWithHeaderLimit(&http.Server{}, c.group)
if got := actual.MaxHeaderBytes; got != c.expect {
t.Errorf("Expect %d, but got %d", c.expect, got)
}
})
}
}
...@@ -38,8 +38,8 @@ type SiteConfig struct { ...@@ -38,8 +38,8 @@ type SiteConfig struct {
// for a request. // for a request.
HiddenFiles []string HiddenFiles []string
// Max amount of bytes a request can send on a given path // Max request's header/body size
MaxRequestBodySizes []PathLimit Limits Limits
// The path to the Caddyfile used to generate this site config // The path to the Caddyfile used to generate this site config
originCaddyfile string originCaddyfile string
...@@ -71,6 +71,12 @@ type Timeouts struct { ...@@ -71,6 +71,12 @@ type Timeouts struct {
IdleTimeoutSet bool IdleTimeoutSet bool
} }
// Limits specify size limit of request's header and body.
type Limits struct {
MaxRequestHeaderSize int64
MaxRequestBodySizes []PathLimit
}
// PathLimit is a mapping from a site's path to its corresponding // PathLimit is a mapping from a site's path to its corresponding
// maximum request body size (in bytes) // maximum request body size (in bytes)
type PathLimit struct { type PathLimit struct {
......
package limits
import (
"io"
"net/http"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
// Limit is a middleware to control request body size
type Limit struct {
Next httpserver.Handler
BodyLimits []httpserver.PathLimit
}
func (l Limit) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
if r.Body == nil {
return l.Next.ServeHTTP(w, r)
}
// apply the path-based request body size limit.
for _, bl := range l.BodyLimits {
if httpserver.Path(r.URL.Path).Matches(bl.Path) {
r.Body = MaxBytesReader(w, r.Body, bl.Limit)
break
}
}
return l.Next.ServeHTTP(w, r)
}
// MaxBytesReader and its associated methods are borrowed from the
// Go Standard library (comments intact). The only difference is that
// it returns a ErrMaxBytesExceeded error instead of a generic error message
// when the request body has exceeded the requested limit
func MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
return &maxBytesReader{w: w, r: r, n: n}
}
type maxBytesReader struct {
w http.ResponseWriter
r io.ReadCloser // underlying reader
n int64 // max bytes remaining
err error // sticky error
}
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
if l.err != nil {
return 0, l.err
}
if len(p) == 0 {
return 0, nil
}
// If they asked for a 32KB byte read but only 5 bytes are
// remaining, no need to read 32KB. 6 bytes will answer the
// question of the whether we hit the limit or go past it.
if int64(len(p)) > l.n+1 {
p = p[:l.n+1]
}
n, err = l.r.Read(p)
if int64(n) <= l.n {
l.n -= int64(n)
l.err = err
return n, err
}
n = int(l.n)
l.n = 0
// The server code and client code both use
// maxBytesReader. This "requestTooLarge" check is
// only used by the server code. To prevent binaries
// which only using the HTTP Client code (such as
// cmd/go) from also linking in the HTTP server, don't
// use a static type assertion to the server
// "*response" type. Check this interface instead:
type requestTooLarger interface {
requestTooLarge()
}
if res, ok := l.w.(requestTooLarger); ok {
res.requestTooLarge()
}
l.err = httpserver.ErrMaxBytesExceeded
return n, l.err
}
func (l *maxBytesReader) Close() error {
return l.r.Close()
}
package limits
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestBodySizeLimit(t *testing.T) {
var (
gotContent []byte
gotError error
expectContent = "hello"
)
l := Limit{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
gotContent, gotError = ioutil.ReadAll(r.Body)
return 0, nil
}),
BodyLimits: []httpserver.PathLimit{{Path: "/", Limit: int64(len(expectContent))}},
}
r := httptest.NewRequest("GET", "/", strings.NewReader(expectContent+expectContent))
l.ServeHTTP(httptest.NewRecorder(), r)
if got := string(gotContent); got != expectContent {
t.Errorf("expected content[%s], got[%s]", expectContent, got)
}
if gotError != httpserver.ErrMaxBytesExceeded {
t.Errorf("expect error %v, got %v", httpserver.ErrMaxBytesExceeded, gotError)
}
}
package maxrequestbody package limits
import ( import (
"errors" "errors"
...@@ -12,13 +12,13 @@ import ( ...@@ -12,13 +12,13 @@ import (
const ( const (
serverType = "http" serverType = "http"
pluginName = "maxrequestbody" pluginName = "limits"
) )
func init() { func init() {
caddy.RegisterPlugin(pluginName, caddy.Plugin{ caddy.RegisterPlugin(pluginName, caddy.Plugin{
ServerType: serverType, ServerType: serverType,
Action: setupMaxRequestBody, Action: setupLimits,
}) })
} }
...@@ -28,56 +28,97 @@ type pathLimitUnparsed struct { ...@@ -28,56 +28,97 @@ type pathLimitUnparsed struct {
Limit string Limit string
} }
func setupMaxRequestBody(c *caddy.Controller) error { func setupLimits(c *caddy.Controller) error {
bls, err := parseLimits(c)
if err != nil {
return err
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return Limit{Next: next, BodyLimits: bls}
})
return nil
}
func parseLimits(c *caddy.Controller) ([]httpserver.PathLimit, error) {
config := httpserver.GetConfig(c) config := httpserver.GetConfig(c)
if !c.Next() { if !c.Next() {
return c.ArgErr() return nil, c.ArgErr()
} }
args := c.RemainingArgs() args := c.RemainingArgs()
argList := []pathLimitUnparsed{} argList := []pathLimitUnparsed{}
headerLimit := ""
switch len(args) { switch len(args) {
case 0: case 0:
// Format: { <path> <limit> ... } // Format: limits {
// header <limit>
// body <path> <limit>
// body <limit>
// ...
// }
for c.NextBlock() { for c.NextBlock() {
path := c.Val() kind := c.Val()
if !c.NextArg() { pathOrLimit := c.RemainingArgs()
// Uneven pairing of path/limit switch kind {
return c.ArgErr() case "header":
if len(pathOrLimit) != 1 {
return nil, c.ArgErr()
} }
headerLimit = pathOrLimit[0]
case "body":
if len(pathOrLimit) == 1 {
argList = append(argList, pathLimitUnparsed{ argList = append(argList, pathLimitUnparsed{
Path: path, Path: "/",
Limit: c.Val(), Limit: pathOrLimit[0],
})
break
}
if len(pathOrLimit) == 2 {
argList = append(argList, pathLimitUnparsed{
Path: pathOrLimit[0],
Limit: pathOrLimit[1],
}) })
break
}
fallthrough
default:
return nil, c.ArgErr()
}
} }
case 1: case 1:
// Format: <limit> // Format: limits <limit>
headerLimit = args[0]
argList = []pathLimitUnparsed{{ argList = []pathLimitUnparsed{{
Path: "/", Path: "/",
Limit: args[0], Limit: args[0],
}} }}
case 2:
// Format: <path> <limit>
argList = []pathLimitUnparsed{{
Path: args[0],
Limit: args[1],
}}
default: default:
return c.ArgErr() return nil, c.ArgErr()
} }
if headerLimit != "" {
size := parseSize(headerLimit)
if size < 1 { // also disallow size = 0
return nil, c.ArgErr()
}
config.Limits.MaxRequestHeaderSize = size
}
if len(argList) > 0 {
pathLimit, err := parseArguments(argList) pathLimit, err := parseArguments(argList)
if err != nil { if err != nil {
return c.ArgErr() return nil, c.ArgErr()
} }
SortPathLimits(pathLimit) SortPathLimits(pathLimit)
config.Limits.MaxRequestBodySizes = pathLimit
}
config.MaxRequestBodySizes = pathLimit return config.Limits.MaxRequestBodySizes, nil
return nil
} }
func parseArguments(args []pathLimitUnparsed) ([]httpserver.PathLimit, error) { func parseArguments(args []pathLimitUnparsed) ([]httpserver.PathLimit, error) {
......
package maxrequestbody package limits
import ( import (
"reflect" "reflect"
...@@ -14,32 +14,98 @@ const ( ...@@ -14,32 +14,98 @@ const (
GB = 1024 * 1024 * 1024 GB = 1024 * 1024 * 1024
) )
func TestSetupMaxRequestBody(t *testing.T) { func TestParseLimits(t *testing.T) {
cases := []struct { for name, c := range map[string]struct {
input string input string
hasError bool shouldErr bool
expect httpserver.Limits
}{ }{
// Format: { <path> <limit> ... } "catchAll": {
{input: "maxrequestbody / 20MB", hasError: false}, input: `limits 2kb`,
// Format: <limit> expect: httpserver.Limits{
{input: "maxrequestbody 999KB", hasError: false}, MaxRequestHeaderSize: 2 * KB,
// Format: { <path> <limit> ... } MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/", Limit: 2 * KB}},
{input: "maxrequestbody { /images 50MB /upload 10MB\n/test 10KB }", hasError: false}, },
},
// Wrong formats "onlyHeader": {
{input: "maxrequestbody typo { /images 50MB }", hasError: true}, input: `limits {
{input: "maxrequestbody 999MB /home 20KB", hasError: true}, header 2kb
} }`,
for caseNum, c := range cases { expect: httpserver.Limits{
MaxRequestHeaderSize: 2 * KB,
},
},
"onlyBody": {
input: `limits {
body 2kb
}`,
expect: httpserver.Limits{
MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/", Limit: 2 * KB}},
},
},
"onlyBodyWithPath": {
input: `limits {
body /test 2kb
}`,
expect: httpserver.Limits{
MaxRequestBodySizes: []httpserver.PathLimit{{Path: "/test", Limit: 2 * KB}},
},
},
"mixture": {
input: `limits {
header 1kb
body 2kb
body /bar 3kb
}`,
expect: httpserver.Limits{
MaxRequestHeaderSize: 1 * KB,
MaxRequestBodySizes: []httpserver.PathLimit{
{Path: "/bar", Limit: 3 * KB},
{Path: "/", Limit: 2 * KB},
},
},
},
"invalidFormat": {
input: `limits a b`,
shouldErr: true,
},
"invalidHeaderFormat": {
input: `limits {
header / 100
}`,
shouldErr: true,
},
"invalidBodyFormat": {
input: `limits {
body / 100 200
}`,
shouldErr: true,
},
"invalidKind": {
input: `limits {
head 100
}`,
shouldErr: true,
},
"invalidLimitSize": {
input: `limits 10bk`,
shouldErr: true,
},
} {
c := c
t.Run(name, func(t *testing.T) {
controller := caddy.NewTestController("", c.input) controller := caddy.NewTestController("", c.input)
err := setupMaxRequestBody(controller) _, err := parseLimits(controller)
if c.shouldErr && err == nil {
if c.hasError && (err == nil) { t.Error("failed to get expected error")
t.Errorf("Expecting error for case %v but none encountered", caseNum)
} }
if !c.hasError && (err != nil) { if !c.shouldErr && err != nil {
t.Errorf("Expecting no error for case %v but encountered %v", caseNum, err) t.Errorf("got unexpected error: %v", err)
}
if got := httpserver.GetConfig(controller).Limits; !reflect.DeepEqual(got, c.expect) {
t.Errorf("expect %#v, but got %#v", c.expect, got)
} }
})
} }
} }
......
package log package log
import ( import (
"strings"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
...@@ -51,49 +53,37 @@ func logParse(c *caddy.Controller) ([]*Rule, error) { ...@@ -51,49 +53,37 @@ func logParse(c *caddy.Controller) ([]*Rule, error) {
} }
} }
if len(args) == 0 { path := "/"
// Nothing specified; use defaults
rules = appendEntry(rules, "/", &Entry{
Log: &httpserver.Logger{
Output: DefaultLogFilename,
Roller: logRoller,
},
Format: DefaultLogFormat,
})
} else if len(args) == 1 {
// Only an output file specified
rules = appendEntry(rules, "/", &Entry{
Log: &httpserver.Logger{
Output: args[0],
Roller: logRoller,
},
Format: DefaultLogFormat,
})
} else {
// Path scope, output file, and maybe a format specified
format := DefaultLogFormat format := DefaultLogFormat
output := DefaultLogFilename
switch len(args) {
case 0:
// nothing to change
case 1:
// Only an output file specified
output = args[0]
case 2, 3:
// Path scope, output file, and maybe a format specified
path = args[0]
output = args[1]
if len(args) > 2 { if len(args) > 2 {
switch args[2] { format = strings.Replace(args[2], "{common}", CommonLogFormat, -1)
case "{common}": format = strings.Replace(format, "{combined}", CombinedLogFormat, -1)
format = CommonLogFormat
case "{combined}":
format = CombinedLogFormat
default:
format = args[2]
} }
default:
// Maximum number of args in log directive is 3.
return nil, c.ArgErr()
} }
rules = appendEntry(rules, args[0], &Entry{ rules = appendEntry(rules, path, &Entry{
Log: &httpserver.Logger{ Log: &httpserver.Logger{
Output: args[1], Output: output,
Roller: logRoller, Roller: logRoller,
}, },
Format: format, Format: format,
}) })
} }
}
return rules, nil return rules, nil
} }
......
...@@ -124,6 +124,16 @@ func TestLogParse(t *testing.T) { ...@@ -124,6 +124,16 @@ func TestLogParse(t *testing.T) {
Format: CommonLogFormat, Format: CommonLogFormat,
}}, }},
}}}, }}},
{`log /myapi log.txt "prefix {common} suffix"`, false, []Rule{{
PathScope: "/myapi",
Entries: []*Entry{{
Log: &httpserver.Logger{
Output: "log.txt",
Roller: httpserver.DefaultLogRoller(),
},
Format: "prefix " + CommonLogFormat + " suffix",
}},
}}},
{`log /test accesslog.txt {combined}`, false, []Rule{{ {`log /test accesslog.txt {combined}`, false, []Rule{{
PathScope: "/test", PathScope: "/test",
Entries: []*Entry{{ Entries: []*Entry{{
...@@ -134,6 +144,16 @@ func TestLogParse(t *testing.T) { ...@@ -134,6 +144,16 @@ func TestLogParse(t *testing.T) {
Format: CombinedLogFormat, Format: CombinedLogFormat,
}}, }},
}}}, }}},
{`log /test accesslog.txt "prefix {combined} suffix"`, false, []Rule{{
PathScope: "/test",
Entries: []*Entry{{
Log: &httpserver.Logger{
Output: "accesslog.txt",
Roller: httpserver.DefaultLogRoller(),
},
Format: "prefix " + CombinedLogFormat + " suffix",
}},
}}},
{`log /api1 log.txt {`log /api1 log.txt
log /api2 accesslog.txt {combined}`, false, []Rule{{ log /api2 accesslog.txt {combined}`, false, []Rule{{
PathScope: "/api1", PathScope: "/api1",
...@@ -207,6 +227,7 @@ func TestLogParse(t *testing.T) { ...@@ -207,6 +227,7 @@ func TestLogParse(t *testing.T) {
}}}, }}},
{`log access.log { rotate_size }`, true, nil}, {`log access.log { rotate_size }`, true, nil},
{`log access.log { invalid_option 1 }`, true, nil}, {`log access.log { invalid_option 1 }`, true, nil},
{`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil},
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("http", test.inputLogRules) c := caddy.NewTestController("http", test.inputLogRules)
......
...@@ -22,7 +22,8 @@ type Data struct { ...@@ -22,7 +22,8 @@ type Data struct {
// Include "overrides" the embedded httpserver.Context's Include() // Include "overrides" the embedded httpserver.Context's Include()
// method so that included files have access to d's fields. // method so that included files have access to d's fields.
// Note: using {{template 'template-name' .}} instead might be better. // Note: using {{template 'template-name' .}} instead might be better.
func (d Data) Include(filename string) (string, error) { func (d Data) Include(filename string, args ...interface{}) (string, error) {
d.Args = args
return httpserver.ContextInclude(filename, d, d.Root) return httpserver.ContextInclude(filename, d, d.Root)
} }
......
...@@ -23,6 +23,7 @@ func init() { ...@@ -23,6 +23,7 @@ func init() {
RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} }) RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} })
RegisterPolicy("ip_hash", func() Policy { return &IPHash{} }) RegisterPolicy("ip_hash", func() Policy { return &IPHash{} })
RegisterPolicy("first", func() Policy { return &First{} }) RegisterPolicy("first", func() Policy { return &First{} })
RegisterPolicy("uri_hash", func() Policy { return &URIHash{} })
} }
// Random is a policy that selects up hosts from a pool at random. // Random is a policy that selects up hosts from a pool at random.
...@@ -84,13 +85,13 @@ func (r *LeastConn) Select(pool HostPool, request *http.Request) *UpstreamHost { ...@@ -84,13 +85,13 @@ func (r *LeastConn) Select(pool HostPool, request *http.Request) *UpstreamHost {
return bestHost return bestHost
} }
// RoundRobin is a policy that selects hosts based on round robin ordering. // RoundRobin is a policy that selects hosts based on round-robin ordering.
type RoundRobin struct { type RoundRobin struct {
robin uint32 robin uint32
mutex sync.Mutex mutex sync.Mutex
} }
// Select selects an up host from the pool using a round robin ordering scheme. // Select selects an up host from the pool using a round-robin ordering scheme.
func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost { func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost {
poolLen := uint32(len(pool)) poolLen := uint32(len(pool))
r.mutex.Lock() r.mutex.Lock()
...@@ -106,37 +107,51 @@ func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost ...@@ -106,37 +107,51 @@ func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost
return nil return nil
} }
// IPHash is a policy that selects hosts based on hashing the request ip // hostByHashing returns an available host from pool based on a hashable string
type IPHash struct{} func hostByHashing(pool HostPool, s string) *UpstreamHost {
poolLen := uint32(len(pool))
index := hash(s) % poolLen
for i := uint32(0); i < poolLen; i++ {
index += i
host := pool[index%poolLen]
if host.Available() {
return host
}
}
return nil
}
// hash calculates a hash based on string s
func hash(s string) uint32 { func hash(s string) uint32 {
h := fnv.New32a() h := fnv.New32a()
h.Write([]byte(s)) h.Write([]byte(s))
return h.Sum32() return h.Sum32()
} }
// Select selects an up host from the pool using a round robin ordering scheme. // IPHash is a policy that selects hosts based on hashing the request IP
type IPHash struct{}
// Select selects an up host from the pool based on hashing the request IP
func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost { func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
poolLen := uint32(len(pool))
clientIP, _, err := net.SplitHostPort(request.RemoteAddr) clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
if err != nil { if err != nil {
clientIP = request.RemoteAddr clientIP = request.RemoteAddr
} }
index := hash(clientIP) % poolLen return hostByHashing(pool, clientIP)
for i := uint32(0); i < poolLen; i++ { }
index += i
host := pool[index%poolLen] // URIHash is a policy that selects the host based on hashing the request URI
if host.Available() { type URIHash struct{}
return host
} // Select selects the host based on hashing the URI
} func (r *URIHash) Select(pool HostPool, request *http.Request) *UpstreamHost {
return nil return hostByHashing(pool, request.RequestURI)
} }
// First is a policy that selects the fist available host // First is a policy that selects the first available host
type First struct{} type First struct{}
// Select selects the first host from the pool, that is available // Select selects the first available host from the pool
func (r *First) Select(pool HostPool, request *http.Request) *UpstreamHost { func (r *First) Select(pool HostPool, request *http.Request) *UpstreamHost {
for _, host := range pool { for _, host := range pool {
if host.Available() { if host.Available() {
......
...@@ -243,3 +243,62 @@ func TestFirstPolicy(t *testing.T) { ...@@ -243,3 +243,62 @@ func TestFirstPolicy(t *testing.T) {
t.Error("Expected first policy host to be the second host.") t.Error("Expected first policy host to be the second host.")
} }
} }
func TestUriPolicy(t *testing.T) {
pool := testPool()
uriPolicy := &URIHash{}
request := httptest.NewRequest(http.MethodGet, "/test", nil)
h := uriPolicy.Select(pool, request)
if h != pool[0] {
t.Error("Expected uri policy host to be the first host.")
}
pool[0].Unhealthy = 1
h = uriPolicy.Select(pool, request)
if h != pool[1] {
t.Error("Expected uri policy host to be the first host.")
}
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
h = uriPolicy.Select(pool, request)
if h != pool[1] {
t.Error("Expected uri policy host to be the second host.")
}
// We should be able to resize the host pool and still be able to predict
// where a request will be routed with the same URI's used above
pool = []*UpstreamHost{
{
Name: workableServer.URL, // this should resolve (healthcheck test)
},
{
Name: "http://localhost:99998", // this shouldn't
},
}
request = httptest.NewRequest(http.MethodGet, "/test", nil)
h = uriPolicy.Select(pool, request)
if h != pool[0] {
t.Error("Expected uri policy host to be the first host.")
}
pool[0].Unhealthy = 1
h = uriPolicy.Select(pool, request)
if h != pool[1] {
t.Error("Expected uri policy host to be the first host.")
}
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
h = uriPolicy.Select(pool, request)
if h != pool[1] {
t.Error("Expected uri policy host to be the second host.")
}
pool[0].Unhealthy = 1
pool[1].Unhealthy = 1
h = uriPolicy.Select(pool, request)
if h != nil {
t.Error("Expected uri policy policy host to be nil.")
}
}
...@@ -228,7 +228,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { ...@@ -228,7 +228,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
return 0, nil return 0, nil
} }
if _, ok := backendErr.(httpserver.MaxBytesExceeded); ok { if backendErr == httpserver.ErrMaxBytesExceeded {
return http.StatusRequestEntityTooLarge, backendErr return http.StatusRequestEntityTooLarge, backendErr
} }
......
...@@ -44,32 +44,62 @@ func TestReverseProxy(t *testing.T) { ...@@ -44,32 +44,62 @@ func TestReverseProxy(t *testing.T) {
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr) defer log.SetOutput(os.Stderr)
verifyHeaders := func(headers http.Header, trailers http.Header) { testHeaderValue := []string{"header-value"}
if headers.Get("X-Header") != "header-value" { testHeaders := http.Header{
t.Error("Expected header 'X-Header' to be proxied properly") "X-Header-1": testHeaderValue,
"X-Header-2": testHeaderValue,
"X-Header-3": testHeaderValue,
}
testTrailerValue := []string{"trailer-value"}
testTrailers := http.Header{
"X-Trailer-1": testTrailerValue,
"X-Trailer-2": testTrailerValue,
"X-Trailer-3": testTrailerValue,
}
verifyHeaderValues := func(actual http.Header, expected http.Header) bool {
if actual == nil {
t.Error("Expected headers")
return true
} }
if trailers == nil { for k := range expected {
t.Error("Expected to receive trailers") if expected.Get(k) != actual.Get(k) {
t.Errorf("Expected header '%s' to be proxied properly", k)
return true
} }
if trailers.Get("X-Trailer") != "trailer-value" { }
t.Error("Expected header 'X-Trailer' to be proxied properly")
return false
}
verifyHeadersTrailers := func(headers http.Header, trailers http.Header) {
if verifyHeaderValues(headers, testHeaders) || verifyHeaderValues(trailers, testTrailers) {
t.FailNow()
} }
} }
var requestReceived bool requestReceived := false
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// read the body (even if it's empty) to make Go parse trailers // read the body (even if it's empty) to make Go parse trailers
io.Copy(ioutil.Discard, r.Body) io.Copy(ioutil.Discard, r.Body)
verifyHeaders(r.Header, r.Trailer)
verifyHeadersTrailers(r.Header, r.Trailer)
requestReceived = true requestReceived = true
w.Header().Set("Trailer", "X-Trailer") // Set headers.
w.Header().Set("X-Header", "header-value") copyHeader(w.Header(), testHeaders)
// Only announce one of the trailers to test wether
// unannounced trailers are proxied correctly.
for k := range testTrailers {
w.Header().Set("Trailer", k)
break
}
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, client")) w.Write([]byte("Hello, client"))
w.Header().Set("X-Trailer", "trailer-value")
// Set trailers.
shallowCopyTrailers(w.Header(), testTrailers, true)
})) }))
defer backend.Close() defer backend.Close()
...@@ -79,24 +109,37 @@ func TestReverseProxy(t *testing.T) { ...@@ -79,24 +109,37 @@ func TestReverseProxy(t *testing.T) {
Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, Upstreams: []Upstream{newFakeUpstream(backend.URL, false)},
} }
// create request and response recorder // Create the fake request body.
r := httptest.NewRequest("GET", "/", strings.NewReader("test")) // This will copy "trailersToSet" to r.Trailer right before it is closed and
w := httptest.NewRecorder() // thus test for us wether unannounced client trailers are proxied correctly.
body := &trailerTestStringReader{
Reader: *strings.NewReader("test"),
trailersToSet: testTrailers,
}
// Create the fake request with the above body.
r := httptest.NewRequest("GET", "/", body)
r.Trailer = make(http.Header)
body.request = r
r.ContentLength = -1 // force chunked encoding (required for trailers) copyHeader(r.Header, testHeaders)
r.Header.Set("X-Header", "header-value")
r.Trailer = map[string][]string{ // Only announce one of the trailers to test wether
"X-Trailer": {"trailer-value"}, // unannounced trailers are proxied correctly.
for k, v := range testTrailers {
r.Trailer[k] = v
break
} }
w := httptest.NewRecorder()
p.ServeHTTP(w, r) p.ServeHTTP(w, r)
res := w.Result()
if !requestReceived { if !requestReceived {
t.Error("Expected backend to receive request, but it didn't") t.Error("Expected backend to receive request, but it didn't")
} }
res := w.Result() verifyHeadersTrailers(res.Header, res.Trailer)
verifyHeaders(res.Header, res.Trailer)
// Make sure {upstream} placeholder is set // Make sure {upstream} placeholder is set
r.Body = ioutil.NopCloser(strings.NewReader("test")) r.Body = ioutil.NopCloser(strings.NewReader("test"))
...@@ -112,6 +155,21 @@ func TestReverseProxy(t *testing.T) { ...@@ -112,6 +155,21 @@ func TestReverseProxy(t *testing.T) {
} }
} }
// trailerTestStringReader is used to test unannounced trailers coming
// from a client which should properly be proxied to the upstream.
type trailerTestStringReader struct {
strings.Reader
request *http.Request
trailersToSet http.Header
}
var _ io.ReadCloser = &trailerTestStringReader{}
func (r *trailerTestStringReader) Close() error {
copyHeader(r.request.Trailer, r.trailersToSet)
return nil
}
func TestReverseProxyInsecureSkipVerify(t *testing.T) { func TestReverseProxyInsecureSkipVerify(t *testing.T) {
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr) defer log.SetOutput(os.Stderr)
...@@ -247,8 +305,8 @@ func TestWebSocketReverseProxyNonHijackerPanic(t *testing.T) { ...@@ -247,8 +305,8 @@ func TestWebSocketReverseProxyNonHijackerPanic(t *testing.T) {
func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) { func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) {
// No-op websocket backend simply allows the WS connection to be // No-op websocket backend simply allows the WS connection to be
// accepted then it will be immediately closed. Perfect for testing. // accepted then it will be immediately closed. Perfect for testing.
var connCount int32 accepted := make(chan struct{})
wsNop := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { atomic.AddInt32(&connCount, 1) })) wsNop := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { close(accepted) }))
defer wsNop.Close() defer wsNop.Close()
// Get proxy to use for the test // Get proxy to use for the test
...@@ -279,8 +337,14 @@ func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) { ...@@ -279,8 +337,14 @@ func TestWebSocketReverseProxyServeHTTPHandler(t *testing.T) {
if !bytes.Equal(actual, expected) { if !bytes.Equal(actual, expected) {
t.Errorf("Expected backend to accept response:\n'%s'\nActually got:\n'%s'", expected, actual) t.Errorf("Expected backend to accept response:\n'%s'\nActually got:\n'%s'", expected, actual)
} }
if got, want := atomic.LoadInt32(&connCount), int32(1); got != want {
t.Errorf("Expected %d websocket connection, got %d", want, got) // wait a minute for backend handling, see issue 1654.
time.Sleep(10 * time.Millisecond)
select {
case <-accepted:
default:
t.Error("Expect a accepted websocket connection, but not")
} }
} }
......
...@@ -318,30 +318,61 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request, ...@@ -318,30 +318,61 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request,
} }
pooledIoCopy(backendConn, conn) pooledIoCopy(backendConn, conn)
} else { } else {
// NOTE:
// Closing the Body involves acquiring a mutex, which is a
// unnecessarily heavy operation, considering that this defer will
// pretty much never be executed with the Body still unclosed.
bodyOpen := true
closeBody := func() {
if bodyOpen {
res.Body.Close()
bodyOpen = false
}
}
defer closeBody()
// Copy all headers over.
// res.Header does not include the "Trailer" header,
// which means we will have to do that manually below.
copyHeader(rw.Header(), res.Header) copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response, // The "Trailer" header isn't included in res' Header map, which
// at least for *http.Transport. Build it up from Trailer. // is why we have to build one ourselves from res.Trailer.
if len(res.Trailer) > 0 { //
trailerKeys := make([]string, 0, len(res.Trailer)) // But res.Trailer does not necessarily contain all trailer keys at this
// point yet. The HTTP spec allows one to send "unannounced trailers"
// after a request and certain systems like gRPC make use of that.
announcedTrailerKeyCount := len(res.Trailer)
if announcedTrailerKeyCount > 0 {
vv := make([]string, 0, announcedTrailerKeyCount)
for k := range res.Trailer { for k := range res.Trailer {
trailerKeys = append(trailerKeys, k) vv = append(vv, k)
} }
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) rw.Header()["Trailer"] = vv
} }
// Now copy over the status code as well as the response body.
rw.WriteHeader(res.StatusCode) rw.WriteHeader(res.StatusCode)
if len(res.Trailer) > 0 { if announcedTrailerKeyCount > 0 {
// Force chunking if we saw a response trailer. // Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short // This prevents net/http from calculating the length
// bodies and adding a Content-Length. // for short bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok { if fl, ok := rw.(http.Flusher); ok {
fl.Flush() fl.Flush()
} }
} }
rp.copyResponse(rw, res.Body) rp.copyResponse(rw, res.Body)
res.Body.Close() // close now, instead of defer, to populate res.Trailer
copyHeader(rw.Header(), res.Trailer) // Now close the body to fully populate res.Trailer.
closeBody()
// Since Go does not remove keys from res.Trailer we
// can safely do a length comparison to check wether
// we received further, unannounced trailers.
//
// Most of the time forceSetTrailers should be false.
forceSetTrailers := len(res.Trailer) != announcedTrailerKeyCount
shallowCopyTrailers(rw.Header(), res.Trailer, forceSetTrailers)
} }
return nil return nil
...@@ -391,6 +422,22 @@ func copyHeader(dst, src http.Header) { ...@@ -391,6 +422,22 @@ func copyHeader(dst, src http.Header) {
} }
} }
// shallowCopyTrailers copies all headers from srcTrailer to dstHeader.
//
// If forceSetTrailers is set to true, the http.TrailerPrefix will be added to
// all srcTrailer key names. Otherwise the Go stdlib will ignore all keys
// which weren't listed in the Trailer map before submitting the Response.
//
// WARNING: Only a shallow copy will be created!
func shallowCopyTrailers(dstHeader, srcTrailer http.Header, forceSetTrailers bool) {
for k, vv := range srcTrailer {
if forceSetTrailers {
k = http.TrailerPrefix + k
}
dstHeader[k] = vv
}
}
// Hop-by-hop headers. These are removed when sent to the backend. // Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{ var hopHeaders = []string{
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
...@@ -42,6 +43,7 @@ type staticUpstream struct { ...@@ -42,6 +43,7 @@ type staticUpstream struct {
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
Host string Host string
Port string
} }
WithoutPathPrefix string WithoutPathPrefix string
IgnoredSubPaths []string IgnoredSubPaths []string
...@@ -321,6 +323,20 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { ...@@ -321,6 +323,20 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
return err return err
} }
u.HealthCheck.Timeout = dur u.HealthCheck.Timeout = dur
case "health_check_port":
if !c.NextArg() {
return c.ArgErr()
}
port := c.Val()
n, err := strconv.Atoi(port)
if err != nil {
return err
}
if n < 0 {
return c.Errf("invalid health_check_port '%s'", port)
}
u.HealthCheck.Port = port
case "header_upstream": case "header_upstream":
var header, value string var header, value string
if !c.Args(&header, &value) { if !c.Args(&header, &value) {
...@@ -380,7 +396,12 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { ...@@ -380,7 +396,12 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
func (u *staticUpstream) healthCheck() { func (u *staticUpstream) healthCheck() {
for _, host := range u.Hosts { for _, host := range u.Hosts {
hostURL := host.Name + u.HealthCheck.Path hostURL := host.Name
if u.HealthCheck.Port != "" {
hostURL = replacePort(host.Name, u.HealthCheck.Port)
}
hostURL += u.HealthCheck.Path
var unhealthy bool var unhealthy bool
// set up request, needed to be able to modify headers // set up request, needed to be able to modify headers
...@@ -483,3 +504,19 @@ func (u *staticUpstream) Stop() error { ...@@ -483,3 +504,19 @@ func (u *staticUpstream) Stop() error {
func RegisterPolicy(name string, policy func() Policy) { func RegisterPolicy(name string, policy func() Policy) {
supportedPolicies[name] = policy supportedPolicies[name] = policy
} }
func replacePort(originalURL string, newPort string) string {
parsedURL, err := url.Parse(originalURL)
if err != nil {
return originalURL
}
// handles 'localhost' and 'localhost:8080'
parsedHost, _, err := net.SplitHostPort(parsedURL.Host)
if err != nil {
parsedHost = parsedURL.Host
}
parsedURL.Host = net.JoinHostPort(parsedHost, newPort)
return parsedURL.String()
}
...@@ -2,6 +2,7 @@ package proxy ...@@ -2,6 +2,7 @@ package proxy
import ( import (
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
...@@ -375,3 +376,75 @@ func TestHealthCheckHost(t *testing.T) { ...@@ -375,3 +376,75 @@ func TestHealthCheckHost(t *testing.T) {
} }
} }
} }
func TestHealthCheckPort(t *testing.T) {
var counter int64
healthCounter := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body.Close()
atomic.AddInt64(&counter, 1)
}))
_, healthPort, err := net.SplitHostPort(healthCounter.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
defer healthCounter.Close()
tests := []struct {
config string
}{
// Test #1: upstream with port
{"proxy / localhost:8080 {\n health_check / health_check_port " + healthPort + "\n}"},
// Test #2: upstream without port (default to 80)
{"proxy / localhost {\n health_check / health_check_port " + healthPort + "\n}"},
}
for i, test := range tests {
counterValueAtStart := atomic.LoadInt64(&counter)
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "")
if err != nil {
t.Error("Expected no error. Got:", err.Error())
}
// Give some time for healthchecks to hit the server.
time.Sleep(500 * time.Millisecond)
for _, upstream := range upstreams {
if err := upstream.Stop(); err != nil {
t.Errorf("Test %d: Expected no error stopping upstream. Got: %v", i, err.Error())
}
}
counterValueAfterShutdown := atomic.LoadInt64(&counter)
if counterValueAfterShutdown == counterValueAtStart {
t.Errorf("Test %d: Expected healthchecks to hit test server. Got no healthchecks.", i)
}
}
t.Run("valid_port", func(t *testing.T) {
tests := []struct {
config string
}{
// Test #1: invalid port (nil)
{"proxy / localhost {\n health_check / health_check_port\n}"},
// Test #2: invalid port (string)
{"proxy / localhost {\n health_check / health_check_port abc\n}"},
// Test #3: invalid port (negative)
{"proxy / localhost {\n health_check / health_check_port -1\n}"},
}
for i, test := range tests {
_, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "")
if err == nil {
t.Errorf("Test %d accepted invalid config", i)
}
}
})
}
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/codahale/aesnicheck"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
) )
...@@ -294,7 +295,7 @@ func (c *Config) buildStandardTLSConfig() error { ...@@ -294,7 +295,7 @@ func (c *Config) buildStandardTLSConfig() error {
// default cipher suites // default cipher suites
if len(config.CipherSuites) == 0 { if len(config.CipherSuites) == 0 {
config.CipherSuites = defaultCiphers config.CipherSuites = getPreferredDefaultCiphers()
} }
// for security, ensure TLS_FALLBACK_SCSV is always included first // for security, ensure TLS_FALLBACK_SCSV is always included first
...@@ -380,7 +381,7 @@ func RegisterConfigGetter(serverType string, fn ConfigGetter) { ...@@ -380,7 +381,7 @@ func RegisterConfigGetter(serverType string, fn ConfigGetter) {
func SetDefaultTLSParams(config *Config) { func SetDefaultTLSParams(config *Config) {
// If no ciphers provided, use default list // If no ciphers provided, use default list
if len(config.Ciphers) == 0 { if len(config.Ciphers) == 0 {
config.Ciphers = defaultCiphers config.Ciphers = getPreferredDefaultCiphers()
} }
// Not a cipher suite, but still important for mitigating protocol downgrade attacks // Not a cipher suite, but still important for mitigating protocol downgrade attacks
...@@ -464,6 +465,35 @@ var defaultCiphers = []uint16{ ...@@ -464,6 +465,35 @@ var defaultCiphers = []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA,
} }
// List of ciphers we should prefer if native AESNI support is missing
var defaultCiphersNonAESNI = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
}
// getPreferredDefaultCiphers returns an appropriate cipher suite to use, depending on
// the hardware support available for AES-NI.
//
// See https://github.com/mholt/caddy/issues/1674
func getPreferredDefaultCiphers() []uint16 {
if aesnicheck.HasAESNI() {
return defaultCiphers
}
// Return a cipher suite that prefers ChaCha20
return defaultCiphersNonAESNI
}
// Map of supported curves // Map of supported curves
// https://golang.org/pkg/crypto/tls/#CurveID // https://golang.org/pkg/crypto/tls/#CurveID
var supportedCurvesMap = map[string]tls.CurveID{ var supportedCurvesMap = map[string]tls.CurveID{
......
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"testing" "testing"
"github.com/codahale/aesnicheck"
) )
func TestConvertTLSConfigProtocolVersions(t *testing.T) { func TestConvertTLSConfigProtocolVersions(t *testing.T) {
...@@ -60,10 +62,11 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) { ...@@ -60,10 +62,11 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) {
{Enabled: true, Ciphers: nil}, {Enabled: true, Ciphers: nil},
} }
defaultCiphersExpected := getPreferredDefaultCiphers()
expectedCiphers := [][]uint16{ expectedCiphers := [][]uint16{
{tls.TLS_FALLBACK_SCSV, 0xc02c, 0xc030}, {tls.TLS_FALLBACK_SCSV, 0xc02c, 0xc030},
{tls.TLS_FALLBACK_SCSV, 0xc012, 0xc030, 0xc00a}, {tls.TLS_FALLBACK_SCSV, 0xc012, 0xc030, 0xc00a},
append([]uint16{tls.TLS_FALLBACK_SCSV}, defaultCiphers...), append([]uint16{tls.TLS_FALLBACK_SCSV}, defaultCiphersExpected...),
} }
for i, config := range configs { for i, config := range configs {
...@@ -79,6 +82,21 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) { ...@@ -79,6 +82,21 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) {
} }
} }
func TestGetPreferredDefaultCiphers(t *testing.T) {
expectedCiphers := defaultCiphers
if !aesnicheck.HasAESNI() {
expectedCiphers = defaultCiphersNonAESNI
}
// Ensure ordering is correct and ciphers are what we expected.
result := getPreferredDefaultCiphers()
for i, actual := range result {
if actual != expectedCiphers[i] {
t.Errorf("Expected cipher in position %d to be %0x, got %0x", i, expectedCiphers[i], actual)
}
}
}
func TestStorageForNoURL(t *testing.T) { func TestStorageForNoURL(t *testing.T) {
c := &Config{} c := &Config{}
if _, err := c.StorageFor(""); err == nil { if _, err := c.StorageFor(""); err == nil {
......
...@@ -25,6 +25,13 @@ const ( ...@@ -25,6 +25,13 @@ const (
// RenewDurationBefore is how long before expiration to renew certificates. // RenewDurationBefore is how long before expiration to renew certificates.
RenewDurationBefore = (24 * time.Hour) * 30 RenewDurationBefore = (24 * time.Hour) * 30
// RenewDurationBeforeAtStartup is how long before expiration to require
// a renewed certificate when the process is first starting up (see #1680).
// A wider window between RenewDurationBefore and this value will allow
// Caddy to start under duress but hopefully this duration will give it
// enough time for the blockage to be relieved.
RenewDurationBeforeAtStartup = (24 * time.Hour) * 7
// OCSPInterval is how often to check if OCSP stapling needs updating. // OCSPInterval is how often to check if OCSP stapling needs updating.
OCSPInterval = 1 * time.Hour OCSPInterval = 1 * time.Hour
) )
...@@ -126,14 +133,18 @@ func RenewManagedCertificates(allowPrompts bool) (err error) { ...@@ -126,14 +133,18 @@ func RenewManagedCertificates(allowPrompts bool) (err error) {
err := cert.Config.RenewCert(renewName, allowPrompts) err := cert.Config.RenewCert(renewName, allowPrompts)
if err != nil { if err != nil {
if allowPrompts { if allowPrompts {
// Certificate renewal failed and the operator is present; we should stop // Certificate renewal failed and the operator is present. See a discussion
// immediately and return the error. See a discussion in issue 642 // about this in issue 642. For a while, we only stopped if the certificate
// about this. For a while, we only stopped if the certificate was // was expired, but in reality, there is no difference between reporting
// expired, but in reality, there is no difference between reporting // it now versus later, except that there's somebody present to deal with
// it now versus later, except that there's somebody present to deal // it right now.
// with it now, so require it. timeLeft := cert.NotAfter.Sub(time.Now().UTC())
if timeLeft < RenewDurationBeforeAtStartup {
// See issue 1680. Only fail at startup if the certificate is dangerously
// close to expiration.
return err return err
} }
}
log.Printf("[ERROR] %v", err) log.Printf("[ERROR] %v", err)
if cert.Config.OnDemand { if cert.Config.OnDemand {
deleteQueue = append(deleteQueue, cert) deleteQueue = append(deleteQueue, cert)
......
...@@ -58,21 +58,7 @@ func TestSetupParseBasic(t *testing.T) { ...@@ -58,21 +58,7 @@ func TestSetupParseBasic(t *testing.T) {
} }
// Cipher checks // Cipher checks
expectedCiphers := []uint16{ expectedCiphers := append([]uint16{tls.TLS_FALLBACK_SCSV}, getPreferredDefaultCiphers()...)
tls.TLS_FALLBACK_SCSV,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
}
// Ensure count is correct (plus one for TLS_FALLBACK_SCSV) // Ensure count is correct (plus one for TLS_FALLBACK_SCSV)
if len(cfg.Ciphers) != len(expectedCiphers) { if len(cfg.Ciphers) != len(expectedCiphers) {
......
CHANGES CHANGES
0.10.3 (May 19, 2017)
- Replace 'maxrequestbody' directive with 'limits' directive
- proxy: Configurable port for health check
- proxy: New load balance policy: uri_hash
- templates: Renamed .Push context action to .AddLink
- tls: Allow narrower certificate renewal window at startup (#1680)
- tls: Prefer ChaCha20 if hardware does not have AES-NI
0.10.2 (May 2, 2017) 0.10.2 (May 2, 2017)
- Hot fix for rule paths of "/" so that they match every request - Hot fix for rule paths of "/" so that they match every request
- fastcgi: Match request paths that don't start with "/" even if rule does - fastcgi: Match request paths that don't start with "/" even if rule does
......
CADDY 0.10.2 CADDY 0.10.3
Website Website
https://caddyserver.com https://caddyserver.com
......
This diff is collapsed.
This diff is collapsed.
The MIT License (MIT)
Copyright (c) 2016 Andreas Auernhammer
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.
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// Package chacha implements some low-level functions of the
// ChaCha cipher family.
package chacha // import "github.com/aead/chacha20/chacha"
import (
"encoding/binary"
"errors"
)
const (
// NonceSize is the size of the ChaCha20 nonce in bytes.
NonceSize = 8
// INonceSize is the size of the IETF-ChaCha20 nonce in bytes.
INonceSize = 12
// XNonceSize is the size of the XChaCha20 nonce in bytes.
XNonceSize = 24
// KeySize is the size of the key in bytes.
KeySize = 32
)
var (
useSSE2 bool
useSSSE3 bool
useAVX2 bool
)
var (
errKeySize = errors.New("chacha20/chacha: bad key length")
errInvalidNonce = errors.New("chacha20/chacha: bad nonce length")
)
func setup(state *[64]byte, nonce, key []byte) (err error) {
if len(key) != KeySize {
err = errKeySize
return
}
var Nonce [16]byte
switch len(nonce) {
case NonceSize:
copy(Nonce[8:], nonce)
initialize(state, key, &Nonce)
case INonceSize:
copy(Nonce[4:], nonce)
initialize(state, key, &Nonce)
case XNonceSize:
var tmpKey [32]byte
var hNonce [16]byte
copy(hNonce[:], nonce[:16])
copy(tmpKey[:], key)
hChaCha20(&tmpKey, &hNonce, &tmpKey)
copy(Nonce[8:], nonce[16:])
initialize(state, tmpKey[:], &Nonce)
// BUG(aead): A "good" compiler will remove this (optimizations)
// But using the provided key instead of tmpKey,
// will change the key (-> probably confuses users)
for i := range tmpKey {
tmpKey[i] = 0
}
default:
err = errInvalidNonce
}
return
}
// XORKeyStream crypts bytes from src to dst using the given nonce and key.
// The length of the nonce determinds the version of ChaCha20:
// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
// The rounds argument specifies the number of rounds performed for keystream
// generation - valid values are 8, 12 or 20. The src and dst may be the same slice
// but otherwise should not overlap. If len(dst) < len(src) this function panics.
// If the nonce is neither 64, 96 nor 192 bits long, this function panics.
func XORKeyStream(dst, src, nonce, key []byte, rounds int) {
if rounds != 20 && rounds != 12 && rounds != 8 {
panic("chacha20/chacha: bad number of rounds")
}
if len(dst) < len(src) {
panic("chacha20/chacha: dst buffer is to small")
}
if len(nonce) == INonceSize && uint64(len(src)) > (1<<38) {
panic("chacha20/chacha: src is too large")
}
var block, state [64]byte
if err := setup(&state, nonce, key); err != nil {
panic(err)
}
xorKeyStream(dst, src, &block, &state, rounds)
}
// Cipher implements ChaCha20/r (XChaCha20/r) for a given number of rounds r.
type Cipher struct {
state, block [64]byte
off int
rounds int // 20 for ChaCha20
noncesize int
}
// NewCipher returns a new *chacha.Cipher implementing the ChaCha20/r or XChaCha20/r
// (r = 8, 12 or 20) stream cipher. The nonce must be unique for one key for all time.
// The length of the nonce determinds the version of ChaCha20:
// - NonceSize: ChaCha20/r with a 64 bit nonce and a 2^64 * 64 byte period.
// - INonceSize: ChaCha20/r as defined in RFC 7539 and a 2^32 * 64 byte period.
// - XNonceSize: XChaCha20/r with a 192 bit nonce and a 2^64 * 64 byte period.
// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
func NewCipher(nonce, key []byte, rounds int) (*Cipher, error) {
if rounds != 20 && rounds != 12 && rounds != 8 {
panic("chacha20/chacha: bad number of rounds")
}
c := new(Cipher)
if err := setup(&(c.state), nonce, key); err != nil {
return nil, err
}
c.rounds = rounds
if len(nonce) == INonceSize {
c.noncesize = INonceSize
} else {
c.noncesize = NonceSize
}
return c, nil
}
// XORKeyStream crypts bytes from src to dst. Src and dst may be the same slice
// but otherwise should not overlap. If len(dst) < len(src) the function panics.
func (c *Cipher) XORKeyStream(dst, src []byte) {
if len(dst) < len(src) {
panic("chacha20/chacha: dst buffer is to small")
}
if c.off > 0 {
n := len(c.block[c.off:])
if len(src) <= n {
for i, v := range src {
dst[i] = v ^ c.block[c.off]
c.off++
}
if c.off == 64 {
c.off = 0
}
return
}
for i, v := range c.block[c.off:] {
dst[i] = src[i] ^ v
}
src = src[n:]
dst = dst[n:]
c.off = 0
}
c.off += xorKeyStream(dst, src, &(c.block), &(c.state), c.rounds)
}
// SetCounter skips ctr * 64 byte blocks. SetCounter(0) resets the cipher.
// This function always skips the unused keystream of the current 64 byte block.
func (c *Cipher) SetCounter(ctr uint64) {
if c.noncesize == INonceSize {
binary.LittleEndian.PutUint32(c.state[48:], uint32(ctr))
} else {
binary.LittleEndian.PutUint64(c.state[48:], ctr)
}
c.off = 0
}
This diff is collapsed.
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// +build 386,!gccgo,!appengine,!nacl
package chacha
import "encoding/binary"
func init() {
useSSE2 = supportsSSE2()
useSSSE3 = supportsSSSE3()
useAVX2 = false
}
func initialize(state *[64]byte, key []byte, nonce *[16]byte) {
binary.LittleEndian.PutUint32(state[0:], sigma[0])
binary.LittleEndian.PutUint32(state[4:], sigma[1])
binary.LittleEndian.PutUint32(state[8:], sigma[2])
binary.LittleEndian.PutUint32(state[12:], sigma[3])
copy(state[16:], key[:])
copy(state[48:], nonce[:])
}
// This function is implemented in chacha_386.s
//go:noescape
func supportsSSE2() bool
// This function is implemented in chacha_386.s
//go:noescape
func supportsSSSE3() bool
// This function is implemented in chacha_386.s
//go:noescape
func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_386.s
//go:noescape
func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_386.s
//go:noescape
func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
// This function is implemented in chacha_386.s
//go:noescape
func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
if useSSSE3 {
hChaCha20SSSE3(out, nonce, key)
} else if useSSE2 {
hChaCha20SSE2(out, nonce, key)
} else {
hChaCha20Generic(out, nonce, key)
}
}
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
if useSSSE3 {
return xorKeyStreamSSSE3(dst, src, block, state, rounds)
} else if useSSE2 {
return xorKeyStreamSSE2(dst, src, block, state, rounds)
}
return xorKeyStreamGeneric(dst, src, block, state, rounds)
}
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// +build 386,!gccgo,!appengine,!nacl
#include "textflag.h"
DATA ·sigma<>+0x00(SB)/4, $0x61707865
DATA ·sigma<>+0x04(SB)/4, $0x3320646e
DATA ·sigma<>+0x08(SB)/4, $0x79622d32
DATA ·sigma<>+0x0C(SB)/4, $0x6b206574
GLOBL ·sigma<>(SB), (NOPTR+RODATA), $16
DATA ·one<>+0x00(SB)/8, $1
DATA ·one<>+0x08(SB)/8, $0
GLOBL ·one<>(SB), (NOPTR+RODATA), $16
DATA ·rol16<>+0x00(SB)/8, $0x0504070601000302
DATA ·rol16<>+0x08(SB)/8, $0x0D0C0F0E09080B0A
GLOBL ·rol16<>(SB), (NOPTR+RODATA), $16
DATA ·rol8<>+0x00(SB)/8, $0x0605040702010003
DATA ·rol8<>+0x08(SB)/8, $0x0E0D0C0F0A09080B
GLOBL ·rol8<>(SB), (NOPTR+RODATA), $16
#define ROTL_SSE2(n, t, v) \
MOVO v, t; \
PSLLL $n, t; \
PSRLL $(32-n), v; \
PXOR t, v
#define CHACHA_QROUND_SSE2(v0 , v1 , v2 , v3 , t0) \
PADDL v1, v0; \
PXOR v0, v3; \
ROTL_SSE2(16, t0, v3); \
PADDL v3, v2; \
PXOR v2, v1; \
ROTL_SSE2(12, t0, v1); \
PADDL v1, v0; \
PXOR v0, v3; \
ROTL_SSE2(8, t0, v3); \
PADDL v3, v2; \
PXOR v2, v1; \
ROTL_SSE2(7, t0, v1)
#define CHACHA_QROUND_SSSE3(v0 , v1 , v2 , v3 , t0, r16, r8) \
PADDL v1, v0; \
PXOR v0, v3; \
PSHUFB r16, v3; \
PADDL v3, v2; \
PXOR v2, v1; \
ROTL_SSE2(12, t0, v1); \
PADDL v1, v0; \
PXOR v0, v3; \
PSHUFB r8, v3; \
PADDL v3, v2; \
PXOR v2, v1; \
ROTL_SSE2(7, t0, v1)
#define CHACHA_SHUFFLE(v1, v2, v3) \
PSHUFL $0x39, v1, v1; \
PSHUFL $0x4E, v2, v2; \
PSHUFL $0x93, v3, v3
#define XOR(dst, src, off, v0 , v1 , v2 , v3 , t0) \
MOVOU 0+off(src), t0; \
PXOR v0, t0; \
MOVOU t0, 0+off(dst); \
MOVOU 16+off(src), t0; \
PXOR v1, t0; \
MOVOU t0, 16+off(dst); \
MOVOU 32+off(src), t0; \
PXOR v2, t0; \
MOVOU t0, 32+off(dst); \
MOVOU 48+off(src), t0; \
PXOR v3, t0; \
MOVOU t0, 48+off(dst)
#define FINALIZE(dst, src, block, len, t0, t1) \
XORL t0, t0; \
XORL t1, t1; \
finalize: \
MOVB 0(src), t0; \
MOVB 0(block), t1; \
XORL t0, t1; \
MOVB t1, 0(dst); \
INCL src; \
INCL block; \
INCL dst; \
DECL len; \
JA finalize \
// func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
TEXT ·xorKeyStreamSSE2(SB),4,$0-40
MOVL dst_base+0(FP), DI
MOVL src_base+12(FP), SI
MOVL src_len+16(FP), CX
MOVL state+28(FP), AX
MOVL rounds+32(FP), DX
MOVOU 0(AX), X0
MOVOU 16(AX), X1
MOVOU 32(AX), X2
MOVOU 48(AX), X3
TESTL CX, CX
JZ done
at_least_64:
MOVO X0, X4
MOVO X1, X5
MOVO X2, X6
MOVO X3, X7
MOVL DX, BX
chacha_loop:
CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0)
CHACHA_SHUFFLE(X5, X6, X7)
CHACHA_QROUND_SSE2(X4, X5, X6, X7, X0)
CHACHA_SHUFFLE(X7, X6, X5)
SUBL $2, BX
JA chacha_loop
MOVOU 0(AX), X0
PADDL X0, X4
PADDL X1, X5
PADDL X2, X6
PADDL X3, X7
MOVOU ·one<>(SB), X0
PADDQ X0, X3
CMPL CX, $64
JB less_than_64
XOR(DI, SI, 0, X4, X5, X6, X7, X0)
MOVOU 0(AX), X0
ADDL $64, SI
ADDL $64, DI
SUBL $64, CX
JNZ at_least_64
less_than_64:
MOVL CX, BP
TESTL BP, BP
JZ done
MOVL block+24(FP), BX
MOVOU X4, 0(BX)
MOVOU X5, 16(BX)
MOVOU X6, 32(BX)
MOVOU X7, 48(BX)
FINALIZE(DI, SI, BX, BP, AX, DX)
done:
MOVL state+28(FP), AX
MOVOU X3, 48(AX)
MOVL CX, ret+36(FP)
RET
// func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int
TEXT ·xorKeyStreamSSSE3(SB),4,$64-40
MOVL dst_base+0(FP), DI
MOVL src_base+12(FP), SI
MOVL src_len+16(FP), CX
MOVL state+28(FP), AX
MOVL rounds+32(FP), DX
MOVOU 48(AX), X3
TESTL CX, CX
JZ done
MOVL SP, BP
ADDL $16, SP
ANDL $-16, SP
MOVOU ·one<>(SB), X0
MOVOU 16(AX), X1
MOVOU 32(AX), X2
MOVO X0, 0(SP)
MOVO X1, 16(SP)
MOVO X2, 32(SP)
MOVOU 0(AX), X0
MOVOU ·rol16<>(SB), X1
MOVOU ·rol8<>(SB), X2
at_least_64:
MOVO X0, X4
MOVO 16(SP), X5
MOVO 32(SP), X6
MOVO X3, X7
MOVL DX, BX
chacha_loop:
CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X0, X1, X2)
CHACHA_SHUFFLE(X5, X6, X7)
CHACHA_QROUND_SSSE3(X4, X5, X6, X7, X0, X1, X2)
CHACHA_SHUFFLE(X7, X6, X5)
SUBL $2, BX
JA chacha_loop
MOVOU 0(AX), X0
PADDL X0, X4
PADDL 16(SP), X5
PADDL 32(SP), X6
PADDL X3, X7
PADDQ 0(SP), X3
CMPL CX, $64
JB less_than_64
XOR(DI, SI, 0, X4, X5, X6, X7, X0)
MOVOU 0(AX), X0
ADDL $64, SI
ADDL $64, DI
SUBL $64, CX
JNZ at_least_64
less_than_64:
MOVL BP, SP
MOVL CX, BP
TESTL BP, BP
JE done
MOVL block+24(FP), BX
MOVOU X4, 0(BX)
MOVOU X5, 16(BX)
MOVOU X6, 32(BX)
MOVOU X7, 48(BX)
FINALIZE(DI, SI, BX, BP, AX, DX)
done:
MOVL state+28(FP), AX
MOVOU X3, 48(AX)
MOVL CX, ret+36(FP)
RET
// func supportsSSE2() bool
TEXT ·supportsSSE2(SB), NOSPLIT, $0-1
XORL AX, AX
INCL AX
CPUID
SHRL $26, DX
ANDL $1, DX
MOVB DX, ret+0(FP)
RET
// func supportsSSSE3() bool
TEXT ·supportsSSSE3(SB), NOSPLIT, $0-1
XORL AX, AX
INCL AX
CPUID
SHRL $9, CX
ANDL $1, CX
MOVB CX, ret+0(FP)
RET
// func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
TEXT ·hChaCha20SSE2(SB), 4, $0-12
MOVL out+0(FP), DI
MOVL nonce+4(FP), AX
MOVL key+8(FP), BX
MOVOU ·sigma<>(SB), X0
MOVOU 0(BX), X1
MOVOU 16(BX), X2
MOVOU 0(AX), X3
MOVL $20, CX
chacha_loop:
CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4)
CHACHA_SHUFFLE(X1, X2, X3)
CHACHA_QROUND_SSE2(X0, X1, X2, X3, X4)
CHACHA_SHUFFLE(X3, X2, X1)
SUBL $2, CX
JNZ chacha_loop
MOVOU X0, 0(DI)
MOVOU X3, 16(DI)
RET
// func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
TEXT ·hChaCha20SSSE3(SB), 4, $0-12
MOVL out+0(FP), DI
MOVL nonce+4(FP), AX
MOVL key+8(FP), BX
MOVOU ·sigma<>(SB), X0
MOVOU 0(BX), X1
MOVOU 16(BX), X2
MOVOU 0(AX), X3
MOVOU ·rol16<>(SB), X5
MOVOU ·rol8<>(SB), X6
MOVL $20, CX
chacha_loop:
CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6)
CHACHA_SHUFFLE(X1, X2, X3)
CHACHA_QROUND_SSSE3(X0, X1, X2, X3, X4, X5, X6)
CHACHA_SHUFFLE(X3, X2, X1)
SUBL $2, CX
JNZ chacha_loop
MOVOU X0, 0(DI)
MOVOU X3, 16(DI)
RET
This diff is collapsed.
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
package chacha
import "encoding/binary"
var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
func xorKeyStreamGeneric(dst, src []byte, block, state *[64]byte, rounds int) int {
for len(src) >= 64 {
chachaGeneric(block, state, rounds)
for i, v := range block {
dst[i] = src[i] ^ v
}
src = src[64:]
dst = dst[64:]
}
n := len(src)
if n > 0 {
chachaGeneric(block, state, rounds)
for i, v := range src {
dst[i] = v ^ block[i]
}
}
return n
}
func chachaGeneric(dst *[64]byte, state *[64]byte, rounds int) {
v00 := binary.LittleEndian.Uint32(state[0:])
v01 := binary.LittleEndian.Uint32(state[4:])
v02 := binary.LittleEndian.Uint32(state[8:])
v03 := binary.LittleEndian.Uint32(state[12:])
v04 := binary.LittleEndian.Uint32(state[16:])
v05 := binary.LittleEndian.Uint32(state[20:])
v06 := binary.LittleEndian.Uint32(state[24:])
v07 := binary.LittleEndian.Uint32(state[28:])
v08 := binary.LittleEndian.Uint32(state[32:])
v09 := binary.LittleEndian.Uint32(state[36:])
v10 := binary.LittleEndian.Uint32(state[40:])
v11 := binary.LittleEndian.Uint32(state[44:])
v12 := binary.LittleEndian.Uint32(state[48:])
v13 := binary.LittleEndian.Uint32(state[52:])
v14 := binary.LittleEndian.Uint32(state[56:])
v15 := binary.LittleEndian.Uint32(state[60:])
s00, s01, s02, s03, s04, s05, s06, s07 := v00, v01, v02, v03, v04, v05, v06, v07
s08, s09, s10, s11, s12, s13, s14, s15 := v08, v09, v10, v11, v12, v13, v14, v15
for i := 0; i < rounds; i += 2 {
v00 += v04
v12 ^= v00
v12 = (v12 << 16) | (v12 >> 16)
v08 += v12
v04 ^= v08
v04 = (v04 << 12) | (v04 >> 20)
v00 += v04
v12 ^= v00
v12 = (v12 << 8) | (v12 >> 24)
v08 += v12
v04 ^= v08
v04 = (v04 << 7) | (v04 >> 25)
v01 += v05
v13 ^= v01
v13 = (v13 << 16) | (v13 >> 16)
v09 += v13
v05 ^= v09
v05 = (v05 << 12) | (v05 >> 20)
v01 += v05
v13 ^= v01
v13 = (v13 << 8) | (v13 >> 24)
v09 += v13
v05 ^= v09
v05 = (v05 << 7) | (v05 >> 25)
v02 += v06
v14 ^= v02
v14 = (v14 << 16) | (v14 >> 16)
v10 += v14
v06 ^= v10
v06 = (v06 << 12) | (v06 >> 20)
v02 += v06
v14 ^= v02
v14 = (v14 << 8) | (v14 >> 24)
v10 += v14
v06 ^= v10
v06 = (v06 << 7) | (v06 >> 25)
v03 += v07
v15 ^= v03
v15 = (v15 << 16) | (v15 >> 16)
v11 += v15
v07 ^= v11
v07 = (v07 << 12) | (v07 >> 20)
v03 += v07
v15 ^= v03
v15 = (v15 << 8) | (v15 >> 24)
v11 += v15
v07 ^= v11
v07 = (v07 << 7) | (v07 >> 25)
v00 += v05
v15 ^= v00
v15 = (v15 << 16) | (v15 >> 16)
v10 += v15
v05 ^= v10
v05 = (v05 << 12) | (v05 >> 20)
v00 += v05
v15 ^= v00
v15 = (v15 << 8) | (v15 >> 24)
v10 += v15
v05 ^= v10
v05 = (v05 << 7) | (v05 >> 25)
v01 += v06
v12 ^= v01
v12 = (v12 << 16) | (v12 >> 16)
v11 += v12
v06 ^= v11
v06 = (v06 << 12) | (v06 >> 20)
v01 += v06
v12 ^= v01
v12 = (v12 << 8) | (v12 >> 24)
v11 += v12
v06 ^= v11
v06 = (v06 << 7) | (v06 >> 25)
v02 += v07
v13 ^= v02
v13 = (v13 << 16) | (v13 >> 16)
v08 += v13
v07 ^= v08
v07 = (v07 << 12) | (v07 >> 20)
v02 += v07
v13 ^= v02
v13 = (v13 << 8) | (v13 >> 24)
v08 += v13
v07 ^= v08
v07 = (v07 << 7) | (v07 >> 25)
v03 += v04
v14 ^= v03
v14 = (v14 << 16) | (v14 >> 16)
v09 += v14
v04 ^= v09
v04 = (v04 << 12) | (v04 >> 20)
v03 += v04
v14 ^= v03
v14 = (v14 << 8) | (v14 >> 24)
v09 += v14
v04 ^= v09
v04 = (v04 << 7) | (v04 >> 25)
}
v00 += s00
v01 += s01
v02 += s02
v03 += s03
v04 += s04
v05 += s05
v06 += s06
v07 += s07
v08 += s08
v09 += s09
v10 += s10
v11 += s11
v12 += s12
v13 += s13
v14 += s14
v15 += s15
s12++
binary.LittleEndian.PutUint32(state[48:], s12)
if s12 == 0 { // indicates overflow
s13++
binary.LittleEndian.PutUint32(state[52:], s13)
}
binary.LittleEndian.PutUint32(dst[0:], v00)
binary.LittleEndian.PutUint32(dst[4:], v01)
binary.LittleEndian.PutUint32(dst[8:], v02)
binary.LittleEndian.PutUint32(dst[12:], v03)
binary.LittleEndian.PutUint32(dst[16:], v04)
binary.LittleEndian.PutUint32(dst[20:], v05)
binary.LittleEndian.PutUint32(dst[24:], v06)
binary.LittleEndian.PutUint32(dst[28:], v07)
binary.LittleEndian.PutUint32(dst[32:], v08)
binary.LittleEndian.PutUint32(dst[36:], v09)
binary.LittleEndian.PutUint32(dst[40:], v10)
binary.LittleEndian.PutUint32(dst[44:], v11)
binary.LittleEndian.PutUint32(dst[48:], v12)
binary.LittleEndian.PutUint32(dst[52:], v13)
binary.LittleEndian.PutUint32(dst[56:], v14)
binary.LittleEndian.PutUint32(dst[60:], v15)
}
func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) {
v00 := sigma[0]
v01 := sigma[1]
v02 := sigma[2]
v03 := sigma[3]
v04 := binary.LittleEndian.Uint32(key[0:])
v05 := binary.LittleEndian.Uint32(key[4:])
v06 := binary.LittleEndian.Uint32(key[8:])
v07 := binary.LittleEndian.Uint32(key[12:])
v08 := binary.LittleEndian.Uint32(key[16:])
v09 := binary.LittleEndian.Uint32(key[20:])
v10 := binary.LittleEndian.Uint32(key[24:])
v11 := binary.LittleEndian.Uint32(key[28:])
v12 := binary.LittleEndian.Uint32(nonce[0:])
v13 := binary.LittleEndian.Uint32(nonce[4:])
v14 := binary.LittleEndian.Uint32(nonce[8:])
v15 := binary.LittleEndian.Uint32(nonce[12:])
for i := 0; i < 20; i += 2 {
v00 += v04
v12 ^= v00
v12 = (v12 << 16) | (v12 >> 16)
v08 += v12
v04 ^= v08
v04 = (v04 << 12) | (v04 >> 20)
v00 += v04
v12 ^= v00
v12 = (v12 << 8) | (v12 >> 24)
v08 += v12
v04 ^= v08
v04 = (v04 << 7) | (v04 >> 25)
v01 += v05
v13 ^= v01
v13 = (v13 << 16) | (v13 >> 16)
v09 += v13
v05 ^= v09
v05 = (v05 << 12) | (v05 >> 20)
v01 += v05
v13 ^= v01
v13 = (v13 << 8) | (v13 >> 24)
v09 += v13
v05 ^= v09
v05 = (v05 << 7) | (v05 >> 25)
v02 += v06
v14 ^= v02
v14 = (v14 << 16) | (v14 >> 16)
v10 += v14
v06 ^= v10
v06 = (v06 << 12) | (v06 >> 20)
v02 += v06
v14 ^= v02
v14 = (v14 << 8) | (v14 >> 24)
v10 += v14
v06 ^= v10
v06 = (v06 << 7) | (v06 >> 25)
v03 += v07
v15 ^= v03
v15 = (v15 << 16) | (v15 >> 16)
v11 += v15
v07 ^= v11
v07 = (v07 << 12) | (v07 >> 20)
v03 += v07
v15 ^= v03
v15 = (v15 << 8) | (v15 >> 24)
v11 += v15
v07 ^= v11
v07 = (v07 << 7) | (v07 >> 25)
v00 += v05
v15 ^= v00
v15 = (v15 << 16) | (v15 >> 16)
v10 += v15
v05 ^= v10
v05 = (v05 << 12) | (v05 >> 20)
v00 += v05
v15 ^= v00
v15 = (v15 << 8) | (v15 >> 24)
v10 += v15
v05 ^= v10
v05 = (v05 << 7) | (v05 >> 25)
v01 += v06
v12 ^= v01
v12 = (v12 << 16) | (v12 >> 16)
v11 += v12
v06 ^= v11
v06 = (v06 << 12) | (v06 >> 20)
v01 += v06
v12 ^= v01
v12 = (v12 << 8) | (v12 >> 24)
v11 += v12
v06 ^= v11
v06 = (v06 << 7) | (v06 >> 25)
v02 += v07
v13 ^= v02
v13 = (v13 << 16) | (v13 >> 16)
v08 += v13
v07 ^= v08
v07 = (v07 << 12) | (v07 >> 20)
v02 += v07
v13 ^= v02
v13 = (v13 << 8) | (v13 >> 24)
v08 += v13
v07 ^= v08
v07 = (v07 << 7) | (v07 >> 25)
v03 += v04
v14 ^= v03
v14 = (v14 << 16) | (v14 >> 16)
v09 += v14
v04 ^= v09
v04 = (v04 << 12) | (v04 >> 20)
v03 += v04
v14 ^= v03
v14 = (v14 << 8) | (v14 >> 24)
v09 += v14
v04 ^= v09
v04 = (v04 << 7) | (v04 >> 25)
}
binary.LittleEndian.PutUint32(out[0:], v00)
binary.LittleEndian.PutUint32(out[4:], v01)
binary.LittleEndian.PutUint32(out[8:], v02)
binary.LittleEndian.PutUint32(out[12:], v03)
binary.LittleEndian.PutUint32(out[16:], v12)
binary.LittleEndian.PutUint32(out[20:], v13)
binary.LittleEndian.PutUint32(out[24:], v14)
binary.LittleEndian.PutUint32(out[28:], v15)
}
// Copyright (c) 2017 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// +build amd64,!gccgo,!appengine,!nacl,!go1.7
package chacha
func init() {
useSSE2 = true
useSSSE3 = supportsSSSE3()
useAVX2 = false
}
// This function is implemented in chacha_amd64.s
//go:noescape
func initialize(state *[64]byte, key []byte, nonce *[16]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func supportsSSSE3() bool
// This function is implemented in chacha_amd64.s
//go:noescape
func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
// This function is implemented in chacha_amd64.s
//go:noescape
func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
if useSSSE3 {
hChaCha20SSSE3(out, nonce, key)
} else if useSSE2 { // on amd64 this is always true - used to test generic on amd64
hChaCha20SSE2(out, nonce, key)
} else {
hChaCha20Generic(out, nonce, key)
}
}
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
if useSSSE3 {
return xorKeyStreamSSSE3(dst, src, block, state, rounds)
} else if useSSE2 { // on amd64 this is always true - used to test generic on amd64
return xorKeyStreamSSE2(dst, src, block, state, rounds)
}
return xorKeyStreamGeneric(dst, src, block, state, rounds)
}
// Copyright (c) 2017 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// +build go1.7,amd64,!gccgo,!appengine,!nacl
package chacha
func init() {
useSSE2 = true
useSSSE3 = supportsSSSE3()
useAVX2 = supportsAVX2()
}
// This function is implemented in chacha_amd64.s
//go:noescape
func initialize(state *[64]byte, key []byte, nonce *[16]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func supportsSSSE3() bool
// This function is implemented in chachaAVX2_amd64.s
//go:noescape
func supportsAVX2() bool
// This function is implemented in chacha_amd64.s
//go:noescape
func hChaCha20SSE2(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func hChaCha20SSSE3(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chachaAVX2_amd64.s
//go:noescape
func hChaCha20AVX(out *[32]byte, nonce *[16]byte, key *[32]byte)
// This function is implemented in chacha_amd64.s
//go:noescape
func xorKeyStreamSSE2(dst, src []byte, block, state *[64]byte, rounds int) int
// This function is implemented in chacha_amd64.s
//go:noescape
func xorKeyStreamSSSE3(dst, src []byte, block, state *[64]byte, rounds int) int
// This function is implemented in chachaAVX2_amd64.s
//go:noescape
func xorKeyStreamAVX2(dst, src []byte, block, state *[64]byte, rounds int) int
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
if useAVX2 {
hChaCha20AVX(out, nonce, key)
} else if useSSSE3 {
hChaCha20SSSE3(out, nonce, key)
} else if useSSE2 { // on amd64 this is always true - neccessary for testing generic on amd64
hChaCha20SSE2(out, nonce, key)
} else {
hChaCha20Generic(out, nonce, key)
}
}
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
if useAVX2 {
return xorKeyStreamAVX2(dst, src, block, state, rounds)
} else if useSSSE3 {
return xorKeyStreamSSSE3(dst, src, block, state, rounds)
} else if useSSE2 { // on amd64 this is always true - neccessary for testing generic on amd64
return xorKeyStreamSSE2(dst, src, block, state, rounds)
}
return xorKeyStreamGeneric(dst, src, block, state, rounds)
}
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// +build !amd64,!386 gccgo appengine nacl
package chacha
import "encoding/binary"
func initialize(state *[64]byte, key []byte, nonce *[16]byte) {
binary.LittleEndian.PutUint32(state[0:], sigma[0])
binary.LittleEndian.PutUint32(state[4:], sigma[1])
binary.LittleEndian.PutUint32(state[8:], sigma[2])
binary.LittleEndian.PutUint32(state[12:], sigma[3])
copy(state[16:], key[:])
copy(state[48:], nonce[:])
}
func xorKeyStream(dst, src []byte, block, state *[64]byte, rounds int) int {
return xorKeyStreamGeneric(dst, src, block, state, rounds)
}
func hChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) {
hChaCha20Generic(out, nonce, key)
}
// Copyright (c) 2016 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// Package chacha20 implements the ChaCha20 / XChaCha20 stream chipher.
// Notice that one specific key-nonce combination must be unique for all time.
//
// There are three versions of ChaCha20:
// - ChaCha20 with a 64 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
// - ChaCha20 with a 96 bit nonce (en/decrypt up to 2^32 * 64 bytes (~256 GB) for one key-nonce combination)
// - XChaCha20 with a 192 bit nonce (en/decrypt up to 2^64 * 64 bytes for one key-nonce combination)
package chacha20 // import "github.com/aead/chacha20"
import (
"crypto/cipher"
"github.com/aead/chacha20/chacha"
)
// XORKeyStream crypts bytes from src to dst using the given nonce and key.
// The length of the nonce determinds the version of ChaCha20:
// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period.
// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period.
// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period.
// Src and dst may be the same slice but otherwise should not overlap.
// If len(dst) < len(src) this function panics.
// If the nonce is neither 64, 96 nor 192 bits long, this function panics.
func XORKeyStream(dst, src, nonce, key []byte) {
chacha.XORKeyStream(dst, src, nonce, key, 20)
}
// NewCipher returns a new cipher.Stream implementing a ChaCha20 version.
// The nonce must be unique for one key for all time.
// The length of the nonce determinds the version of ChaCha20:
// - 8 bytes: ChaCha20 with a 64 bit nonce and a 2^64 * 64 byte period.
// - 12 bytes: ChaCha20 as defined in RFC 7539 and a 2^32 * 64 byte period.
// - 24 bytes: XChaCha20 with a 192 bit nonce and a 2^64 * 64 byte period.
// If the nonce is neither 64, 96 nor 192 bits long, a non-nil error is returned.
func NewCipher(nonce, key []byte) (cipher.Stream, error) {
return chacha.NewCipher(nonce, key, 20)
}
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Helper functions to make constructing templates easier.
package template
import (
"fmt"
"io/ioutil"
"path/filepath"
)
// Functions and methods to parse templates.
// Must is a helper that wraps a call to a function returning (*Template, error)
// and panics if the error is non-nil. It is intended for use in variable
// initializations such as
// var t = template.Must(template.New("name").Parse("text"))
func Must(t *Template, err error) *Template {
if err != nil {
panic(err)
}
return t
}
// ParseFiles creates a new Template and parses the template definitions from
// the named files. The returned template's name will have the (base) name and
// (parsed) contents of the first file. There must be at least one file.
// If an error occurs, parsing stops and the returned *Template is nil.
func ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(nil, filenames...)
}
// ParseFiles parses the named files and associates the resulting templates with
// t. If an error occurs, parsing stops and the returned template is nil;
// otherwise it is t. There must be at least one file.
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(t, filenames...)
}
// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
func parseFiles(t *Template, filenames ...string) (*Template, error) {
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
}
for _, filename := range filenames {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
s := string(b)
name := filepath.Base(filename)
// First template becomes return value if not already defined,
// and we use that one for subsequent New calls to associate
// all the templates together. Also, if this file has the same name
// as t, this file becomes the contents of t, so
// t, err := New(name).Funcs(xxx).ParseFiles(name)
// works. Otherwise we create a new template associated with t.
var tmpl *Template
if t == nil {
t = New(name)
}
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
_, err = tmpl.Parse(s)
if err != nil {
return nil, err
}
}
return t, nil
}
// ParseGlob creates a new Template and parses the template definitions from the
// files identified by the pattern, which must match at least one file. The
// returned template will have the (base) name and (parsed) contents of the
// first file matched by the pattern. ParseGlob is equivalent to calling
// ParseFiles with the list of files matched by the pattern.
func ParseGlob(pattern string) (*Template, error) {
return parseGlob(nil, pattern)
}
// ParseGlob parses the template definitions in the files identified by the
// pattern and associates the resulting templates with t. The pattern is
// processed by filepath.Glob and must match at least one file. ParseGlob is
// equivalent to calling t.ParseFiles with the list of files matched by the
// pattern.
func (t *Template) ParseGlob(pattern string) (*Template, error) {
return parseGlob(t, pattern)
}
// parseGlob is the implementation of the function and method ParseGlob.
func parseGlob(t *Template, pattern string) (*Template, error) {
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
if len(filenames) == 0 {
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
}
return parseFiles(t, filenames...)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Copyright (C) 2014 Alec Thomas
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.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// func HasAESNI() bool
TEXT ·HasAESNI(SB),$0
XORQ AX, AX
INCL AX
CPUID
SHRQ $25, CX
ANDQ $1, CX
MOVB CX, ret+0(FP)
RET
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment