forked from SW/traefik
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8393faf0a | ||
|
|
4b8ece5b42 | ||
|
|
7574bb9226 | ||
|
|
f68b629469 | ||
|
|
f9e9e11035 | ||
|
|
772c9ca4d5 | ||
|
|
208d0fa471 | ||
|
|
9f72b6d1d5 | ||
|
|
29ef007917 | ||
|
|
590a0d67bb |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.7.20](https://github.com/containous/traefik/tree/v1.7.20) (2019-12-09)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.19...v1.7.20)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Truncate key for identification in log ([#5941](https://github.com/containous/traefik/pull/5941) by [dtomcej](https://github.com/dtomcej))
|
||||
- **[middleware]** fix: location header rewrite. ([#5857](https://github.com/containous/traefik/pull/5857) by [ldez](https://github.com/ldez))
|
||||
|
||||
**Documentation:**
|
||||
- Add a warning note regarding optional TLS mutual auth ([#5434](https://github.com/containous/traefik/pull/5434) by [bradjones1](https://github.com/bradjones1))
|
||||
|
||||
## [v1.7.19](https://github.com/containous/traefik/tree/v1.7.19) (2019-10-25)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.18...v1.7.19)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[k8s,k8s/ingress]** Add functions to support precise float compute of weight ([#5663](https://github.com/containous/traefik/pull/5663) by [rmrfself](https://github.com/rmrfself))
|
||||
- **[middleware]** Fix Location response header http to https when SSL ([#5574](https://github.com/containous/traefik/pull/5574) by [elielgoncalves](https://github.com/elielgoncalves))
|
||||
- **[tls]** Allow Default Certificate to work on macOS 10.15 ([#5662](https://github.com/containous/traefik/pull/5662) by [dtomcej](https://github.com/dtomcej))
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s]** Update DaemonSet apiVersion ([#5682](https://github.com/containous/traefik/pull/5682) by [ialidzhikov](https://github.com/ialidzhikov))
|
||||
|
||||
## [v1.7.18](https://github.com/containous/traefik/tree/v1.7.18) (2019-09-23)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.17...v1.7.18)
|
||||
|
||||
|
||||
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@@ -1770,6 +1770,13 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "1d7be4effb13d2d908342d349d71a284a7542693"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9ca27be3cfd8a452f9814926a5842c6917289da8c21174ee0f9c79d84850b2ed"
|
||||
name = "github.com/shopspring/decimal"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "f1972eb1d1f519e2e5f4b51f2dea765e8c93a130"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:01252cd79aac70f16cac02a72a1067dd136e0ad6d5b597d0129cf74c739fd8d1"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
@@ -1908,12 +1915,12 @@
|
||||
revision = "50716a0a853771bb36bfce61a45cdefdb98c2e6e"
|
||||
|
||||
[[projects]]
|
||||
branch = "v1"
|
||||
digest = "1:60888cead16f066c948c078258b27f2885dce91cb6aadacf545b62a1ae1d08cb"
|
||||
digest = "1:649756d307b6d8ddb369d1cca0465b679aa7d1a956ddfa8eb18f8072a1a2b7a4"
|
||||
name = "github.com/unrolled/secure"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "a1cf62cc2159fff407728f118c41aece76c397fa"
|
||||
revision = "996bc0cd7e5be6e6a1c5f34b0259bc47c8bcfbc9"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e84e99d5f369afaa9a5c41f55b57fa03047ecd3bac2a65861607882693ceea81"
|
||||
@@ -2612,6 +2619,7 @@
|
||||
"github.com/rancher/go-rancher-metadata/metadata",
|
||||
"github.com/rancher/go-rancher/v2",
|
||||
"github.com/ryanuber/go-glob",
|
||||
"github.com/shopspring/decimal",
|
||||
"github.com/sirupsen/logrus",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/mock",
|
||||
|
||||
10
Gopkg.toml
10
Gopkg.toml
@@ -167,8 +167,8 @@
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v1"
|
||||
name = "github.com/unrolled/secure"
|
||||
version = "1.0.5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vdemeester/shakers"
|
||||
@@ -264,3 +264,11 @@
|
||||
[[constraint]]
|
||||
name = "github.com/google/uuid"
|
||||
version = "0.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/shopspring/decimal"
|
||||
revision = "f1972eb1d1f519e2e5f4b51f2dea765e8c93a130"
|
||||
|
||||
[[override]]
|
||||
name = "contrib.go.opencensus.io/exporter/ocagent"
|
||||
version = "0.4.12"
|
||||
|
||||
@@ -111,7 +111,12 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
return privateKey
|
||||
}
|
||||
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
keySnippet := ""
|
||||
if len(a.PrivateKey) >= 16 {
|
||||
keySnippet = string(a.PrivateKey[:16])
|
||||
}
|
||||
|
||||
log.Errorf("Cannot unmarshall private key beginning with %s", keySnippet)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -239,11 +239,14 @@ TLS Mutual Authentication can be `optional` or not.
|
||||
* If `optional = true`, if a certificate is provided, verifies if it is signed by a specified Certificate Authority (CA). Otherwise proceeds without any certificate.
|
||||
* If `optional = false`, Traefik will only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||
|
||||
!!! warning
|
||||
While the TLS [1.1](https://tools.ietf.org/html/rfc4346#section-7.4.6) and [1.2](https://tools.ietf.org/html/rfc5246#section-7.4.6) RFCs specify that clients should proceed with handshaking by sending an empty list should they have no certs for the CAs specified by the server, not all do so in practice.
|
||||
Use this feature with caution should you require maximum compatibility with a wide variety of client user agents which may not strictly implement these specs.
|
||||
|
||||
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
|
||||
The `CA:s` has to be in PEM format.
|
||||
|
||||
By default, `ClientCAFiles` is not optional, all clients will be required to present a valid cert.
|
||||
The requirement will apply to all server certs in the entrypoint.
|
||||
By default, `ClientCAFiles` is not optional, all clients will be required to present a valid cert. The requirement will apply to all server certs in the entrypoint.
|
||||
|
||||
In the example below both `snitest.com` and `snitest.org` will require client certs
|
||||
|
||||
|
||||
@@ -6,13 +6,17 @@ metadata:
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: DaemonSet
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
@@ -341,24 +341,32 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.org"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "snitest.com"
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
// Connection without client certificate should fail
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.NotNil)
|
||||
|
||||
// Connect with client signed by ca1
|
||||
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
conn.Close()
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Connect with client signed by ca2
|
||||
tlsConfig = &tls.Config{
|
||||
@@ -370,10 +378,13 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
client = http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Connect with client signed by ca3 should fail
|
||||
tlsConfig = &tls.Config{
|
||||
@@ -385,8 +396,13 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
client = http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.NotNil)
|
||||
}
|
||||
|
||||
// TestWithClientCertificateAuthentication
|
||||
@@ -402,24 +418,32 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFi
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1000*time.Millisecond, try.BodyContains("Host:snitest.org"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = "snitest.com"
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
// Connection without client certificate should fail
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.NotNil)
|
||||
|
||||
// Connect with client signed by ca1
|
||||
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
conn.Close()
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Connect with client signed by ca2
|
||||
tlsConfig = &tls.Config{
|
||||
@@ -431,9 +455,13 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFi
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
conn.Close()
|
||||
client = http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// Connect with client signed by ca3 should fail
|
||||
tlsConfig = &tls.Config{
|
||||
@@ -445,8 +473,13 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFi
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
client = http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsConfig},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
c.Assert(err, checker.NotNil)
|
||||
}
|
||||
|
||||
func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) {
|
||||
|
||||
@@ -25,11 +25,6 @@ var host = flag.Bool("host", false, "run host integration tests")
|
||||
var showLog = flag.Bool("tlog", false, "always show Traefik logs")
|
||||
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
if !*integration {
|
||||
log.Info("Integration tests disabled.")
|
||||
return
|
||||
@@ -70,6 +65,8 @@ func init() {
|
||||
check.Suite(&ProxyProtocolSuite{})
|
||||
check.Suite(&Etcd3Suite{})
|
||||
}
|
||||
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
||||
@@ -57,7 +57,12 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
return privateKey
|
||||
}
|
||||
|
||||
log.Errorf("Cannot unmarshal private key %+v", a.PrivateKey)
|
||||
keySnippet := ""
|
||||
if len(a.PrivateKey) >= 16 {
|
||||
keySnippet = string(a.PrivateKey[:16])
|
||||
}
|
||||
|
||||
log.Errorf("Cannot unmarshall private key beginning with %s", keySnippet)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package kubernetes
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
const defaultPercentageValuePrecision = 3
|
||||
@@ -43,5 +45,6 @@ func newPercentageValueFromString(rawValue string) (percentageValue, error) {
|
||||
|
||||
// newPercentageValueFromFloat64 reads percentage value from float64
|
||||
func newPercentageValueFromFloat64(f float64) percentageValue {
|
||||
return percentageValue(f * (1000 * 100))
|
||||
value := decimal.NewFromFloat(f).Mul(decimal.NewFromFloat(1000 * 100))
|
||||
return percentageValue(value.IntPart())
|
||||
}
|
||||
|
||||
@@ -74,46 +74,54 @@ func TestNewPercentageValueFromString(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
value: "1%",
|
||||
expectError: false,
|
||||
expectedString: "1.000%",
|
||||
expectedFloat64: 0.01,
|
||||
},
|
||||
{
|
||||
value: "0.5",
|
||||
expectError: false,
|
||||
expectedString: "0.500%",
|
||||
expectedFloat64: 0.005,
|
||||
},
|
||||
{
|
||||
value: "99%",
|
||||
expectError: false,
|
||||
expectedString: "99.000%",
|
||||
expectedFloat64: 0.99,
|
||||
},
|
||||
{
|
||||
value: "99.9%",
|
||||
expectError: false,
|
||||
expectedString: "99.900%",
|
||||
expectedFloat64: 0.999,
|
||||
},
|
||||
{
|
||||
value: "-99.9%",
|
||||
expectError: false,
|
||||
expectedString: "-99.900%",
|
||||
expectedFloat64: -0.999,
|
||||
},
|
||||
{
|
||||
value: "-99.99999%",
|
||||
expectError: false,
|
||||
expectedString: "-99.999%",
|
||||
expectedFloat64: -0.99999,
|
||||
},
|
||||
{
|
||||
value: "0%",
|
||||
expectError: false,
|
||||
expectedString: "0.000%",
|
||||
expectedFloat64: 0,
|
||||
},
|
||||
{
|
||||
value: "2.3%",
|
||||
expectedString: "2.300%",
|
||||
expectedFloat64: 0.023,
|
||||
},
|
||||
{
|
||||
value: "5.1%",
|
||||
expectedString: "5.100%",
|
||||
expectedFloat64: 0.051,
|
||||
},
|
||||
{
|
||||
value: "83.85%",
|
||||
expectedString: "83.850%",
|
||||
expectedFloat64: 0.83850,
|
||||
},
|
||||
{
|
||||
value: "%",
|
||||
expectError: true,
|
||||
|
||||
@@ -86,6 +86,7 @@ func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]by
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageDataEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
}
|
||||
|
||||
45
vendor/github.com/shopspring/decimal/LICENSE
generated
vendored
Normal file
45
vendor/github.com/shopspring/decimal/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Spring, Inc.
|
||||
|
||||
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.
|
||||
|
||||
- Based on https://github.com/oguzbilgic/fpd, which has the following license:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Oguz Bilgic
|
||||
|
||||
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.
|
||||
"""
|
||||
414
vendor/github.com/shopspring/decimal/decimal-go.go
generated
vendored
Normal file
414
vendor/github.com/shopspring/decimal/decimal-go.go
generated
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
// Multiprecision decimal numbers.
|
||||
// For floating-point formatting only; not general purpose.
|
||||
// Only operations are assign and (binary) left/right shift.
|
||||
// Can do binary floating point in multiprecision decimal precisely
|
||||
// because 2 divides 10; cannot do decimal floating point
|
||||
// in multiprecision binary precisely.
|
||||
package decimal
|
||||
|
||||
type decimal struct {
|
||||
d [800]byte // digits, big-endian representation
|
||||
nd int // number of digits used
|
||||
dp int // decimal point
|
||||
neg bool // negative flag
|
||||
trunc bool // discarded nonzero digits beyond d[:nd]
|
||||
}
|
||||
|
||||
func (a *decimal) String() string {
|
||||
n := 10 + a.nd
|
||||
if a.dp > 0 {
|
||||
n += a.dp
|
||||
}
|
||||
if a.dp < 0 {
|
||||
n += -a.dp
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
w := 0
|
||||
switch {
|
||||
case a.nd == 0:
|
||||
return "0"
|
||||
|
||||
case a.dp <= 0:
|
||||
// zeros fill space between decimal point and digits
|
||||
buf[w] = '0'
|
||||
w++
|
||||
buf[w] = '.'
|
||||
w++
|
||||
w += digitZero(buf[w : w+-a.dp])
|
||||
w += copy(buf[w:], a.d[0:a.nd])
|
||||
|
||||
case a.dp < a.nd:
|
||||
// decimal point in middle of digits
|
||||
w += copy(buf[w:], a.d[0:a.dp])
|
||||
buf[w] = '.'
|
||||
w++
|
||||
w += copy(buf[w:], a.d[a.dp:a.nd])
|
||||
|
||||
default:
|
||||
// zeros fill space between digits and decimal point
|
||||
w += copy(buf[w:], a.d[0:a.nd])
|
||||
w += digitZero(buf[w : w+a.dp-a.nd])
|
||||
}
|
||||
return string(buf[0:w])
|
||||
}
|
||||
|
||||
func digitZero(dst []byte) int {
|
||||
for i := range dst {
|
||||
dst[i] = '0'
|
||||
}
|
||||
return len(dst)
|
||||
}
|
||||
|
||||
// trim trailing zeros from number.
|
||||
// (They are meaningless; the decimal point is tracked
|
||||
// independent of the number of digits.)
|
||||
func trim(a *decimal) {
|
||||
for a.nd > 0 && a.d[a.nd-1] == '0' {
|
||||
a.nd--
|
||||
}
|
||||
if a.nd == 0 {
|
||||
a.dp = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Assign v to a.
|
||||
func (a *decimal) Assign(v uint64) {
|
||||
var buf [24]byte
|
||||
|
||||
// Write reversed decimal in buf.
|
||||
n := 0
|
||||
for v > 0 {
|
||||
v1 := v / 10
|
||||
v -= 10 * v1
|
||||
buf[n] = byte(v + '0')
|
||||
n++
|
||||
v = v1
|
||||
}
|
||||
|
||||
// Reverse again to produce forward decimal in a.d.
|
||||
a.nd = 0
|
||||
for n--; n >= 0; n-- {
|
||||
a.d[a.nd] = buf[n]
|
||||
a.nd++
|
||||
}
|
||||
a.dp = a.nd
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Maximum shift that we can do in one pass without overflow.
|
||||
// A uint has 32 or 64 bits, and we have to be able to accommodate 9<<k.
|
||||
const uintSize = 32 << (^uint(0) >> 63)
|
||||
const maxShift = uintSize - 4
|
||||
|
||||
// Binary shift right (/ 2) by k bits. k <= maxShift to avoid overflow.
|
||||
func rightShift(a *decimal, k uint) {
|
||||
r := 0 // read pointer
|
||||
w := 0 // write pointer
|
||||
|
||||
// Pick up enough leading digits to cover first shift.
|
||||
var n uint
|
||||
for ; n>>k == 0; r++ {
|
||||
if r >= a.nd {
|
||||
if n == 0 {
|
||||
// a == 0; shouldn't get here, but handle anyway.
|
||||
a.nd = 0
|
||||
return
|
||||
}
|
||||
for n>>k == 0 {
|
||||
n = n * 10
|
||||
r++
|
||||
}
|
||||
break
|
||||
}
|
||||
c := uint(a.d[r])
|
||||
n = n*10 + c - '0'
|
||||
}
|
||||
a.dp -= r - 1
|
||||
|
||||
var mask uint = (1 << k) - 1
|
||||
|
||||
// Pick up a digit, put down a digit.
|
||||
for ; r < a.nd; r++ {
|
||||
c := uint(a.d[r])
|
||||
dig := n >> k
|
||||
n &= mask
|
||||
a.d[w] = byte(dig + '0')
|
||||
w++
|
||||
n = n*10 + c - '0'
|
||||
}
|
||||
|
||||
// Put down extra digits.
|
||||
for n > 0 {
|
||||
dig := n >> k
|
||||
n &= mask
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(dig + '0')
|
||||
w++
|
||||
} else if dig > 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = n * 10
|
||||
}
|
||||
|
||||
a.nd = w
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Cheat sheet for left shift: table indexed by shift count giving
|
||||
// number of new digits that will be introduced by that shift.
|
||||
//
|
||||
// For example, leftcheats[4] = {2, "625"}. That means that
|
||||
// if we are shifting by 4 (multiplying by 16), it will add 2 digits
|
||||
// when the string prefix is "625" through "999", and one fewer digit
|
||||
// if the string prefix is "000" through "624".
|
||||
//
|
||||
// Credit for this trick goes to Ken.
|
||||
|
||||
type leftCheat struct {
|
||||
delta int // number of new digits
|
||||
cutoff string // minus one digit if original < a.
|
||||
}
|
||||
|
||||
var leftcheats = []leftCheat{
|
||||
// Leading digits of 1/2^i = 5^i.
|
||||
// 5^23 is not an exact 64-bit floating point number,
|
||||
// so have to use bc for the math.
|
||||
// Go up to 60 to be large enough for 32bit and 64bit platforms.
|
||||
/*
|
||||
seq 60 | sed 's/^/5^/' | bc |
|
||||
awk 'BEGIN{ print "\t{ 0, \"\" }," }
|
||||
{
|
||||
log2 = log(2)/log(10)
|
||||
printf("\t{ %d, \"%s\" },\t// * %d\n",
|
||||
int(log2*NR+1), $0, 2**NR)
|
||||
}'
|
||||
*/
|
||||
{0, ""},
|
||||
{1, "5"}, // * 2
|
||||
{1, "25"}, // * 4
|
||||
{1, "125"}, // * 8
|
||||
{2, "625"}, // * 16
|
||||
{2, "3125"}, // * 32
|
||||
{2, "15625"}, // * 64
|
||||
{3, "78125"}, // * 128
|
||||
{3, "390625"}, // * 256
|
||||
{3, "1953125"}, // * 512
|
||||
{4, "9765625"}, // * 1024
|
||||
{4, "48828125"}, // * 2048
|
||||
{4, "244140625"}, // * 4096
|
||||
{4, "1220703125"}, // * 8192
|
||||
{5, "6103515625"}, // * 16384
|
||||
{5, "30517578125"}, // * 32768
|
||||
{5, "152587890625"}, // * 65536
|
||||
{6, "762939453125"}, // * 131072
|
||||
{6, "3814697265625"}, // * 262144
|
||||
{6, "19073486328125"}, // * 524288
|
||||
{7, "95367431640625"}, // * 1048576
|
||||
{7, "476837158203125"}, // * 2097152
|
||||
{7, "2384185791015625"}, // * 4194304
|
||||
{7, "11920928955078125"}, // * 8388608
|
||||
{8, "59604644775390625"}, // * 16777216
|
||||
{8, "298023223876953125"}, // * 33554432
|
||||
{8, "1490116119384765625"}, // * 67108864
|
||||
{9, "7450580596923828125"}, // * 134217728
|
||||
{9, "37252902984619140625"}, // * 268435456
|
||||
{9, "186264514923095703125"}, // * 536870912
|
||||
{10, "931322574615478515625"}, // * 1073741824
|
||||
{10, "4656612873077392578125"}, // * 2147483648
|
||||
{10, "23283064365386962890625"}, // * 4294967296
|
||||
{10, "116415321826934814453125"}, // * 8589934592
|
||||
{11, "582076609134674072265625"}, // * 17179869184
|
||||
{11, "2910383045673370361328125"}, // * 34359738368
|
||||
{11, "14551915228366851806640625"}, // * 68719476736
|
||||
{12, "72759576141834259033203125"}, // * 137438953472
|
||||
{12, "363797880709171295166015625"}, // * 274877906944
|
||||
{12, "1818989403545856475830078125"}, // * 549755813888
|
||||
{13, "9094947017729282379150390625"}, // * 1099511627776
|
||||
{13, "45474735088646411895751953125"}, // * 2199023255552
|
||||
{13, "227373675443232059478759765625"}, // * 4398046511104
|
||||
{13, "1136868377216160297393798828125"}, // * 8796093022208
|
||||
{14, "5684341886080801486968994140625"}, // * 17592186044416
|
||||
{14, "28421709430404007434844970703125"}, // * 35184372088832
|
||||
{14, "142108547152020037174224853515625"}, // * 70368744177664
|
||||
{15, "710542735760100185871124267578125"}, // * 140737488355328
|
||||
{15, "3552713678800500929355621337890625"}, // * 281474976710656
|
||||
{15, "17763568394002504646778106689453125"}, // * 562949953421312
|
||||
{16, "88817841970012523233890533447265625"}, // * 1125899906842624
|
||||
{16, "444089209850062616169452667236328125"}, // * 2251799813685248
|
||||
{16, "2220446049250313080847263336181640625"}, // * 4503599627370496
|
||||
{16, "11102230246251565404236316680908203125"}, // * 9007199254740992
|
||||
{17, "55511151231257827021181583404541015625"}, // * 18014398509481984
|
||||
{17, "277555756156289135105907917022705078125"}, // * 36028797018963968
|
||||
{17, "1387778780781445675529539585113525390625"}, // * 72057594037927936
|
||||
{18, "6938893903907228377647697925567626953125"}, // * 144115188075855872
|
||||
{18, "34694469519536141888238489627838134765625"}, // * 288230376151711744
|
||||
{18, "173472347597680709441192448139190673828125"}, // * 576460752303423488
|
||||
{19, "867361737988403547205962240695953369140625"}, // * 1152921504606846976
|
||||
}
|
||||
|
||||
// Is the leading prefix of b lexicographically less than s?
|
||||
func prefixIsLessThan(b []byte, s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if i >= len(b) {
|
||||
return true
|
||||
}
|
||||
if b[i] != s[i] {
|
||||
return b[i] < s[i]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Binary shift left (* 2) by k bits. k <= maxShift to avoid overflow.
|
||||
func leftShift(a *decimal, k uint) {
|
||||
delta := leftcheats[k].delta
|
||||
if prefixIsLessThan(a.d[0:a.nd], leftcheats[k].cutoff) {
|
||||
delta--
|
||||
}
|
||||
|
||||
r := a.nd // read index
|
||||
w := a.nd + delta // write index
|
||||
|
||||
// Pick up a digit, put down a digit.
|
||||
var n uint
|
||||
for r--; r >= 0; r-- {
|
||||
n += (uint(a.d[r]) - '0') << k
|
||||
quo := n / 10
|
||||
rem := n - 10*quo
|
||||
w--
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(rem + '0')
|
||||
} else if rem != 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = quo
|
||||
}
|
||||
|
||||
// Put down extra digits.
|
||||
for n > 0 {
|
||||
quo := n / 10
|
||||
rem := n - 10*quo
|
||||
w--
|
||||
if w < len(a.d) {
|
||||
a.d[w] = byte(rem + '0')
|
||||
} else if rem != 0 {
|
||||
a.trunc = true
|
||||
}
|
||||
n = quo
|
||||
}
|
||||
|
||||
a.nd += delta
|
||||
if a.nd >= len(a.d) {
|
||||
a.nd = len(a.d)
|
||||
}
|
||||
a.dp += delta
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Binary shift left (k > 0) or right (k < 0).
|
||||
func (a *decimal) Shift(k int) {
|
||||
switch {
|
||||
case a.nd == 0:
|
||||
// nothing to do: a == 0
|
||||
case k > 0:
|
||||
for k > maxShift {
|
||||
leftShift(a, maxShift)
|
||||
k -= maxShift
|
||||
}
|
||||
leftShift(a, uint(k))
|
||||
case k < 0:
|
||||
for k < -maxShift {
|
||||
rightShift(a, maxShift)
|
||||
k += maxShift
|
||||
}
|
||||
rightShift(a, uint(-k))
|
||||
}
|
||||
}
|
||||
|
||||
// If we chop a at nd digits, should we round up?
|
||||
func shouldRoundUp(a *decimal, nd int) bool {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return false
|
||||
}
|
||||
if a.d[nd] == '5' && nd+1 == a.nd { // exactly halfway - round to even
|
||||
// if we truncated, a little higher than what's recorded - always round up
|
||||
if a.trunc {
|
||||
return true
|
||||
}
|
||||
return nd > 0 && (a.d[nd-1]-'0')%2 != 0
|
||||
}
|
||||
// not halfway - digit tells all
|
||||
return a.d[nd] >= '5'
|
||||
}
|
||||
|
||||
// Round a to nd digits (or fewer).
|
||||
// If nd is zero, it means we're rounding
|
||||
// just to the left of the digits, as in
|
||||
// 0.09 -> 0.1.
|
||||
func (a *decimal) Round(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
if shouldRoundUp(a, nd) {
|
||||
a.RoundUp(nd)
|
||||
} else {
|
||||
a.RoundDown(nd)
|
||||
}
|
||||
}
|
||||
|
||||
// Round a down to nd digits (or fewer).
|
||||
func (a *decimal) RoundDown(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
a.nd = nd
|
||||
trim(a)
|
||||
}
|
||||
|
||||
// Round a up to nd digits (or fewer).
|
||||
func (a *decimal) RoundUp(nd int) {
|
||||
if nd < 0 || nd >= a.nd {
|
||||
return
|
||||
}
|
||||
|
||||
// round up
|
||||
for i := nd - 1; i >= 0; i-- {
|
||||
c := a.d[i]
|
||||
if c < '9' { // can stop after this digit
|
||||
a.d[i]++
|
||||
a.nd = i + 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Number is all 9s.
|
||||
// Change to single 1 with adjusted decimal point.
|
||||
a.d[0] = '1'
|
||||
a.nd = 1
|
||||
a.dp++
|
||||
}
|
||||
|
||||
// Extract integer part, rounded appropriately.
|
||||
// No guarantees about overflow.
|
||||
func (a *decimal) RoundedInteger() uint64 {
|
||||
if a.dp > 20 {
|
||||
return 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
var i int
|
||||
n := uint64(0)
|
||||
for i = 0; i < a.dp && i < a.nd; i++ {
|
||||
n = n*10 + uint64(a.d[i]-'0')
|
||||
}
|
||||
for ; i < a.dp; i++ {
|
||||
n *= 10
|
||||
}
|
||||
if shouldRoundUp(a, a.dp) {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
1438
vendor/github.com/shopspring/decimal/decimal.go
generated
vendored
Normal file
1438
vendor/github.com/shopspring/decimal/decimal.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
47
vendor/github.com/shopspring/decimal/decomposer.go
generated
vendored
Normal file
47
vendor/github.com/shopspring/decimal/decomposer.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package decimal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Decompose returns the internal decimal state into parts.
|
||||
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
|
||||
// the value set and length set as appropriate.
|
||||
func (d Decimal) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
|
||||
negative = d.value.Sign() < 0
|
||||
exponent = d.exp
|
||||
coefficient = d.value.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
decomposeFinite = 0
|
||||
decomposeInfinite = 1
|
||||
decomposeNaN = 2
|
||||
)
|
||||
|
||||
// Compose sets the internal decimal value from parts. If the value cannot be
|
||||
// represented then an error should be returned.
|
||||
func (d *Decimal) Compose(form byte, negative bool, coefficient []byte, exponent int32) error {
|
||||
switch form {
|
||||
default:
|
||||
return fmt.Errorf("unknown form: %v", form)
|
||||
case decomposeFinite:
|
||||
// Set rest of finite form below.
|
||||
case decomposeInfinite:
|
||||
return fmt.Errorf("Infinite form not supported")
|
||||
case decomposeNaN:
|
||||
return fmt.Errorf("NaN form not supported")
|
||||
}
|
||||
// Finite form.
|
||||
if d.value == nil {
|
||||
d.value = &big.Int{}
|
||||
}
|
||||
d.value.SetBytes(coefficient)
|
||||
if negative && d.value.Sign() >= 0 {
|
||||
d.value.Neg(d.value)
|
||||
}
|
||||
d.exp = exponent
|
||||
return nil
|
||||
}
|
||||
118
vendor/github.com/shopspring/decimal/rounding.go
generated
vendored
Normal file
118
vendor/github.com/shopspring/decimal/rounding.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
// Multiprecision decimal numbers.
|
||||
// For floating-point formatting only; not general purpose.
|
||||
// Only operations are assign and (binary) left/right shift.
|
||||
// Can do binary floating point in multiprecision decimal precisely
|
||||
// because 2 divides 10; cannot do decimal floating point
|
||||
// in multiprecision binary precisely.
|
||||
package decimal
|
||||
|
||||
type floatInfo struct {
|
||||
mantbits uint
|
||||
expbits uint
|
||||
bias int
|
||||
}
|
||||
|
||||
var float32info = floatInfo{23, 8, -127}
|
||||
var float64info = floatInfo{52, 11, -1023}
|
||||
|
||||
// roundShortest rounds d (= mant * 2^exp) to the shortest number of digits
|
||||
// that will let the original floating point value be precisely reconstructed.
|
||||
func roundShortest(d *decimal, mant uint64, exp int, flt *floatInfo) {
|
||||
// If mantissa is zero, the number is zero; stop now.
|
||||
if mant == 0 {
|
||||
d.nd = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Compute upper and lower such that any decimal number
|
||||
// between upper and lower (possibly inclusive)
|
||||
// will round to the original floating point number.
|
||||
|
||||
// We may see at once that the number is already shortest.
|
||||
//
|
||||
// Suppose d is not denormal, so that 2^exp <= d < 10^dp.
|
||||
// The closest shorter number is at least 10^(dp-nd) away.
|
||||
// The lower/upper bounds computed below are at distance
|
||||
// at most 2^(exp-mantbits).
|
||||
//
|
||||
// So the number is already shortest if 10^(dp-nd) > 2^(exp-mantbits),
|
||||
// or equivalently log2(10)*(dp-nd) > exp-mantbits.
|
||||
// It is true if 332/100*(dp-nd) >= exp-mantbits (log2(10) > 3.32).
|
||||
minexp := flt.bias + 1 // minimum possible exponent
|
||||
if exp > minexp && 332*(d.dp-d.nd) >= 100*(exp-int(flt.mantbits)) {
|
||||
// The number is already shortest.
|
||||
return
|
||||
}
|
||||
|
||||
// d = mant << (exp - mantbits)
|
||||
// Next highest floating point number is mant+1 << exp-mantbits.
|
||||
// Our upper bound is halfway between, mant*2+1 << exp-mantbits-1.
|
||||
upper := new(decimal)
|
||||
upper.Assign(mant*2 + 1)
|
||||
upper.Shift(exp - int(flt.mantbits) - 1)
|
||||
|
||||
// d = mant << (exp - mantbits)
|
||||
// Next lowest floating point number is mant-1 << exp-mantbits,
|
||||
// unless mant-1 drops the significant bit and exp is not the minimum exp,
|
||||
// in which case the next lowest is mant*2-1 << exp-mantbits-1.
|
||||
// Either way, call it mantlo << explo-mantbits.
|
||||
// Our lower bound is halfway between, mantlo*2+1 << explo-mantbits-1.
|
||||
var mantlo uint64
|
||||
var explo int
|
||||
if mant > 1<<flt.mantbits || exp == minexp {
|
||||
mantlo = mant - 1
|
||||
explo = exp
|
||||
} else {
|
||||
mantlo = mant*2 - 1
|
||||
explo = exp - 1
|
||||
}
|
||||
lower := new(decimal)
|
||||
lower.Assign(mantlo*2 + 1)
|
||||
lower.Shift(explo - int(flt.mantbits) - 1)
|
||||
|
||||
// The upper and lower bounds are possible outputs only if
|
||||
// the original mantissa is even, so that IEEE round-to-even
|
||||
// would round to the original mantissa and not the neighbors.
|
||||
inclusive := mant%2 == 0
|
||||
|
||||
// Now we can figure out the minimum number of digits required.
|
||||
// Walk along until d has distinguished itself from upper and lower.
|
||||
for i := 0; i < d.nd; i++ {
|
||||
l := byte('0') // lower digit
|
||||
if i < lower.nd {
|
||||
l = lower.d[i]
|
||||
}
|
||||
m := d.d[i] // middle digit
|
||||
u := byte('0') // upper digit
|
||||
if i < upper.nd {
|
||||
u = upper.d[i]
|
||||
}
|
||||
|
||||
// Okay to round down (truncate) if lower has a different digit
|
||||
// or if lower is inclusive and is exactly the result of rounding
|
||||
// down (i.e., and we have reached the final digit of lower).
|
||||
okdown := l != m || inclusive && i+1 == lower.nd
|
||||
|
||||
// Okay to round up if upper has a different digit and either upper
|
||||
// is inclusive or upper is bigger than the result of rounding up.
|
||||
okup := m != u && (inclusive || m+1 < u || i+1 < upper.nd)
|
||||
|
||||
// If it's okay to do either, then round to the nearest one.
|
||||
// If it's okay to do only one, do it.
|
||||
switch {
|
||||
case okdown && okup:
|
||||
d.Round(i + 1)
|
||||
return
|
||||
case okdown:
|
||||
d.RoundDown(i + 1)
|
||||
return
|
||||
case okup:
|
||||
d.RoundUp(i + 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
163
vendor/github.com/unrolled/secure/secure.go
generated
vendored
163
vendor/github.com/unrolled/secure/secure.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -11,7 +12,7 @@ type secureCtxKey string
|
||||
|
||||
const (
|
||||
stsHeader = "Strict-Transport-Security"
|
||||
stsSubdomainString = "; includeSubdomains"
|
||||
stsSubdomainString = "; includeSubDomains"
|
||||
stsPreloadString = "; preload"
|
||||
frameOptionsHeader = "X-Frame-Options"
|
||||
frameOptionsValue = "DENY"
|
||||
@@ -20,8 +21,11 @@ const (
|
||||
xssProtectionHeader = "X-XSS-Protection"
|
||||
xssProtectionValue = "1; mode=block"
|
||||
cspHeader = "Content-Security-Policy"
|
||||
cspReportOnlyHeader = "Content-Security-Policy-Report-Only"
|
||||
hpkpHeader = "Public-Key-Pins"
|
||||
referrerPolicyHeader = "Referrer-Policy"
|
||||
featurePolicyHeader = "Feature-Policy"
|
||||
expectCTHeader = "Expect-CT"
|
||||
|
||||
ctxSecureHeaderKey = secureCtxKey("SecureResponseHeader")
|
||||
cspNonceSize = 16
|
||||
@@ -61,6 +65,8 @@ type Options struct {
|
||||
STSPreload bool
|
||||
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
|
||||
ContentSecurityPolicy string
|
||||
// ContentSecurityPolicyReportOnly allows the Content-Security-Policy-Report-Only header value to be set with a custom value. Default is "".
|
||||
ContentSecurityPolicyReportOnly string
|
||||
// CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "".
|
||||
CustomBrowserXssValue string // nolint: golint
|
||||
// Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be later retrieved using the Nonce function.
|
||||
@@ -71,10 +77,15 @@ type Options struct {
|
||||
PublicKey string
|
||||
// ReferrerPolicy allows sites to control when browsers will pass the Referer header to other sites. Default is "".
|
||||
ReferrerPolicy string
|
||||
// FeaturePolicy allows to selectively enable and disable use of various browser features and APIs. Default is "".
|
||||
FeaturePolicy string
|
||||
// SSLHost is the host name that is used to redirect http requests to https. Default is "", which indicates to use the same host.
|
||||
SSLHost string
|
||||
// AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
|
||||
AllowedHosts []string
|
||||
// AllowedHostsAreRegex determines, if the provided slice contains valid regular expressions. If this flag is set to true, every request's
|
||||
// host will be checked against these expressions. Default is false for backwards compatibility.
|
||||
AllowedHostsAreRegex bool
|
||||
// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
|
||||
HostsProxyHeaders []string
|
||||
// SSLHostFunc is a function pointer, the return value of the function is the host name that has same functionality as `SSHost`. Default is nil.
|
||||
@@ -84,6 +95,8 @@ type Options struct {
|
||||
SSLProxyHeaders map[string]string
|
||||
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
|
||||
STSSeconds int64
|
||||
// ExpectCTHeader allows the Expect-CT header value to be set with a custom value. Default is "".
|
||||
ExpectCTHeader string
|
||||
}
|
||||
|
||||
// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
|
||||
@@ -94,6 +107,10 @@ type Secure struct {
|
||||
|
||||
// badHostHandler is the handler used when an incorrect host is passed in.
|
||||
badHostHandler http.Handler
|
||||
|
||||
// cRegexAllowedHosts saves the compiled regular expressions of the AllowedHosts
|
||||
// option for subsequent use in processRequest
|
||||
cRegexAllowedHosts []*regexp.Regexp
|
||||
}
|
||||
|
||||
// New constructs a new Secure instance with the supplied options.
|
||||
@@ -106,13 +123,27 @@ func New(options ...Options) *Secure {
|
||||
}
|
||||
|
||||
o.ContentSecurityPolicy = strings.Replace(o.ContentSecurityPolicy, "$NONCE", "'nonce-%[1]s'", -1)
|
||||
o.ContentSecurityPolicyReportOnly = strings.Replace(o.ContentSecurityPolicyReportOnly, "$NONCE", "'nonce-%[1]s'", -1)
|
||||
|
||||
o.nonceEnabled = strings.Contains(o.ContentSecurityPolicy, "%[1]s")
|
||||
o.nonceEnabled = strings.Contains(o.ContentSecurityPolicy, "%[1]s") || strings.Contains(o.ContentSecurityPolicyReportOnly, "%[1]s")
|
||||
|
||||
return &Secure{
|
||||
s := &Secure{
|
||||
opt: o,
|
||||
badHostHandler: http.HandlerFunc(defaultBadHostHandler),
|
||||
}
|
||||
|
||||
if s.opt.AllowedHostsAreRegex {
|
||||
// Test for invalid regular expressions in AllowedHosts
|
||||
for _, allowedHost := range o.AllowedHosts {
|
||||
regex, err := regexp.Compile(fmt.Sprintf("^%s$", allowedHost))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error parsing AllowedHost: %s", err))
|
||||
}
|
||||
s.cRegexAllowedHosts = append(s.cRegexAllowedHosts, regex)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// SetBadHostHandler sets the handler to call when secure rejects the host name.
|
||||
@@ -123,13 +154,10 @@ func (s *Secure) SetBadHostHandler(handler http.Handler) {
|
||||
// Handler implements the http.HandlerFunc for integration with the standard net/http lib.
|
||||
func (s *Secure) Handler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.opt.nonceEnabled {
|
||||
r = withCSPNonce(r, cspRandNonce())
|
||||
}
|
||||
|
||||
// Let secure process the request. If it returns an error,
|
||||
// that indicates the request should not continue.
|
||||
err := s.Process(w, r)
|
||||
responseHeader, r, err := s.processRequest(w, r)
|
||||
addResponseHeaders(responseHeader, w)
|
||||
|
||||
// If there was an error, do not continue.
|
||||
if err != nil {
|
||||
@@ -144,13 +172,9 @@ func (s *Secure) Handler(h http.Handler) http.Handler {
|
||||
// Note that this is for requests only and will not write any headers.
|
||||
func (s *Secure) HandlerForRequestOnly(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.opt.nonceEnabled {
|
||||
r = withCSPNonce(r, cspRandNonce())
|
||||
}
|
||||
|
||||
// Let secure process the request. If it returns an error,
|
||||
// that indicates the request should not continue.
|
||||
responseHeader, err := s.processRequest(w, r)
|
||||
responseHeader, r, err := s.processRequest(w, r)
|
||||
|
||||
// If there was an error, do not continue.
|
||||
if err != nil {
|
||||
@@ -167,13 +191,10 @@ func (s *Secure) HandlerForRequestOnly(h http.Handler) http.Handler {
|
||||
|
||||
// HandlerFuncWithNext is a special implementation for Negroni, but could be used elsewhere.
|
||||
func (s *Secure) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if s.opt.nonceEnabled {
|
||||
r = withCSPNonce(r, cspRandNonce())
|
||||
}
|
||||
|
||||
// Let secure process the request. If it returns an error,
|
||||
// that indicates the request should not continue.
|
||||
err := s.Process(w, r)
|
||||
responseHeader, r, err := s.processRequest(w, r)
|
||||
addResponseHeaders(responseHeader, w)
|
||||
|
||||
// If there was an error, do not call next.
|
||||
if err == nil && next != nil {
|
||||
@@ -184,13 +205,9 @@ func (s *Secure) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, nex
|
||||
// HandlerFuncWithNextForRequestOnly is a special implementation for Negroni, but could be used elsewhere.
|
||||
// Note that this is for requests only and will not write any headers.
|
||||
func (s *Secure) HandlerFuncWithNextForRequestOnly(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if s.opt.nonceEnabled {
|
||||
r = withCSPNonce(r, cspRandNonce())
|
||||
}
|
||||
|
||||
// Let secure process the request. If it returns an error,
|
||||
// that indicates the request should not continue.
|
||||
responseHeader, err := s.processRequest(w, r)
|
||||
responseHeader, r, err := s.processRequest(w, r)
|
||||
|
||||
// If there was an error, do not call next.
|
||||
if err == nil && next != nil {
|
||||
@@ -202,21 +219,44 @@ func (s *Secure) HandlerFuncWithNextForRequestOnly(w http.ResponseWriter, r *htt
|
||||
}
|
||||
}
|
||||
|
||||
// Process runs the actual checks and writes the headers in the ResponseWriter.
|
||||
func (s *Secure) Process(w http.ResponseWriter, r *http.Request) error {
|
||||
responseHeader, err := s.processRequest(w, r)
|
||||
if responseHeader != nil {
|
||||
for key, values := range responseHeader {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
// addResponseHeaders Adds the headers from 'responseHeader' to the response.
|
||||
func addResponseHeaders(responseHeader http.Header, w http.ResponseWriter) {
|
||||
for key, values := range responseHeader {
|
||||
for _, value := range values {
|
||||
w.Header().Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process runs the actual checks and writes the headers in the ResponseWriter.
|
||||
func (s *Secure) Process(w http.ResponseWriter, r *http.Request) error {
|
||||
responseHeader, _, err := s.processRequest(w, r)
|
||||
addResponseHeaders(responseHeader, w)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ProcessAndReturnNonce runs the actual checks and writes the headers in the ResponseWriter.
|
||||
// In addition, the generated nonce for the request is returned as well as the error value.
|
||||
func (s *Secure) ProcessAndReturnNonce(w http.ResponseWriter, r *http.Request) (string, error) {
|
||||
responseHeader, newR, err := s.processRequest(w, r)
|
||||
addResponseHeaders(responseHeader, w)
|
||||
|
||||
return CSPNonce(newR.Context()), err
|
||||
}
|
||||
|
||||
// ProcessNoModifyRequest runs the actual checks but does not write the headers in the ResponseWriter.
|
||||
func (s *Secure) ProcessNoModifyRequest(w http.ResponseWriter, r *http.Request) (http.Header, *http.Request, error) {
|
||||
return s.processRequest(w, r)
|
||||
}
|
||||
|
||||
// processRequest runs the actual checks on the request and returns an error if the middleware chain should stop.
|
||||
func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.Header, error) {
|
||||
func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.Header, *http.Request, error) {
|
||||
// Setup nonce if required.
|
||||
if s.opt.nonceEnabled {
|
||||
r = withCSPNonce(r, cspRandNonce())
|
||||
}
|
||||
|
||||
// Resolve the host for the request, using proxy headers if present.
|
||||
host := r.Host
|
||||
for _, header := range s.opt.HostsProxyHeaders {
|
||||
@@ -229,16 +269,25 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He
|
||||
// Allowed hosts check.
|
||||
if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment {
|
||||
isGoodHost := false
|
||||
for _, allowedHost := range s.opt.AllowedHosts {
|
||||
if strings.EqualFold(allowedHost, host) {
|
||||
isGoodHost = true
|
||||
break
|
||||
if s.opt.AllowedHostsAreRegex {
|
||||
for _, allowedHost := range s.cRegexAllowedHosts {
|
||||
if match := allowedHost.MatchString(host); match {
|
||||
isGoodHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, allowedHost := range s.opt.AllowedHosts {
|
||||
if strings.EqualFold(allowedHost, host) {
|
||||
isGoodHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isGoodHost {
|
||||
s.badHostHandler.ServeHTTP(w, r)
|
||||
return nil, fmt.Errorf("bad host name: %s", host)
|
||||
return nil, nil, fmt.Errorf("bad host name: %s", host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,11 +314,11 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He
|
||||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), status)
|
||||
return nil, fmt.Errorf("redirecting to HTTPS")
|
||||
return nil, nil, fmt.Errorf("redirecting to HTTPS")
|
||||
}
|
||||
|
||||
if s.opt.SSLForceHost {
|
||||
var SSLHost = host;
|
||||
var SSLHost = host
|
||||
if s.opt.SSLHostFunc != nil {
|
||||
if h := (*s.opt.SSLHostFunc)(host); len(h) > 0 {
|
||||
SSLHost = h
|
||||
@@ -288,7 +337,7 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He
|
||||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), status)
|
||||
return nil, fmt.Errorf("redirecting to HTTPS")
|
||||
return nil, nil, fmt.Errorf("redirecting to HTTPS")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +392,31 @@ func (s *Secure) processRequest(w http.ResponseWriter, r *http.Request) (http.He
|
||||
}
|
||||
}
|
||||
|
||||
// Content Security Policy Report Only header.
|
||||
if len(s.opt.ContentSecurityPolicyReportOnly) > 0 {
|
||||
if s.opt.nonceEnabled {
|
||||
responseHeader.Set(cspReportOnlyHeader, fmt.Sprintf(s.opt.ContentSecurityPolicyReportOnly, CSPNonce(r.Context())))
|
||||
} else {
|
||||
responseHeader.Set(cspReportOnlyHeader, s.opt.ContentSecurityPolicyReportOnly)
|
||||
}
|
||||
}
|
||||
|
||||
// Referrer Policy header.
|
||||
if len(s.opt.ReferrerPolicy) > 0 {
|
||||
responseHeader.Set(referrerPolicyHeader, s.opt.ReferrerPolicy)
|
||||
}
|
||||
|
||||
return responseHeader, nil
|
||||
// Feature Policy header.
|
||||
if len(s.opt.FeaturePolicy) > 0 {
|
||||
responseHeader.Set(featurePolicyHeader, s.opt.FeaturePolicy)
|
||||
}
|
||||
|
||||
// Expect-CT header.
|
||||
if len(s.opt.ExpectCTHeader) > 0 {
|
||||
responseHeader.Set(expectCTHeader, s.opt.ExpectCTHeader)
|
||||
}
|
||||
|
||||
return responseHeader, r, nil
|
||||
}
|
||||
|
||||
// isSSL determine if we are on HTTPS.
|
||||
@@ -369,6 +437,19 @@ func (s *Secure) isSSL(r *http.Request) bool {
|
||||
// Used by http.ReverseProxy.
|
||||
func (s *Secure) ModifyResponseHeaders(res *http.Response) error {
|
||||
if res != nil && res.Request != nil {
|
||||
// Fix Location response header http to https:
|
||||
// When SSL is enabled,
|
||||
// And SSLHost is defined,
|
||||
// And the response location header includes the SSLHost as the domain with a trailing slash,
|
||||
// Or an exact match to the SSLHost.
|
||||
location := res.Header.Get("Location")
|
||||
if s.isSSL(res.Request) &&
|
||||
len(s.opt.SSLHost) > 0 &&
|
||||
(strings.HasPrefix(location, fmt.Sprintf("http://%s/", s.opt.SSLHost)) || location == fmt.Sprintf("http://%s", s.opt.SSLHost)) {
|
||||
location = strings.Replace(location, "http:", "https:", 1)
|
||||
res.Header.Set("Location", location)
|
||||
}
|
||||
|
||||
responseHeader := res.Request.Context().Value(ctxSecureHeaderKey)
|
||||
if responseHeader != nil {
|
||||
for header, values := range responseHeader.(http.Header) {
|
||||
|
||||
Reference in New Issue
Block a user