mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2025-03-04 02:14:40 +01:00
Update deps, include cachecontrol
This commit is contained in:
parent
ed60976dd2
commit
6b49470b95
23 changed files with 6195 additions and 4 deletions
21
Gopkg.lock
generated
21
Gopkg.lock
generated
|
@ -31,14 +31,23 @@
|
|||
packages = ["."]
|
||||
revision = "6cf43fdfd7a228cf3003ae23d10ddbf65e85997b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/agl/ed25519"
|
||||
packages = [
|
||||
".",
|
||||
"edwards25519"
|
||||
]
|
||||
revision = "5312a61534124124185d41f09206b9fef1d88403"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/coreos/go-systemd"
|
||||
packages = [
|
||||
"activation",
|
||||
"daemon"
|
||||
]
|
||||
revision = "d2196463941895ee908e13531a23a39feb9e1243"
|
||||
version = "v15"
|
||||
revision = "40e2722dffead74698ca12a750f64ef313ddce05"
|
||||
version = "v16"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -103,6 +112,12 @@
|
|||
revision = "5364553f1ee9cddc7ac8b62dce148309c386695b"
|
||||
version = "v1.0.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pquerna/cachecontrol"
|
||||
packages = ["cacheobject"]
|
||||
revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
@ -144,6 +159,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "7b78a3bd9d050e52892939c38491c43a50dcbd851b990fa570439be12a1bd8d2"
|
||||
inputs-digest = "8cae66c7ccdf2b62925b458f82a9c9855db9106c0ffe6d022b65fa0e4387a0c9"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
10
Gopkg.toml
10
Gopkg.toml
|
@ -10,9 +10,13 @@
|
|||
branch = "master"
|
||||
name = "github.com/VividCortex/godaemon"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/agl/ed25519"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/coreos/go-systemd"
|
||||
version = "15.0.0"
|
||||
version = "16.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
|
@ -46,6 +50,10 @@
|
|||
name = "github.com/miekg/dns"
|
||||
version = "1.0.4"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/pquerna/cachecontrol"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
|
27
vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
27
vendor/github.com/agl/ed25519/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
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.
|
127
vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
127
vendor/github.com/agl/ed25519/ed25519.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2013 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.
|
||||
|
||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||
// http://ed25519.cr.yp.to/.
|
||||
package ed25519
|
||||
|
||||
// This code is a port of the public domain, "ref10" implementation of ed25519
|
||||
// from SUPERCOP.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"io"
|
||||
|
||||
"github.com/agl/ed25519/edwards25519"
|
||||
)
|
||||
|
||||
const (
|
||||
PublicKeySize = 32
|
||||
PrivateKeySize = 64
|
||||
SignatureSize = 64
|
||||
)
|
||||
|
||||
// GenerateKey generates a public/private key pair using randomness from rand.
|
||||
func GenerateKey(rand io.Reader) (publicKey *[PublicKeySize]byte, privateKey *[PrivateKeySize]byte, err error) {
|
||||
privateKey = new([64]byte)
|
||||
publicKey = new([32]byte)
|
||||
_, err = io.ReadFull(rand, privateKey[:32])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
digest := h.Sum(nil)
|
||||
|
||||
digest[0] &= 248
|
||||
digest[31] &= 127
|
||||
digest[31] |= 64
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
var hBytes [32]byte
|
||||
copy(hBytes[:], digest)
|
||||
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||
A.ToBytes(publicKey)
|
||||
|
||||
copy(privateKey[32:], publicKey[:])
|
||||
return
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature.
|
||||
func Sign(privateKey *[PrivateKeySize]byte, message []byte) *[SignatureSize]byte {
|
||||
h := sha512.New()
|
||||
h.Write(privateKey[:32])
|
||||
|
||||
var digest1, messageDigest, hramDigest [64]byte
|
||||
var expandedSecretKey [32]byte
|
||||
h.Sum(digest1[:0])
|
||||
copy(expandedSecretKey[:], digest1[:])
|
||||
expandedSecretKey[0] &= 248
|
||||
expandedSecretKey[31] &= 63
|
||||
expandedSecretKey[31] |= 64
|
||||
|
||||
h.Reset()
|
||||
h.Write(digest1[32:])
|
||||
h.Write(message)
|
||||
h.Sum(messageDigest[:0])
|
||||
|
||||
var messageDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||
var R edwards25519.ExtendedGroupElement
|
||||
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||
|
||||
var encodedR [32]byte
|
||||
R.ToBytes(&encodedR)
|
||||
|
||||
h.Reset()
|
||||
h.Write(encodedR[:])
|
||||
h.Write(privateKey[32:])
|
||||
h.Write(message)
|
||||
h.Sum(hramDigest[:0])
|
||||
var hramDigestReduced [32]byte
|
||||
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||
|
||||
var s [32]byte
|
||||
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||
|
||||
signature := new([64]byte)
|
||||
copy(signature[:], encodedR[:])
|
||||
copy(signature[32:], s[:])
|
||||
return signature
|
||||
}
|
||||
|
||||
// Verify returns true iff sig is a valid signature of message by publicKey.
|
||||
func Verify(publicKey *[PublicKeySize]byte, message []byte, sig *[SignatureSize]byte) bool {
|
||||
if sig[63]&224 != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
if !A.FromBytes(publicKey) {
|
||||
return false
|
||||
}
|
||||
edwards25519.FeNeg(&A.X, &A.X)
|
||||
edwards25519.FeNeg(&A.T, &A.T)
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(sig[:32])
|
||||
h.Write(publicKey[:])
|
||||
h.Write(message)
|
||||
var digest [64]byte
|
||||
h.Sum(digest[:0])
|
||||
|
||||
var hReduced [32]byte
|
||||
edwards25519.ScReduce(&hReduced, &digest)
|
||||
|
||||
var R edwards25519.ProjectiveGroupElement
|
||||
var b [32]byte
|
||||
copy(b[:], sig[32:])
|
||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
||||
|
||||
var checkR [32]byte
|
||||
R.ToBytes(&checkR)
|
||||
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
||||
}
|
160
vendor/github.com/agl/ed25519/ed25519_test.go
generated
vendored
Normal file
160
vendor/github.com/agl/ed25519/ed25519_test.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2012 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.
|
||||
|
||||
package ed25519
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/agl/ed25519/edwards25519"
|
||||
)
|
||||
|
||||
type zeroReader struct{}
|
||||
|
||||
func (zeroReader) Read(buf []byte) (int, error) {
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestUnmarshalMarshal(t *testing.T) {
|
||||
pub, _, _ := GenerateKey(rand.Reader)
|
||||
|
||||
var A edwards25519.ExtendedGroupElement
|
||||
if !A.FromBytes(pub) {
|
||||
t.Fatalf("ExtendedGroupElement.FromBytes failed")
|
||||
}
|
||||
|
||||
var pub2 [32]byte
|
||||
A.ToBytes(&pub2)
|
||||
|
||||
if *pub != pub2 {
|
||||
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", *pub, pub2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignVerify(t *testing.T) {
|
||||
var zero zeroReader
|
||||
public, private, _ := GenerateKey(zero)
|
||||
|
||||
message := []byte("test message")
|
||||
sig := Sign(private, message)
|
||||
if !Verify(public, message, sig) {
|
||||
t.Errorf("valid signature rejected")
|
||||
}
|
||||
|
||||
wrongMessage := []byte("wrong message")
|
||||
if Verify(public, wrongMessage, sig) {
|
||||
t.Errorf("signature of different message accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGolden(t *testing.T) {
|
||||
// sign.input.gz is a selection of test cases from
|
||||
// http://ed25519.cr.yp.to/python/sign.input
|
||||
testDataZ, err := os.Open("testdata/sign.input.gz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testDataZ.Close()
|
||||
testData, err := gzip.NewReader(testDataZ)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testData.Close()
|
||||
|
||||
in := bufio.NewReaderSize(testData, 1<<12)
|
||||
lineNo := 0
|
||||
for {
|
||||
lineNo++
|
||||
lineBytes, isPrefix, err := in.ReadLine()
|
||||
if isPrefix {
|
||||
t.Fatal("bufio buffer too small")
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatalf("error reading test data: %s", err)
|
||||
}
|
||||
|
||||
line := string(lineBytes)
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) != 5 {
|
||||
t.Fatalf("bad number of parts on line %d", lineNo)
|
||||
}
|
||||
|
||||
privBytes, _ := hex.DecodeString(parts[0])
|
||||
pubKeyBytes, _ := hex.DecodeString(parts[1])
|
||||
msg, _ := hex.DecodeString(parts[2])
|
||||
sig, _ := hex.DecodeString(parts[3])
|
||||
// The signatures in the test vectors also include the message
|
||||
// at the end, but we just want R and S.
|
||||
sig = sig[:SignatureSize]
|
||||
|
||||
if l := len(pubKeyBytes); l != PublicKeySize {
|
||||
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
|
||||
}
|
||||
|
||||
var priv [PrivateKeySize]byte
|
||||
copy(priv[:], privBytes)
|
||||
copy(priv[32:], pubKeyBytes)
|
||||
|
||||
sig2 := Sign(&priv, msg)
|
||||
if !bytes.Equal(sig, sig2[:]) {
|
||||
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
|
||||
}
|
||||
|
||||
var pubKey [PublicKeySize]byte
|
||||
copy(pubKey[:], pubKeyBytes)
|
||||
if !Verify(&pubKey, msg, sig2) {
|
||||
t.Errorf("signature failed to verify on line %d", lineNo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyGeneration(b *testing.B) {
|
||||
var zero zeroReader
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, err := GenerateKey(zero); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSigning(b *testing.B) {
|
||||
var zero zeroReader
|
||||
_, priv, err := GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Sign(priv, message)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVerification(b *testing.B) {
|
||||
var zero zeroReader
|
||||
pub, priv, err := GenerateKey(zero)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
message := []byte("Hello, world!")
|
||||
signature := Sign(priv, message)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Verify(pub, message, signature)
|
||||
}
|
||||
}
|
1411
vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
1411
vendor/github.com/agl/ed25519/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1773
vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
1773
vendor/github.com/agl/ed25519/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
vendor/github.com/coreos/go-systemd/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/go-systemd/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
61
vendor/github.com/coreos/go-systemd/code-of-conduct.md
generated
vendored
Normal file
61
vendor/github.com/coreos/go-systemd/code-of-conduct.md
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
## CoreOS Community Code of Conduct
|
||||
|
||||
### Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating
|
||||
documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free
|
||||
experience for everyone, regardless of level of experience, gender, gender
|
||||
identity and expression, sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently applying these
|
||||
principles to every aspect of managing this project. Project maintainers who do
|
||||
not follow or enforce the Code of Conduct may be permanently removed from the
|
||||
project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting a project maintainer, Brandon Philips
|
||||
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant
|
||||
(http://contributor-covenant.org), version 1.2.0, available at
|
||||
http://contributor-covenant.org/version/1/2/0/
|
||||
|
||||
### CoreOS Events Code of Conduct
|
||||
|
||||
CoreOS events are working conferences intended for professional networking and
|
||||
collaboration in the CoreOS community. Attendees are expected to behave
|
||||
according to professional standards and in accordance with their employer’s
|
||||
policies on appropriate workplace behavior.
|
||||
|
||||
While at CoreOS events or related social networking opportunities, attendees
|
||||
should not engage in discriminatory or offensive speech or actions including
|
||||
but not limited to gender, sexuality, race, age, disability, or religion.
|
||||
Speakers should be especially aware of these concerns.
|
||||
|
||||
CoreOS does not condone any statements by speakers contrary to these standards.
|
||||
CoreOS reserves the right to deny entrance and/or eject from an event (without
|
||||
refund) any individual found to be engaging in discriminatory or offensive
|
||||
speech or actions.
|
||||
|
||||
Please bring any concerns to the immediate attention of designated on-site
|
||||
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
language: go
|
||||
|
||||
install:
|
||||
- go get -d -v ./...
|
||||
- go get -u github.com/stretchr/testify/require
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
# cachecontrol: HTTP Caching Parser and Interpretation
|
||||
|
||||
[](https://godoc.org/github.com/pquerna/cachecontrol)[](https://travis-ci.org/pquerna/cachecontrol)
|
||||
|
||||
|
||||
|
||||
`cachecontrol` implements [RFC 7234](http://tools.ietf.org/html/rfc7234) __Hypertext Transfer Protocol (HTTP/1.1): Caching__. It does this by parsing the `Cache-Control` and other headers, providing information about requests and responses -- but `cachecontrol` does not implement an actual cache backend, just the control plane to make decisions about if a particular response is cachable.
|
||||
|
||||
# Usage
|
||||
|
||||
`cachecontrol.CachableResponse` returns an array of [reasons](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Reason) why a response should not be cached and when it expires. In the case that `len(reasons) == 0`, the response is cachable according to the RFC. However, some people want non-compliant caches for various business use cases, so each reason is specifically named, so if your cache wants to cache `POST` requests, it can easily do that, but still be RFC compliant in other situations.
|
||||
|
||||
# Examples
|
||||
|
||||
## Can you cache Example.com?
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pquerna/cachecontrol"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
_, _ = ioutil.ReadAll(res.Body)
|
||||
|
||||
reasons, expires, _ := cachecontrol.CachableResponse(req, res, cachecontrol.Options{})
|
||||
|
||||
fmt.Println("Reasons to not cache: ", reasons)
|
||||
fmt.Println("Expiration: ", expires.String())
|
||||
}
|
||||
```
|
||||
|
||||
## Can I use this in a high performance caching server?
|
||||
|
||||
`cachecontrol` is divided into two packages: `cachecontrol` with a high level API, and a lower level `cacheobject` package. Use [Object](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Object) in a high performance use case where you have previously parsed headers containing dates or would like to avoid memory allocations.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pquerna/cachecontrol/cacheobject"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
_, _ = ioutil.ReadAll(res.Body)
|
||||
|
||||
reqDir, _ := cacheobject.ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||
|
||||
resDir, _ := cacheobject.ParseResponseCacheControl(res.Header.Get("Cache-Control"))
|
||||
expiresHeader, _ := http.ParseTime(res.Header.Get("Expires"))
|
||||
dateHeader, _ := http.ParseTime(res.Header.Get("Date"))
|
||||
lastModifiedHeader, _ := http.ParseTime(res.Header.Get("Last-Modified"))
|
||||
|
||||
obj := cacheobject.Object{
|
||||
RespDirectives: resDir,
|
||||
RespHeaders: res.Header,
|
||||
RespStatusCode: res.StatusCode,
|
||||
RespExpiresHeader: expiresHeader,
|
||||
RespDateHeader: dateHeader,
|
||||
RespLastModifiedHeader: lastModifiedHeader,
|
||||
|
||||
ReqDirectives: reqDir,
|
||||
ReqHeaders: req.Header,
|
||||
ReqMethod: req.Method,
|
||||
|
||||
NowUTC: time.Now().UTC(),
|
||||
}
|
||||
rv := cacheobject.ObjectResults{}
|
||||
|
||||
cacheobject.CachableObject(&obj, &rv)
|
||||
cacheobject.ExpirationObject(&obj, &rv)
|
||||
|
||||
fmt.Println("Errors: ", rv.OutErr)
|
||||
fmt.Println("Reasons to not cache: ", rv.OutReasons)
|
||||
fmt.Println("Warning headers to add: ", rv.OutWarnings)
|
||||
fmt.Println("Expiration: ", rv.OutExpirationTime.String())
|
||||
}
|
||||
```
|
||||
|
||||
## Improvements, bugs, adding features, and taking cachecontrol new directions!
|
||||
|
||||
Please [open issues in Github](https://github.com/pquerna/cachecontrol/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
|
||||
|
||||
# Credits
|
||||
|
||||
`cachecontrol` has recieved significant contributions from:
|
||||
|
||||
* [Paul Querna](https://github.com/pquerna)
|
||||
|
||||
## License
|
||||
|
||||
`cachecontrol` is licensed under the [Apache License, Version 2.0](./LICENSE)
|
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cachecontrol
|
||||
|
||||
import (
|
||||
"github.com/pquerna/cachecontrol/cacheobject"
|
||||
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
// Set to True for a prviate cache, which is not shared amoung users (eg, in a browser)
|
||||
// Set to False for a "shared" cache, which is more common in a server context.
|
||||
PrivateCache bool
|
||||
}
|
||||
|
||||
// Given an HTTP Request, the future Status Code, and an ResponseWriter,
|
||||
// determine the possible reasons a response SHOULD NOT be cached.
|
||||
func CachableResponseWriter(req *http.Request,
|
||||
statusCode int,
|
||||
resp http.ResponseWriter,
|
||||
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||
return cacheobject.UsingRequestResponse(req, statusCode, resp.Header(), opts.PrivateCache)
|
||||
}
|
||||
|
||||
// Given an HTTP Request and Response, determine the possible reasons a response SHOULD NOT
|
||||
// be cached.
|
||||
func CachableResponse(req *http.Request,
|
||||
resp *http.Response,
|
||||
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||
return cacheobject.UsingRequestResponse(req, resp.StatusCode, resp.Header, opts.PrivateCache)
|
||||
}
|
111
vendor/github.com/pquerna/cachecontrol/api_test.go
generated
vendored
Normal file
111
vendor/github.com/pquerna/cachecontrol/api_test.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cachecontrol
|
||||
|
||||
import (
|
||||
"github.com/pquerna/cachecontrol/cacheobject"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func roundTrip(t *testing.T, fnc func(w http.ResponseWriter, r *http.Request)) (*http.Request, *http.Response) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(fnc))
|
||||
defer ts.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
return req, res
|
||||
}
|
||||
|
||||
func TestCachableResponsePublic(t *testing.T) {
|
||||
req, res := roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "public")
|
||||
w.Header().Set("Last-Modified",
|
||||
time.Now().UTC().Add(time.Duration(time.Hour*-5)).Format(http.TimeFormat))
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
||||
opts := Options{}
|
||||
reasons, expires, err := CachableResponse(req, res, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.WithinDuration(t,
|
||||
time.Now().UTC().Add(time.Duration(float64(time.Hour)*0.5)),
|
||||
expires,
|
||||
10*time.Second)
|
||||
}
|
||||
|
||||
func TestCachableResponsePrivate(t *testing.T) {
|
||||
req, res := roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
||||
opts := Options{}
|
||||
reasons, expires, err := CachableResponse(req, res, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 1)
|
||||
require.Equal(t, reasons[0], cacheobject.ReasonResponsePrivate)
|
||||
require.Equal(t, time.Time{}, expires)
|
||||
|
||||
opts.PrivateCache = true
|
||||
reasons, expires, err = CachableResponse(req, res, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.Equal(t, time.Time{}, expires)
|
||||
}
|
||||
|
||||
func TestResponseWriter(t *testing.T) {
|
||||
var resp http.ResponseWriter
|
||||
var req *http.Request
|
||||
_, _ = roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
fmt.Fprintln(w, `{}`)
|
||||
resp = w
|
||||
req = r
|
||||
})
|
||||
|
||||
opts := Options{}
|
||||
reasons, expires, err := CachableResponseWriter(req, 200, resp, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 1)
|
||||
require.Equal(t, reasons[0], cacheobject.ReasonResponsePrivate)
|
||||
require.Equal(t, time.Time{}, expires)
|
||||
|
||||
opts.PrivateCache = true
|
||||
reasons, expires, err = CachableResponseWriter(req, 200, resp, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.Equal(t, time.Time{}, expires)
|
||||
}
|
510
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
510
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
|
@ -0,0 +1,510 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(pquerna): add extensions from here: http://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
|
||||
|
||||
var (
|
||||
ErrQuoteMismatch = errors.New("Missing closing quote")
|
||||
ErrMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `max-age`")
|
||||
ErrSMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `s-maxage`")
|
||||
ErrMaxStaleDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||
ErrMinFreshDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||
ErrNoCacheNoArgs = errors.New("Unexpected argument to `no-cache`")
|
||||
ErrNoStoreNoArgs = errors.New("Unexpected argument to `no-store`")
|
||||
ErrNoTransformNoArgs = errors.New("Unexpected argument to `no-transform`")
|
||||
ErrOnlyIfCachedNoArgs = errors.New("Unexpected argument to `only-if-cached`")
|
||||
ErrMustRevalidateNoArgs = errors.New("Unexpected argument to `must-revalidate`")
|
||||
ErrPublicNoArgs = errors.New("Unexpected argument to `public`")
|
||||
ErrProxyRevalidateNoArgs = errors.New("Unexpected argument to `proxy-revalidate`")
|
||||
)
|
||||
|
||||
func whitespace(b byte) bool {
|
||||
if b == '\t' || b == ' ' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parse(value string, cd cacheDirective) error {
|
||||
var err error = nil
|
||||
i := 0
|
||||
|
||||
for i < len(value) && err == nil {
|
||||
// eat leading whitespace or commas
|
||||
if whitespace(value[i]) || value[i] == ',' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
j := i + 1
|
||||
|
||||
for j < len(value) {
|
||||
if !isToken(value[j]) {
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
token := strings.ToLower(value[i:j])
|
||||
tokenHasFields := hasFieldNames(token)
|
||||
/*
|
||||
println("GOT TOKEN:")
|
||||
println(" i -> ", i)
|
||||
println(" j -> ", j)
|
||||
println(" token -> ", token)
|
||||
*/
|
||||
|
||||
if j+1 < len(value) && value[j] == '=' {
|
||||
k := j + 1
|
||||
// minimum size two bytes of "", but we let httpUnquote handle it.
|
||||
if k < len(value) && value[k] == '"' {
|
||||
eaten, result := httpUnquote(value[k:])
|
||||
if eaten == -1 {
|
||||
return ErrQuoteMismatch
|
||||
}
|
||||
i = k + eaten
|
||||
|
||||
err = cd.addPair(token, result)
|
||||
} else {
|
||||
z := k
|
||||
for z < len(value) {
|
||||
if tokenHasFields {
|
||||
if whitespace(value[z]) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if whitespace(value[z]) || value[z] == ',' {
|
||||
break
|
||||
}
|
||||
}
|
||||
z++
|
||||
}
|
||||
i = z
|
||||
|
||||
result := value[k:z]
|
||||
if result != "" && result[len(result)-1] == ',' {
|
||||
result = result[:len(result)-1]
|
||||
}
|
||||
|
||||
err = cd.addPair(token, result)
|
||||
}
|
||||
} else {
|
||||
if token != "," {
|
||||
err = cd.addToken(token)
|
||||
}
|
||||
i = j
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeltaSeconds specifies a non-negative integer, representing
|
||||
// time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||
//
|
||||
// When set to -1, this means unset.
|
||||
//
|
||||
type DeltaSeconds int32
|
||||
|
||||
// Parser for delta-seconds, a uint31, more or less:
|
||||
// http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||
func parseDeltaSeconds(v string) (DeltaSeconds, error) {
|
||||
n, err := strconv.ParseUint(v, 10, 32)
|
||||
if err != nil {
|
||||
if numError, ok := err.(*strconv.NumError); ok {
|
||||
if numError.Err == strconv.ErrRange {
|
||||
return DeltaSeconds(math.MaxInt32), nil
|
||||
}
|
||||
}
|
||||
return DeltaSeconds(-1), err
|
||||
} else {
|
||||
if n > math.MaxInt32 {
|
||||
return DeltaSeconds(math.MaxInt32), nil
|
||||
} else {
|
||||
return DeltaSeconds(n), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fields present in a header.
|
||||
type FieldNames map[string]bool
|
||||
|
||||
// internal interface for shared methods of RequestCacheDirectives and ResponseCacheDirectives
|
||||
type cacheDirective interface {
|
||||
addToken(s string) error
|
||||
addPair(s string, v string) error
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Repersentation of possible request directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.1
|
||||
//
|
||||
// Note: Many fields will be `nil` in practice.
|
||||
//
|
||||
type RequestCacheDirectives struct {
|
||||
|
||||
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.1
|
||||
//
|
||||
// The "max-age" request directive indicates that the client is
|
||||
// unwilling to accept a response whose age is greater than the
|
||||
// specified number of seconds. Unless the max-stale request directive
|
||||
// is also present, the client is not willing to accept a stale
|
||||
// response.
|
||||
MaxAge DeltaSeconds
|
||||
|
||||
// max-stale(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.2
|
||||
//
|
||||
// The "max-stale" request directive indicates that the client is
|
||||
// willing to accept a response that has exceeded its freshness
|
||||
// lifetime. If max-stale is assigned a value, then the client is
|
||||
// willing to accept a response that has exceeded its freshness lifetime
|
||||
// by no more than the specified number of seconds. If no value is
|
||||
// assigned to max-stale, then the client is willing to accept a stale
|
||||
// response of any age.
|
||||
MaxStale DeltaSeconds
|
||||
|
||||
// min-fresh(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.3
|
||||
//
|
||||
// The "min-fresh" request directive indicates that the client is
|
||||
// willing to accept a response whose freshness lifetime is no less than
|
||||
// its current age plus the specified time in seconds. That is, the
|
||||
// client wants a response that will still be fresh for at least the
|
||||
// specified number of seconds.
|
||||
MinFresh DeltaSeconds
|
||||
|
||||
// no-cache(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.4
|
||||
//
|
||||
// The "no-cache" request directive indicates that a cache MUST NOT use
|
||||
// a stored response to satisfy the request without successful
|
||||
// validation on the origin server.
|
||||
NoCache bool
|
||||
|
||||
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.5
|
||||
//
|
||||
// The "no-store" request directive indicates that a cache MUST NOT
|
||||
// store any part of either this request or any response to it. This
|
||||
// directive applies to both private and shared caches.
|
||||
NoStore bool
|
||||
|
||||
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.6
|
||||
//
|
||||
// The "no-transform" request directive indicates that an intermediary
|
||||
// (whether or not it implements a cache) MUST NOT transform the
|
||||
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||
NoTransform bool
|
||||
|
||||
// only-if-cached(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.7
|
||||
//
|
||||
// The "only-if-cached" request directive indicates that the client only
|
||||
// wishes to obtain a stored response.
|
||||
OnlyIfCached bool
|
||||
|
||||
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||
//
|
||||
// The Cache-Control header field can be extended through the use of one
|
||||
// or more cache-extension tokens, each with an optional value. A cache
|
||||
// MUST ignore unrecognized cache directives.
|
||||
Extensions []string
|
||||
}
|
||||
|
||||
func (cd *RequestCacheDirectives) addToken(token string) error {
|
||||
var err error = nil
|
||||
|
||||
switch token {
|
||||
case "max-age":
|
||||
err = ErrMaxAgeDeltaSeconds
|
||||
case "max-stale":
|
||||
err = ErrMaxStaleDeltaSeconds
|
||||
case "min-fresh":
|
||||
err = ErrMinFreshDeltaSeconds
|
||||
case "no-cache":
|
||||
cd.NoCache = true
|
||||
case "no-store":
|
||||
cd.NoStore = true
|
||||
case "no-transform":
|
||||
cd.NoTransform = true
|
||||
case "only-if-cached":
|
||||
cd.OnlyIfCached = true
|
||||
default:
|
||||
cd.Extensions = append(cd.Extensions, token)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (cd *RequestCacheDirectives) addPair(token string, v string) error {
|
||||
var err error = nil
|
||||
|
||||
switch token {
|
||||
case "max-age":
|
||||
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||
if err != nil {
|
||||
err = ErrMaxAgeDeltaSeconds
|
||||
}
|
||||
case "max-stale":
|
||||
cd.MaxStale, err = parseDeltaSeconds(v)
|
||||
if err != nil {
|
||||
err = ErrMaxStaleDeltaSeconds
|
||||
}
|
||||
case "min-fresh":
|
||||
cd.MinFresh, err = parseDeltaSeconds(v)
|
||||
if err != nil {
|
||||
err = ErrMinFreshDeltaSeconds
|
||||
}
|
||||
case "no-cache":
|
||||
err = ErrNoCacheNoArgs
|
||||
case "no-store":
|
||||
err = ErrNoStoreNoArgs
|
||||
case "no-transform":
|
||||
err = ErrNoTransformNoArgs
|
||||
case "only-if-cached":
|
||||
err = ErrOnlyIfCachedNoArgs
|
||||
default:
|
||||
// TODO(pquerna): this sucks, making user re-parse
|
||||
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Parses a Cache Control Header from a Request into a set of directives.
|
||||
func ParseRequestCacheControl(value string) (*RequestCacheDirectives, error) {
|
||||
cd := &RequestCacheDirectives{
|
||||
MaxAge: -1,
|
||||
MaxStale: -1,
|
||||
MinFresh: -1,
|
||||
}
|
||||
|
||||
err := parse(value, cd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cd, nil
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Repersentation of possible response directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.2
|
||||
//
|
||||
// Note: Many fields will be `nil` in practice.
|
||||
//
|
||||
type ResponseCacheDirectives struct {
|
||||
|
||||
// must-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.1
|
||||
//
|
||||
// The "must-revalidate" response directive indicates that once it has
|
||||
// become stale, a cache MUST NOT use the response to satisfy subsequent
|
||||
// requests without successful validation on the origin server.
|
||||
MustRevalidate bool
|
||||
|
||||
// no-cache(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||
//
|
||||
// The "no-cache" response directive indicates that the response MUST
|
||||
// NOT be used to satisfy a subsequent request without successful
|
||||
// validation on the origin server.
|
||||
//
|
||||
// If the no-cache response directive specifies one or more field-names,
|
||||
// then a cache MAY use the response to satisfy a subsequent request,
|
||||
// subject to any other restrictions on caching. However, any header
|
||||
// fields in the response that have the field-name(s) listed MUST NOT be
|
||||
// sent in the response to a subsequent request without successful
|
||||
// revalidation with the origin server.
|
||||
NoCache FieldNames
|
||||
|
||||
// no-cache(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||
//
|
||||
// While the RFC defines optional field-names on a no-cache directive,
|
||||
// many applications only want to know if any no-cache directives were
|
||||
// present at all.
|
||||
NoCachePresent bool
|
||||
|
||||
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.3
|
||||
//
|
||||
// The "no-store" request directive indicates that a cache MUST NOT
|
||||
// store any part of either this request or any response to it. This
|
||||
// directive applies to both private and shared caches.
|
||||
NoStore bool
|
||||
|
||||
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.4
|
||||
//
|
||||
// The "no-transform" response directive indicates that an intermediary
|
||||
// (regardless of whether it implements a cache) MUST NOT transform the
|
||||
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||
NoTransform bool
|
||||
|
||||
// public(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.5
|
||||
//
|
||||
// The "public" response directive indicates that any cache MAY store
|
||||
// the response, even if the response would normally be non-cacheable or
|
||||
// cacheable only within a private cache.
|
||||
Public bool
|
||||
|
||||
// private(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||
//
|
||||
// The "private" response directive indicates that the response message
|
||||
// is intended for a single user and MUST NOT be stored by a shared
|
||||
// cache. A private cache MAY store the response and reuse it for later
|
||||
// requests, even if the response would normally be non-cacheable.
|
||||
//
|
||||
// If the private response directive specifies one or more field-names,
|
||||
// this requirement is limited to the field-values associated with the
|
||||
// listed response header fields. That is, a shared cache MUST NOT
|
||||
// store the specified field-names(s), whereas it MAY store the
|
||||
// remainder of the response message.
|
||||
Private FieldNames
|
||||
|
||||
// private(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||
//
|
||||
// While the RFC defines optional field-names on a private directive,
|
||||
// many applications only want to know if any private directives were
|
||||
// present at all.
|
||||
PrivatePresent bool
|
||||
|
||||
// proxy-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.7
|
||||
//
|
||||
// The "proxy-revalidate" response directive has the same meaning as the
|
||||
// must-revalidate response directive, except that it does not apply to
|
||||
// private caches.
|
||||
ProxyRevalidate bool
|
||||
|
||||
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.8
|
||||
//
|
||||
// The "max-age" response directive indicates that the response is to be
|
||||
// considered stale after its age is greater than the specified number
|
||||
// of seconds.
|
||||
MaxAge DeltaSeconds
|
||||
|
||||
// s-maxage(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.9
|
||||
//
|
||||
// The "s-maxage" response directive indicates that, in shared caches,
|
||||
// the maximum age specified by this directive overrides the maximum age
|
||||
// specified by either the max-age directive or the Expires header
|
||||
// field. The s-maxage directive also implies the semantics of the
|
||||
// proxy-revalidate response directive.
|
||||
SMaxAge DeltaSeconds
|
||||
|
||||
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||
//
|
||||
// The Cache-Control header field can be extended through the use of one
|
||||
// or more cache-extension tokens, each with an optional value. A cache
|
||||
// MUST ignore unrecognized cache directives.
|
||||
Extensions []string
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Parses a Cache Control Header from a Response into a set of directives.
|
||||
func ParseResponseCacheControl(value string) (*ResponseCacheDirectives, error) {
|
||||
cd := &ResponseCacheDirectives{
|
||||
MaxAge: -1,
|
||||
SMaxAge: -1,
|
||||
}
|
||||
|
||||
err := parse(value, cd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cd, nil
|
||||
}
|
||||
|
||||
func (cd *ResponseCacheDirectives) addToken(token string) error {
|
||||
var err error = nil
|
||||
switch token {
|
||||
case "must-revalidate":
|
||||
cd.MustRevalidate = true
|
||||
case "no-cache":
|
||||
cd.NoCachePresent = true
|
||||
case "no-store":
|
||||
cd.NoStore = true
|
||||
case "no-transform":
|
||||
cd.NoTransform = true
|
||||
case "public":
|
||||
cd.Public = true
|
||||
case "private":
|
||||
cd.PrivatePresent = true
|
||||
case "proxy-revalidate":
|
||||
cd.ProxyRevalidate = true
|
||||
case "max-age":
|
||||
err = ErrMaxAgeDeltaSeconds
|
||||
case "s-maxage":
|
||||
err = ErrSMaxAgeDeltaSeconds
|
||||
default:
|
||||
cd.Extensions = append(cd.Extensions, token)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func hasFieldNames(token string) bool {
|
||||
switch token {
|
||||
case "no-cache":
|
||||
return true
|
||||
case "private":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cd *ResponseCacheDirectives) addPair(token string, v string) error {
|
||||
var err error = nil
|
||||
|
||||
switch token {
|
||||
case "must-revalidate":
|
||||
err = ErrMustRevalidateNoArgs
|
||||
case "no-cache":
|
||||
cd.NoCachePresent = true
|
||||
tokens := strings.Split(v, ",")
|
||||
if cd.NoCache == nil {
|
||||
cd.NoCache = make(FieldNames)
|
||||
}
|
||||
for _, t := range tokens {
|
||||
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||
cd.NoCache[k] = true
|
||||
}
|
||||
case "no-store":
|
||||
err = ErrNoStoreNoArgs
|
||||
case "no-transform":
|
||||
err = ErrNoTransformNoArgs
|
||||
case "public":
|
||||
err = ErrPublicNoArgs
|
||||
case "private":
|
||||
cd.PrivatePresent = true
|
||||
tokens := strings.Split(v, ",")
|
||||
if cd.Private == nil {
|
||||
cd.Private = make(FieldNames)
|
||||
}
|
||||
for _, t := range tokens {
|
||||
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||
cd.Private[k] = true
|
||||
}
|
||||
case "proxy-revalidate":
|
||||
err = ErrProxyRevalidateNoArgs
|
||||
case "max-age":
|
||||
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||
case "s-maxage":
|
||||
cd.SMaxAge, err = parseDeltaSeconds(v)
|
||||
default:
|
||||
// TODO(pquerna): this sucks, making user re-parse, and its technically not 'quoted' like the original,
|
||||
// but this is still easier, just a SplitN on "="
|
||||
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
433
vendor/github.com/pquerna/cachecontrol/cacheobject/directive_test.go
generated
vendored
Normal file
433
vendor/github.com/pquerna/cachecontrol/cacheobject/directive_test.go
generated
vendored
Normal file
|
@ -0,0 +1,433 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaxAge(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(-1))
|
||||
|
||||
cd, err = ParseResponseCacheControl("max-age")
|
||||
require.Error(t, err)
|
||||
|
||||
cd, err = ParseResponseCacheControl("max-age=20")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(20))
|
||||
|
||||
cd, err = ParseResponseCacheControl("max-age=0")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(0))
|
||||
|
||||
cd, err = ParseResponseCacheControl("max-age=-1")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSMaxAge(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(-1))
|
||||
|
||||
cd, err = ParseResponseCacheControl("s-maxage")
|
||||
require.Error(t, err)
|
||||
|
||||
cd, err = ParseResponseCacheControl("s-maxage=20")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(20))
|
||||
|
||||
cd, err = ParseResponseCacheControl("s-maxage=0")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(0))
|
||||
|
||||
cd, err = ParseResponseCacheControl("s-maxage=-1")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResNoCache(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(-1))
|
||||
|
||||
cd, err = ParseResponseCacheControl("no-cache")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.NoCachePresent, true)
|
||||
require.Equal(t, len(cd.NoCache), 0)
|
||||
|
||||
cd, err = ParseResponseCacheControl("no-cache=MyThing")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.NoCachePresent, true)
|
||||
require.Equal(t, len(cd.NoCache), 1)
|
||||
}
|
||||
|
||||
func TestResSpaceOnly(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(" ")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestResTabOnly(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl("\t")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestResPrivateExtensionQuoted(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`private="Set-Cookie,Request-Id" public`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.Public, true)
|
||||
require.Equal(t, cd.PrivatePresent, true)
|
||||
require.Equal(t, len(cd.Private), 2)
|
||||
require.Equal(t, len(cd.Extensions), 0)
|
||||
require.Equal(t, cd.Private["Set-Cookie"], true)
|
||||
require.Equal(t, cd.Private["Request-Id"], true)
|
||||
}
|
||||
|
||||
func TestResCommaFollowingBare(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`public, max-age=500`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.Public, true)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(500))
|
||||
require.Equal(t, cd.PrivatePresent, false)
|
||||
require.Equal(t, len(cd.Extensions), 0)
|
||||
}
|
||||
|
||||
func TestResCommaFollowingKV(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`max-age=500, public`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.Public, true)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(500))
|
||||
require.Equal(t, cd.PrivatePresent, false)
|
||||
require.Equal(t, len(cd.Extensions), 0)
|
||||
}
|
||||
|
||||
func TestResPrivateTrailingComma(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`private=Set-Cookie, public`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.Public, true)
|
||||
require.Equal(t, cd.PrivatePresent, true)
|
||||
require.Equal(t, len(cd.Private), 1)
|
||||
require.Equal(t, len(cd.Extensions), 0)
|
||||
require.Equal(t, cd.Private["Set-Cookie"], true)
|
||||
}
|
||||
|
||||
func TestResPrivateExtension(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`private=Set-Cookie,Request-Id public`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.Public, true)
|
||||
require.Equal(t, cd.PrivatePresent, true)
|
||||
require.Equal(t, len(cd.Private), 2)
|
||||
require.Equal(t, len(cd.Extensions), 0)
|
||||
require.Equal(t, cd.Private["Set-Cookie"], true)
|
||||
require.Equal(t, cd.Private["Request-Id"], true)
|
||||
}
|
||||
|
||||
func TestResMultipleNoCacheTabExtension(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl("no-cache " + "\t" + "no-cache=Mything aasdfdsfa")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.NoCachePresent, true)
|
||||
require.Equal(t, len(cd.NoCache), 1)
|
||||
require.Equal(t, len(cd.Extensions), 1)
|
||||
require.Equal(t, cd.NoCache["Mything"], true)
|
||||
}
|
||||
|
||||
func TestResExtensionsEmptyQuote(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`foo="" bar="hi"`)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cd.SMaxAge, DeltaSeconds(-1))
|
||||
require.Equal(t, len(cd.Extensions), 2)
|
||||
require.Contains(t, cd.Extensions, "bar=hi")
|
||||
require.Contains(t, cd.Extensions, "foo=")
|
||||
}
|
||||
|
||||
func TestResQuoteMismatch(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`foo="`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrQuoteMismatch)
|
||||
}
|
||||
|
||||
func TestResMustRevalidateNoArgs(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`must-revalidate=234`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrMustRevalidateNoArgs)
|
||||
}
|
||||
|
||||
func TestResNoTransformNoArgs(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-transform="xxx"`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrNoTransformNoArgs)
|
||||
}
|
||||
|
||||
func TestResNoStoreNoArgs(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-store=""`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrNoStoreNoArgs)
|
||||
}
|
||||
|
||||
func TestResProxyRevalidateNoArgs(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`proxy-revalidate=23432`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrProxyRevalidateNoArgs)
|
||||
}
|
||||
|
||||
func TestResPublicNoArgs(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`public=999Vary`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrPublicNoArgs)
|
||||
}
|
||||
|
||||
func TestResMustRevalidate(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`must-revalidate`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MustRevalidate, true)
|
||||
}
|
||||
|
||||
func TestResNoTransform(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-transform`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoTransform, true)
|
||||
}
|
||||
|
||||
func TestResNoStore(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-store`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoStore, true)
|
||||
}
|
||||
|
||||
func TestResProxyRevalidate(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`proxy-revalidate`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.ProxyRevalidate, true)
|
||||
}
|
||||
|
||||
func TestResPublic(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`public`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.Public, true)
|
||||
}
|
||||
|
||||
func TestResPrivate(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`private`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Len(t, cd.Private, 0)
|
||||
require.Equal(t, cd.PrivatePresent, true)
|
||||
}
|
||||
|
||||
func TestParseDeltaSecondsZero(t *testing.T) {
|
||||
ds, err := parseDeltaSeconds("0")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ds, DeltaSeconds(0))
|
||||
}
|
||||
|
||||
func TestParseDeltaSecondsLarge(t *testing.T) {
|
||||
ds, err := parseDeltaSeconds(fmt.Sprintf("%d", int64(math.MaxInt32)*2))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ds, DeltaSeconds(math.MaxInt32))
|
||||
}
|
||||
|
||||
func TestParseDeltaSecondsVeryLarge(t *testing.T) {
|
||||
ds, err := parseDeltaSeconds(fmt.Sprintf("%d", math.MaxInt64))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ds, DeltaSeconds(math.MaxInt32))
|
||||
}
|
||||
|
||||
func TestParseDeltaSecondsNegative(t *testing.T) {
|
||||
ds, err := parseDeltaSeconds("-60")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, DeltaSeconds(-1), ds)
|
||||
}
|
||||
|
||||
func TestReqNoCacheNoArgs(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`no-cache=234`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrNoCacheNoArgs)
|
||||
}
|
||||
|
||||
func TestReqNoStoreNoArgs(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`no-store=,,x`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrNoStoreNoArgs)
|
||||
}
|
||||
|
||||
func TestReqNoTransformNoArgs(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`no-transform=akx`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrNoTransformNoArgs)
|
||||
}
|
||||
|
||||
func TestReqOnlyIfCachedNoArgs(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`only-if-cached=no-store`)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, cd)
|
||||
require.Equal(t, err, ErrOnlyIfCachedNoArgs)
|
||||
}
|
||||
|
||||
func TestReqMaxAge(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`max-age=99999`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(99999))
|
||||
require.Equal(t, cd.MaxStale, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestReqMaxStale(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`max-stale=99999`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MaxStale, DeltaSeconds(99999))
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(-1))
|
||||
require.Equal(t, cd.MinFresh, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestReqMaxAgeBroken(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`max-age`)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, ErrMaxAgeDeltaSeconds, err)
|
||||
require.Nil(t, cd)
|
||||
}
|
||||
|
||||
func TestReqMaxStaleBroken(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`max-stale`)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, ErrMaxStaleDeltaSeconds, err)
|
||||
require.Nil(t, cd)
|
||||
}
|
||||
|
||||
func TestReqMinFresh(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh=99999`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MinFresh, DeltaSeconds(99999))
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(-1))
|
||||
require.Equal(t, cd.MaxStale, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestReqMinFreshBroken(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh`)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, ErrMinFreshDeltaSeconds, err)
|
||||
require.Nil(t, cd)
|
||||
}
|
||||
|
||||
func TestReqMinFreshJunk(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh=a99a`)
|
||||
require.Equal(t, ErrMinFreshDeltaSeconds, err)
|
||||
require.Nil(t, cd)
|
||||
}
|
||||
|
||||
func TestReqMinFreshBadValue(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh=-1`)
|
||||
require.Equal(t, ErrMinFreshDeltaSeconds, err)
|
||||
require.Nil(t, cd)
|
||||
}
|
||||
|
||||
func TestReqExtensions(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh=99999 foobar=1 cats`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MinFresh, DeltaSeconds(99999))
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(-1))
|
||||
require.Equal(t, cd.MaxStale, DeltaSeconds(-1))
|
||||
require.Len(t, cd.Extensions, 2)
|
||||
require.Contains(t, cd.Extensions, "foobar=1")
|
||||
require.Contains(t, cd.Extensions, "cats")
|
||||
}
|
||||
|
||||
func TestReqMultiple(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`no-store no-transform`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoStore, true)
|
||||
require.Equal(t, cd.NoTransform, true)
|
||||
require.Equal(t, cd.OnlyIfCached, false)
|
||||
require.Len(t, cd.Extensions, 0)
|
||||
}
|
||||
|
||||
func TestReqMultipleComma(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`no-cache,only-if-cached`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoCache, true)
|
||||
require.Equal(t, cd.NoStore, false)
|
||||
require.Equal(t, cd.NoTransform, false)
|
||||
require.Equal(t, cd.OnlyIfCached, true)
|
||||
require.Len(t, cd.Extensions, 0)
|
||||
}
|
||||
|
||||
func TestReqLeadingComma(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`,no-cache`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Len(t, cd.Extensions, 0)
|
||||
require.Equal(t, cd.NoCache, true)
|
||||
require.Equal(t, cd.NoStore, false)
|
||||
require.Equal(t, cd.NoTransform, false)
|
||||
require.Equal(t, cd.OnlyIfCached, false)
|
||||
}
|
||||
|
||||
func TestReqMinFreshQuoted(t *testing.T) {
|
||||
cd, err := ParseRequestCacheControl(`min-fresh="99999"`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.MinFresh, DeltaSeconds(99999))
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(-1))
|
||||
require.Equal(t, cd.MaxStale, DeltaSeconds(-1))
|
||||
}
|
||||
|
||||
func TestNoSpacesIssue3(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-cache,no-store,max-age=0,must-revalidate`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoCachePresent, true)
|
||||
require.Equal(t, cd.NoStore, true)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(0))
|
||||
require.Equal(t, cd.MustRevalidate, true)
|
||||
}
|
||||
|
||||
func TestNoSpacesIssue3PrivateFields(t *testing.T) {
|
||||
cd, err := ParseResponseCacheControl(`no-cache, no-store, private=set-cookie,hello, max-age=0, must-revalidate`)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cd)
|
||||
require.Equal(t, cd.NoCachePresent, true)
|
||||
require.Equal(t, cd.NoStore, true)
|
||||
require.Equal(t, cd.MaxAge, DeltaSeconds(0))
|
||||
require.Equal(t, cd.MustRevalidate, true)
|
||||
require.Equal(t, true, cd.Private["Set-Cookie"])
|
||||
require.Equal(t, true, cd.Private["Hello"])
|
||||
}
|
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
// 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.
|
||||
|
||||
package cacheobject
|
||||
|
||||
// This file deals with lexical matters of HTTP
|
||||
|
||||
func isSeparator(c byte) bool {
|
||||
switch c {
|
||||
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
|
||||
|
||||
func isChar(c byte) bool { return 0 <= c && c <= 127 }
|
||||
|
||||
func isAnyText(c byte) bool { return !isCtl(c) }
|
||||
|
||||
func isQdText(c byte) bool { return isAnyText(c) && c != '"' }
|
||||
|
||||
func isToken(c byte) bool { return isChar(c) && !isCtl(c) && !isSeparator(c) }
|
||||
|
||||
// Valid escaped sequences are not specified in RFC 2616, so for now, we assume
|
||||
// that they coincide with the common sense ones used by GO. Malformed
|
||||
// characters should probably not be treated as errors by a robust (forgiving)
|
||||
// parser, so we replace them with the '?' character.
|
||||
func httpUnquotePair(b byte) byte {
|
||||
// skip the first byte, which should always be '\'
|
||||
switch b {
|
||||
case 'a':
|
||||
return '\a'
|
||||
case 'b':
|
||||
return '\b'
|
||||
case 'f':
|
||||
return '\f'
|
||||
case 'n':
|
||||
return '\n'
|
||||
case 'r':
|
||||
return '\r'
|
||||
case 't':
|
||||
return '\t'
|
||||
case 'v':
|
||||
return '\v'
|
||||
case '\\':
|
||||
return '\\'
|
||||
case '\'':
|
||||
return '\''
|
||||
case '"':
|
||||
return '"'
|
||||
}
|
||||
return '?'
|
||||
}
|
||||
|
||||
// raw must begin with a valid quoted string. Only the first quoted string is
|
||||
// parsed and is unquoted in result. eaten is the number of bytes parsed, or -1
|
||||
// upon failure.
|
||||
func httpUnquote(raw string) (eaten int, result string) {
|
||||
buf := make([]byte, len(raw))
|
||||
if raw[0] != '"' {
|
||||
return -1, ""
|
||||
}
|
||||
eaten = 1
|
||||
j := 0 // # of bytes written in buf
|
||||
for i := 1; i < len(raw); i++ {
|
||||
switch b := raw[i]; b {
|
||||
case '"':
|
||||
eaten++
|
||||
buf = buf[0:j]
|
||||
return i + 1, string(buf)
|
||||
case '\\':
|
||||
if len(raw) < i+2 {
|
||||
return -1, ""
|
||||
}
|
||||
buf[j] = httpUnquotePair(raw[i+1])
|
||||
eaten += 2
|
||||
j++
|
||||
i++
|
||||
default:
|
||||
if isQdText(b) {
|
||||
buf[j] = b
|
||||
} else {
|
||||
buf[j] = '?'
|
||||
}
|
||||
eaten++
|
||||
j++
|
||||
}
|
||||
}
|
||||
return -1, ""
|
||||
}
|
378
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
378
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
|
@ -0,0 +1,378 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LOW LEVEL API: Repersents a potentially cachable HTTP object.
|
||||
//
|
||||
// This struct is designed to be serialized efficiently, so in a high
|
||||
// performance caching server, things like Date-Strings don't need to be
|
||||
// parsed for every use of a cached object.
|
||||
type Object struct {
|
||||
CacheIsPrivate bool
|
||||
|
||||
RespDirectives *ResponseCacheDirectives
|
||||
RespHeaders http.Header
|
||||
RespStatusCode int
|
||||
RespExpiresHeader time.Time
|
||||
RespDateHeader time.Time
|
||||
RespLastModifiedHeader time.Time
|
||||
|
||||
ReqDirectives *RequestCacheDirectives
|
||||
ReqHeaders http.Header
|
||||
ReqMethod string
|
||||
|
||||
NowUTC time.Time
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Repersents the results of examinig an Object with
|
||||
// CachableObject and ExpirationObject.
|
||||
//
|
||||
// TODO(pquerna): decide if this is a good idea or bad
|
||||
type ObjectResults struct {
|
||||
OutReasons []Reason
|
||||
OutWarnings []Warning
|
||||
OutExpirationTime time.Time
|
||||
OutErr error
|
||||
}
|
||||
|
||||
// LOW LEVEL API: Check if a object is cachable.
|
||||
func CachableObject(obj *Object, rv *ObjectResults) {
|
||||
rv.OutReasons = nil
|
||||
rv.OutWarnings = nil
|
||||
rv.OutErr = nil
|
||||
|
||||
switch obj.ReqMethod {
|
||||
case "GET":
|
||||
break
|
||||
case "HEAD":
|
||||
break
|
||||
case "POST":
|
||||
/**
|
||||
POST: http://tools.ietf.org/html/rfc7231#section-4.3.3
|
||||
|
||||
Responses to POST requests are only cacheable when they include
|
||||
explicit freshness information (see Section 4.2.1 of [RFC7234]).
|
||||
However, POST caching is not widely implemented. For cases where an
|
||||
origin server wishes the client to be able to cache the result of a
|
||||
POST in a way that can be reused by a later GET, the origin server
|
||||
MAY send a 200 (OK) response containing the result and a
|
||||
Content-Location header field that has the same value as the POST's
|
||||
effective request URI (Section 3.1.4.2).
|
||||
*/
|
||||
if !hasFreshness(obj.ReqDirectives, obj.RespDirectives, obj.RespHeaders, obj.RespExpiresHeader, obj.CacheIsPrivate) {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPOST)
|
||||
}
|
||||
|
||||
case "PUT":
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPUT)
|
||||
|
||||
case "DELETE":
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodDELETE)
|
||||
|
||||
case "CONNECT":
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodCONNECT)
|
||||
|
||||
case "OPTIONS":
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodOPTIONS)
|
||||
|
||||
case "TRACE":
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodTRACE)
|
||||
|
||||
// HTTP Extension Methods: http://www.iana.org/assignments/http-methods/http-methods.xhtml
|
||||
//
|
||||
// To my knowledge, none of them are cachable. Please open a ticket if this is not the case!
|
||||
//
|
||||
default:
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodUnkown)
|
||||
}
|
||||
|
||||
if obj.ReqDirectives.NoStore {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestNoStore)
|
||||
}
|
||||
|
||||
// Storing Responses to Authenticated Requests: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||
authz := obj.ReqHeaders.Get("Authorization")
|
||||
if authz != "" {
|
||||
if obj.RespDirectives.MustRevalidate ||
|
||||
obj.RespDirectives.Public ||
|
||||
obj.RespDirectives.SMaxAge != -1 {
|
||||
// Expires of some kind present, this is potentially OK.
|
||||
} else {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonRequestAuthorizationHeader)
|
||||
}
|
||||
}
|
||||
|
||||
if obj.RespDirectives.PrivatePresent && !obj.CacheIsPrivate {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonResponsePrivate)
|
||||
}
|
||||
|
||||
if obj.RespDirectives.NoStore {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonResponseNoStore)
|
||||
}
|
||||
|
||||
/*
|
||||
the response either:
|
||||
|
||||
* contains an Expires header field (see Section 5.3), or
|
||||
|
||||
* contains a max-age response directive (see Section 5.2.2.8), or
|
||||
|
||||
* contains a s-maxage response directive (see Section 5.2.2.9)
|
||||
and the cache is shared, or
|
||||
|
||||
* contains a Cache Control Extension (see Section 5.2.3) that
|
||||
allows it to be cached, or
|
||||
|
||||
* has a status code that is defined as cacheable by default (see
|
||||
Section 4.2.2), or
|
||||
|
||||
* contains a public response directive (see Section 5.2.2.5).
|
||||
*/
|
||||
|
||||
expires := obj.RespHeaders.Get("Expires") != ""
|
||||
statusCachable := cachableStatusCode(obj.RespStatusCode)
|
||||
|
||||
if expires ||
|
||||
obj.RespDirectives.MaxAge != -1 ||
|
||||
(obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate) ||
|
||||
statusCachable ||
|
||||
obj.RespDirectives.Public {
|
||||
/* cachable by default, at least one of the above conditions was true */
|
||||
} else {
|
||||
rv.OutReasons = append(rv.OutReasons, ReasonResponseUncachableByDefault)
|
||||
}
|
||||
}
|
||||
|
||||
var twentyFourHours = time.Duration(24 * time.Hour)
|
||||
|
||||
const debug = false
|
||||
|
||||
// LOW LEVEL API: Update an objects expiration time.
|
||||
func ExpirationObject(obj *Object, rv *ObjectResults) {
|
||||
/**
|
||||
* Okay, lets calculate Freshness/Expiration now. woo:
|
||||
* http://tools.ietf.org/html/rfc7234#section-4.2
|
||||
*/
|
||||
|
||||
/*
|
||||
o If the cache is shared and the s-maxage response directive
|
||||
(Section 5.2.2.9) is present, use its value, or
|
||||
|
||||
o If the max-age response directive (Section 5.2.2.8) is present,
|
||||
use its value, or
|
||||
|
||||
o If the Expires response header field (Section 5.3) is present, use
|
||||
its value minus the value of the Date response header field, or
|
||||
|
||||
o Otherwise, no explicit expiration time is present in the response.
|
||||
A heuristic freshness lifetime might be applicable; see
|
||||
Section 4.2.2.
|
||||
*/
|
||||
|
||||
var expiresTime time.Time
|
||||
|
||||
if obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate {
|
||||
expiresTime = obj.NowUTC.Add(time.Second * time.Duration(obj.RespDirectives.SMaxAge))
|
||||
} else if obj.RespDirectives.MaxAge != -1 {
|
||||
expiresTime = obj.NowUTC.UTC().Add(time.Second * time.Duration(obj.RespDirectives.MaxAge))
|
||||
} else if !obj.RespExpiresHeader.IsZero() {
|
||||
serverDate := obj.RespDateHeader
|
||||
if serverDate.IsZero() {
|
||||
// common enough case when a Date: header has not yet been added to an
|
||||
// active response.
|
||||
serverDate = obj.NowUTC
|
||||
}
|
||||
expiresTime = obj.NowUTC.Add(obj.RespExpiresHeader.Sub(serverDate))
|
||||
} else if !obj.RespLastModifiedHeader.IsZero() {
|
||||
// heuristic freshness lifetime
|
||||
rv.OutWarnings = append(rv.OutWarnings, WarningHeuristicExpiration)
|
||||
|
||||
// http://httpd.apache.org/docs/2.4/mod/mod_cache.html#cachelastmodifiedfactor
|
||||
// CacheMaxExpire defaults to 24 hours
|
||||
// CacheLastModifiedFactor: is 0.1
|
||||
//
|
||||
// expiry-period = MIN(time-since-last-modified-date * factor, 24 hours)
|
||||
//
|
||||
// obj.NowUTC
|
||||
|
||||
since := obj.RespLastModifiedHeader.Sub(obj.NowUTC)
|
||||
since = time.Duration(float64(since) * -0.1)
|
||||
|
||||
if since > twentyFourHours {
|
||||
expiresTime = obj.NowUTC.Add(twentyFourHours)
|
||||
} else {
|
||||
expiresTime = obj.NowUTC.Add(since)
|
||||
}
|
||||
|
||||
if debug {
|
||||
println("Now UTC: ", obj.NowUTC.String())
|
||||
println("Last-Modified: ", obj.RespLastModifiedHeader.String())
|
||||
println("Since: ", since.String())
|
||||
println("TwentyFourHours: ", twentyFourHours.String())
|
||||
println("Expiration: ", expiresTime.String())
|
||||
}
|
||||
} else {
|
||||
// TODO(pquerna): what should the default behavoir be for expiration time?
|
||||
}
|
||||
|
||||
rv.OutExpirationTime = expiresTime
|
||||
}
|
||||
|
||||
// Evaluate cachability based on an HTTP request, and parts of the response.
|
||||
func UsingRequestResponse(req *http.Request,
|
||||
statusCode int,
|
||||
respHeaders http.Header,
|
||||
privateCache bool) ([]Reason, time.Time, error) {
|
||||
|
||||
var reqHeaders http.Header
|
||||
var reqMethod string
|
||||
|
||||
var reqDir *RequestCacheDirectives = nil
|
||||
respDir, err := ParseResponseCacheControl(respHeaders.Get("Cache-Control"))
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
|
||||
if req != nil {
|
||||
reqDir, err = ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
reqHeaders = req.Header
|
||||
reqMethod = req.Method
|
||||
}
|
||||
|
||||
var expiresHeader time.Time
|
||||
var dateHeader time.Time
|
||||
var lastModifiedHeader time.Time
|
||||
|
||||
if respHeaders.Get("Expires") != "" {
|
||||
expiresHeader, err = http.ParseTime(respHeaders.Get("Expires"))
|
||||
if err != nil {
|
||||
// sometimes servers will return `Expires: 0` or `Expires: -1` to
|
||||
// indicate expired content
|
||||
expiresHeader = time.Time{}
|
||||
}
|
||||
expiresHeader = expiresHeader.UTC()
|
||||
}
|
||||
|
||||
if respHeaders.Get("Date") != "" {
|
||||
dateHeader, err = http.ParseTime(respHeaders.Get("Date"))
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
dateHeader = dateHeader.UTC()
|
||||
}
|
||||
|
||||
if respHeaders.Get("Last-Modified") != "" {
|
||||
lastModifiedHeader, err = http.ParseTime(respHeaders.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
lastModifiedHeader = lastModifiedHeader.UTC()
|
||||
}
|
||||
|
||||
obj := Object{
|
||||
CacheIsPrivate: privateCache,
|
||||
|
||||
RespDirectives: respDir,
|
||||
RespHeaders: respHeaders,
|
||||
RespStatusCode: statusCode,
|
||||
RespExpiresHeader: expiresHeader,
|
||||
RespDateHeader: dateHeader,
|
||||
RespLastModifiedHeader: lastModifiedHeader,
|
||||
|
||||
ReqDirectives: reqDir,
|
||||
ReqHeaders: reqHeaders,
|
||||
ReqMethod: reqMethod,
|
||||
|
||||
NowUTC: time.Now().UTC(),
|
||||
}
|
||||
rv := ObjectResults{}
|
||||
|
||||
CachableObject(&obj, &rv)
|
||||
if rv.OutErr != nil {
|
||||
return nil, time.Time{}, rv.OutErr
|
||||
}
|
||||
|
||||
ExpirationObject(&obj, &rv)
|
||||
if rv.OutErr != nil {
|
||||
return nil, time.Time{}, rv.OutErr
|
||||
}
|
||||
|
||||
return rv.OutReasons, rv.OutExpirationTime, nil
|
||||
}
|
||||
|
||||
// calculate if a freshness directive is present: http://tools.ietf.org/html/rfc7234#section-4.2.1
|
||||
func hasFreshness(reqDir *RequestCacheDirectives, respDir *ResponseCacheDirectives, respHeaders http.Header, respExpires time.Time, privateCache bool) bool {
|
||||
if !privateCache && respDir.SMaxAge != -1 {
|
||||
return true
|
||||
}
|
||||
|
||||
if respDir.MaxAge != -1 {
|
||||
return true
|
||||
}
|
||||
|
||||
if !respExpires.IsZero() || respHeaders.Get("Expires") != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func cachableStatusCode(statusCode int) bool {
|
||||
/*
|
||||
Responses with status codes that are defined as cacheable by default
|
||||
(e.g., 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501 in
|
||||
this specification) can be reused by a cache with heuristic
|
||||
expiration unless otherwise indicated by the method definition or
|
||||
explicit cache controls [RFC7234]; all other status codes are not
|
||||
cacheable by default.
|
||||
*/
|
||||
switch statusCode {
|
||||
case 200:
|
||||
return true
|
||||
case 203:
|
||||
return true
|
||||
case 204:
|
||||
return true
|
||||
case 206:
|
||||
return true
|
||||
case 300:
|
||||
return true
|
||||
case 301:
|
||||
return true
|
||||
case 404:
|
||||
return true
|
||||
case 405:
|
||||
return true
|
||||
case 410:
|
||||
return true
|
||||
case 414:
|
||||
return true
|
||||
case 501:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
91
vendor/github.com/pquerna/cachecontrol/cacheobject/object_http_test.go
generated
vendored
Normal file
91
vendor/github.com/pquerna/cachecontrol/cacheobject/object_http_test.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func roundTrip(t *testing.T, fnc func(w http.ResponseWriter, r *http.Request)) (*http.Request, *http.Response) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(fnc))
|
||||
defer ts.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
return req, res
|
||||
}
|
||||
|
||||
func TestCachableResponsePublic(t *testing.T) {
|
||||
req, res := roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "public")
|
||||
w.Header().Set("Last-Modified",
|
||||
time.Now().UTC().Add(time.Duration(time.Hour*-5)).Format(http.TimeFormat))
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
||||
reasons, expires, err := UsingRequestResponse(req, res.StatusCode, res.Header, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.WithinDuration(t,
|
||||
time.Now().UTC().Add(time.Duration(float64(time.Hour)*0.5)),
|
||||
expires,
|
||||
10*time.Second)
|
||||
}
|
||||
|
||||
func TestCachableResponseNoHeaders(t *testing.T) {
|
||||
req, res := roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
||||
reasons, expires, err := UsingRequestResponse(req, res.StatusCode, res.Header, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.True(t, expires.IsZero())
|
||||
}
|
||||
|
||||
func TestCachableResponseBadExpires(t *testing.T) {
|
||||
req, res := roundTrip(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Expires", "-1")
|
||||
fmt.Fprintln(w, `{}`)
|
||||
})
|
||||
|
||||
reasons, expires, err := UsingRequestResponse(req, res.StatusCode, res.Header, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reasons, 0)
|
||||
require.True(t, expires.IsZero())
|
||||
}
|
394
vendor/github.com/pquerna/cachecontrol/cacheobject/object_test.go
generated
vendored
Normal file
394
vendor/github.com/pquerna/cachecontrol/cacheobject/object_test.go
generated
vendored
Normal file
|
@ -0,0 +1,394 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCachableStatusCode(t *testing.T) {
|
||||
ok := []int{200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501}
|
||||
for _, v := range ok {
|
||||
require.True(t, cachableStatusCode(v), "status code should be cacheable: %d", v)
|
||||
}
|
||||
|
||||
notok := []int{201, 429, 500, 504}
|
||||
for _, v := range notok {
|
||||
require.False(t, cachableStatusCode(v), "status code should not be cachable: %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func fill(t *testing.T, now time.Time) Object {
|
||||
RespDirectives, err := ParseResponseCacheControl("")
|
||||
require.NoError(t, err)
|
||||
ReqDirectives, err := ParseRequestCacheControl("")
|
||||
require.NoError(t, err)
|
||||
|
||||
obj := Object{
|
||||
RespDirectives: RespDirectives,
|
||||
RespHeaders: http.Header{},
|
||||
RespStatusCode: 200,
|
||||
RespDateHeader: now,
|
||||
|
||||
ReqDirectives: ReqDirectives,
|
||||
ReqHeaders: http.Header{},
|
||||
ReqMethod: "GET",
|
||||
|
||||
NowUTC: now,
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func TestGETPrivate(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
RespDirectives, err := ParseResponseCacheControl("private")
|
||||
require.NoError(t, err)
|
||||
|
||||
obj.RespDirectives = RespDirectives
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonResponsePrivate)
|
||||
}
|
||||
|
||||
func TestGETPrivateWithPrivateCache(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
RespDirectives, err := ParseResponseCacheControl("private")
|
||||
require.NoError(t, err)
|
||||
|
||||
obj.CacheIsPrivate = true
|
||||
obj.RespDirectives = RespDirectives
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
}
|
||||
|
||||
func TestUncachableMethods(t *testing.T) {
|
||||
type methodPair struct {
|
||||
m string
|
||||
r Reason
|
||||
}
|
||||
|
||||
tc := []methodPair{
|
||||
{"PUT", ReasonRequestMethodPUT},
|
||||
{"DELETE", ReasonRequestMethodDELETE},
|
||||
{"CONNECT", ReasonRequestMethodCONNECT},
|
||||
{"OPTIONS", ReasonRequestMethodOPTIONS},
|
||||
{"CONNECT", ReasonRequestMethodCONNECT},
|
||||
{"TRACE", ReasonRequestMethodTRACE},
|
||||
{"MADEUP", ReasonRequestMethodUnkown},
|
||||
}
|
||||
|
||||
for _, mp := range tc {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = mp.m
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, mp.r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHEAD(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "HEAD"
|
||||
obj.RespLastModifiedHeader = now.Add(time.Hour * -1)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
require.False(t, rv.OutExpirationTime.IsZero())
|
||||
}
|
||||
|
||||
func TestHEADLongLastModified(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "HEAD"
|
||||
obj.RespLastModifiedHeader = now.Add(time.Hour * -70000)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
require.False(t, rv.OutExpirationTime.IsZero())
|
||||
require.WithinDuration(t, now.Add(twentyFourHours), rv.OutExpirationTime, time.Second*60)
|
||||
}
|
||||
|
||||
func TestNonCachablePOST(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "POST"
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestMethodPOST)
|
||||
}
|
||||
|
||||
func TestCachablePOSTExpiresHeader(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "POST"
|
||||
obj.RespExpiresHeader = now.Add(time.Hour * 1)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
}
|
||||
|
||||
func TestCachablePOSTSMax(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "POST"
|
||||
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
}
|
||||
|
||||
func TestNonCachablePOSTSMax(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "POST"
|
||||
obj.CacheIsPrivate = true
|
||||
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestMethodPOST)
|
||||
}
|
||||
|
||||
func TestCachablePOSTMax(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "POST"
|
||||
obj.RespDirectives.MaxAge = DeltaSeconds(9000)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
}
|
||||
|
||||
func TestPUTs(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "PUT"
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestMethodPUT)
|
||||
}
|
||||
|
||||
func TestPUTWithExpires(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqMethod = "PUT"
|
||||
obj.RespExpiresHeader = now.Add(time.Hour * 1)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestMethodPUT)
|
||||
}
|
||||
|
||||
func TestAuthorization(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqHeaders.Set("Authorization", "bearer random")
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestAuthorizationHeader)
|
||||
}
|
||||
|
||||
func TestCachableAuthorization(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqHeaders.Set("Authorization", "bearer random")
|
||||
obj.RespDirectives.Public = true
|
||||
obj.RespDirectives.MaxAge = DeltaSeconds(300)
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.NoError(t, rv.OutErr)
|
||||
require.Len(t, rv.OutReasons, 0)
|
||||
}
|
||||
|
||||
func TestRespNoStore(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.RespDirectives.NoStore = true
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonResponseNoStore)
|
||||
}
|
||||
|
||||
func TestReqNoStore(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.ReqDirectives.NoStore = true
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonRequestNoStore)
|
||||
}
|
||||
|
||||
func TestResp500(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.RespStatusCode = 500
|
||||
|
||||
rv := ObjectResults{}
|
||||
CachableObject(&obj, &rv)
|
||||
require.Len(t, rv.OutReasons, 1)
|
||||
require.Contains(t, rv.OutReasons, ReasonResponseUncachableByDefault)
|
||||
}
|
||||
|
||||
func TestExpirationSMaxShared(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.RespDirectives.SMaxAge = DeltaSeconds(60)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.WithinDuration(t, now.Add(time.Second*60), rv.OutExpirationTime, time.Second*1)
|
||||
}
|
||||
|
||||
func TestExpirationSMaxPrivate(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.CacheIsPrivate = true
|
||||
obj.RespDirectives.SMaxAge = DeltaSeconds(60)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.True(t, rv.OutExpirationTime.IsZero())
|
||||
}
|
||||
|
||||
func TestExpirationMax(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
obj.RespDirectives.MaxAge = DeltaSeconds(60)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.WithinDuration(t, now.Add(time.Second*60), rv.OutExpirationTime, time.Second*1)
|
||||
}
|
||||
|
||||
func TestExpirationMaxAndSMax(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
// cache should select the SMax age since this is a shared cache.
|
||||
obj.RespDirectives.MaxAge = DeltaSeconds(60)
|
||||
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.WithinDuration(t, now.Add(time.Second*900), rv.OutExpirationTime, time.Second*1)
|
||||
}
|
||||
|
||||
func TestExpirationExpires(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
// cache should select the SMax age since this is a shared cache.
|
||||
obj.RespExpiresHeader = now.Add(time.Second * 1500)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.WithinDuration(t, now.Add(time.Second*1500), rv.OutExpirationTime, time.Second*1)
|
||||
}
|
||||
|
||||
func TestExpirationExpiresNoServerDate(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
obj := fill(t, now)
|
||||
// cache should select the SMax age since this is a shared cache.
|
||||
obj.RespDateHeader = time.Time{}
|
||||
obj.RespExpiresHeader = now.Add(time.Second * 1500)
|
||||
|
||||
rv := ObjectResults{}
|
||||
ExpirationObject(&obj, &rv)
|
||||
require.Len(t, rv.OutWarnings, 0)
|
||||
require.WithinDuration(t, now.Add(time.Second*1500), rv.OutExpirationTime, time.Second*1)
|
||||
}
|
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
// Repersents a potential Reason to not cache an object.
|
||||
//
|
||||
// Applications may wish to ignore specific reasons, which will make them non-RFC
|
||||
// compliant, but this type gives them specific cases they can choose to ignore,
|
||||
// making them compliant in as many cases as they can.
|
||||
type Reason int
|
||||
|
||||
const (
|
||||
|
||||
// The request method was POST and an Expiration header was not supplied.
|
||||
ReasonRequestMethodPOST Reason = iota
|
||||
|
||||
// The request method was PUT and PUTs are not cachable.
|
||||
ReasonRequestMethodPUT
|
||||
|
||||
// The request method was DELETE and DELETEs are not cachable.
|
||||
ReasonRequestMethodDELETE
|
||||
|
||||
// The request method was CONNECT and CONNECTs are not cachable.
|
||||
ReasonRequestMethodCONNECT
|
||||
|
||||
// The request method was OPTIONS and OPTIONS are not cachable.
|
||||
ReasonRequestMethodOPTIONS
|
||||
|
||||
// The request method was TRACE and TRACEs are not cachable.
|
||||
ReasonRequestMethodTRACE
|
||||
|
||||
// The request method was not recognized by cachecontrol, and should not be cached.
|
||||
ReasonRequestMethodUnkown
|
||||
|
||||
// The request included an Cache-Control: no-store header
|
||||
ReasonRequestNoStore
|
||||
|
||||
// The request included an Authorization header without an explicit Public or Expiration time: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||
ReasonRequestAuthorizationHeader
|
||||
|
||||
// The response included an Cache-Control: no-store header
|
||||
ReasonResponseNoStore
|
||||
|
||||
// The response included an Cache-Control: private header and this is not a Private cache
|
||||
ReasonResponsePrivate
|
||||
|
||||
// The response failed to meet at least one of the conditions specified in RFC 7234 section 3: http://tools.ietf.org/html/rfc7234#section-3
|
||||
ReasonResponseUncachableByDefault
|
||||
)
|
||||
|
||||
func (r Reason) String() string {
|
||||
switch r {
|
||||
case ReasonRequestMethodPOST:
|
||||
return "ReasonRequestMethodPOST"
|
||||
case ReasonRequestMethodPUT:
|
||||
return "ReasonRequestMethodPUT"
|
||||
case ReasonRequestMethodDELETE:
|
||||
return "ReasonRequestMethodDELETE"
|
||||
case ReasonRequestMethodCONNECT:
|
||||
return "ReasonRequestMethodCONNECT"
|
||||
case ReasonRequestMethodOPTIONS:
|
||||
return "ReasonRequestMethodOPTIONS"
|
||||
case ReasonRequestMethodTRACE:
|
||||
return "ReasonRequestMethodTRACE"
|
||||
case ReasonRequestMethodUnkown:
|
||||
return "ReasonRequestMethodUnkown"
|
||||
case ReasonRequestNoStore:
|
||||
return "ReasonRequestNoStore"
|
||||
case ReasonRequestAuthorizationHeader:
|
||||
return "ReasonRequestAuthorizationHeader"
|
||||
case ReasonResponseNoStore:
|
||||
return "ReasonResponseNoStore"
|
||||
case ReasonResponsePrivate:
|
||||
return "ReasonResponsePrivate"
|
||||
case ReasonResponseUncachableByDefault:
|
||||
return "ReasonResponseUncachableByDefault"
|
||||
}
|
||||
|
||||
panic(r)
|
||||
}
|
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package cacheobject
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Repersents an HTTP Warning: http://tools.ietf.org/html/rfc7234#section-5.5
|
||||
type Warning int
|
||||
|
||||
const (
|
||||
// Response is Stale
|
||||
// A cache SHOULD generate this whenever the sent response is stale.
|
||||
WarningResponseIsStale Warning = 110
|
||||
|
||||
// Revalidation Failed
|
||||
// A cache SHOULD generate this when sending a stale
|
||||
// response because an attempt to validate the response failed, due to an
|
||||
// inability to reach the server.
|
||||
WarningRevalidationFailed Warning = 111
|
||||
|
||||
// Disconnected Operation
|
||||
// A cache SHOULD generate this if it is intentionally disconnected from
|
||||
// the rest of the network for a period of time.
|
||||
WarningDisconnectedOperation Warning = 112
|
||||
|
||||
// Heuristic Expiration
|
||||
//
|
||||
// A cache SHOULD generate this if it heuristically chose a freshness
|
||||
// lifetime greater than 24 hours and the response's age is greater than
|
||||
// 24 hours.
|
||||
WarningHeuristicExpiration Warning = 113
|
||||
|
||||
// Miscellaneous Warning
|
||||
//
|
||||
// The warning text can include arbitrary information to be presented to
|
||||
// a human user or logged. A system receiving this warning MUST NOT
|
||||
// take any automated action, besides presenting the warning to the
|
||||
// user.
|
||||
WarningMiscellaneousWarning Warning = 199
|
||||
|
||||
// Transformation Applied
|
||||
//
|
||||
// This Warning code MUST be added by a proxy if it applies any
|
||||
// transformation to the representation, such as changing the
|
||||
// content-coding, media-type, or modifying the representation data,
|
||||
// unless this Warning code already appears in the response.
|
||||
WarningTransformationApplied Warning = 214
|
||||
|
||||
// Miscellaneous Persistent Warning
|
||||
//
|
||||
// The warning text can include arbitrary information to be presented to
|
||||
// a human user or logged. A system receiving this warning MUST NOT
|
||||
// take any automated action.
|
||||
WarningMiscellaneousPersistentWarning Warning = 299
|
||||
)
|
||||
|
||||
func (w Warning) HeaderString(agent string, date time.Time) string {
|
||||
if agent == "" {
|
||||
agent = "-"
|
||||
} else {
|
||||
// TODO(pquerna): this doesn't escape agent if it contains bad things.
|
||||
agent = `"` + agent + `"`
|
||||
}
|
||||
return fmt.Sprintf(`%d %s "%s" %s`, w, agent, w.String(), date.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
func (w Warning) String() string {
|
||||
switch w {
|
||||
case WarningResponseIsStale:
|
||||
return "Response is Stale"
|
||||
case WarningRevalidationFailed:
|
||||
return "Revalidation Failed"
|
||||
case WarningDisconnectedOperation:
|
||||
return "Disconnected Operation"
|
||||
case WarningHeuristicExpiration:
|
||||
return "Heuristic Expiration"
|
||||
case WarningMiscellaneousWarning:
|
||||
// TODO(pquerna): ideally had a better way to override this one code.
|
||||
return "Miscellaneous Warning"
|
||||
case WarningTransformationApplied:
|
||||
return "Transformation Applied"
|
||||
case WarningMiscellaneousPersistentWarning:
|
||||
// TODO(pquerna): same as WarningMiscellaneousWarning
|
||||
return "Miscellaneous Persistent Warning"
|
||||
}
|
||||
|
||||
panic(w)
|
||||
}
|
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright 2015 Paul Querna
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package cachecontrol implements the logic for HTTP Caching
|
||||
//
|
||||
// Deciding if an HTTP Response can be cached is often harder
|
||||
// and more bug prone than an actual cache storage backend.
|
||||
// cachecontrol provides a simple interface to determine if
|
||||
// request and response pairs are cachable as defined under
|
||||
// RFC 7234 http://tools.ietf.org/html/rfc7234
|
||||
package cachecontrol
|
Loading…
Add table
Reference in a new issue