@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 179 lines 5.39 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Package headercheck verifies that every http.Header key written directly
16 // into the map uses net/http's canonical form.
17 //
18 // Hot paths index http.Header directly to skip the canonicalization that
19 // Get/Set/Add/Del perform on every call. That trades a linted call for an
20 // unlinted map operation: the canonicalheader linter only inspects method
21 // calls, and staticcheck's SA1008 stops descending as soon as an assignment
22 // writes to a header, so neither covers a direct write or a delete. A key
23 // that is not canonical there is a silent lookup miss, not a compile error.
24 //
25 // This covers four shapes the enabled linters miss:
26 //
27 // h["x-key"] = []string{"v"} // direct write
28 // http.Header{"x-key": {"v"}} // composite literal
29 // delete(h, "x-key") // delete builtin
30 // h["x-key"] = append(h["x-key"], "v) // read-modify-write
31 package headercheck
32
33 import (
34 "go/ast"
35 "go/constant"
36 "go/token"
37 "go/types"
38 "net/http"
39 "strings"
40 "testing"
41
42 "golang.org/x/tools/go/packages"
43 )
44
45 // headerType is the type whose map operations must use canonical keys.
46 const headerType = "net/http.Header"
47
48 // nolintDirective marks a deliberate non-canonical key on its line, such as
49 // the RFC 6455 spellings written for case-sensitive upstreams.
50 const nolintDirective = "//nolint:canonicalheader"
51
52 // TODO: delete this package once golangci-lint ships https://github.com/lasiar/canonicalheader/pull/90;
53 // the linter honors the same //nolint:canonicalheader directives.
54 func TestHeaderKeysAreCanonical(t *testing.T) {
55 cfg := &packages.Config{
56 Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes |
57 packages.NeedTypesInfo | packages.NeedDeps | packages.NeedImports,
58 Dir: "../..",
59 Tests: false,
60 }
61
62 pkgs, err := packages.Load(cfg, "./...")
63 if err != nil {
64 t.Fatalf("loading packages: %v", err)
65 }
66 if packages.PrintErrors(pkgs) > 0 {
67 t.Fatal("packages failed to load; see errors above")
68 }
69
70 var checked int
71 for _, pkg := range pkgs {
72 for _, file := range pkg.Syntax {
73 checked += checkFile(t, pkg, file)
74 }
75 }
76
77 // Guard against the walk silently covering nothing, e.g. if package
78 // loading is reconfigured and stops yielding type information.
79 if checked == 0 {
80 t.Fatal("no http.Header map operations found; the check is not running")
81 }
82 t.Logf("checked %d direct http.Header map operations", checked)
83 }
84
85 // checkFile reports the number of direct http.Header map operations examined.
86 func checkFile(t *testing.T, pkg *packages.Package, file *ast.File) int {
87 var checked int
88
89 exempt := make(map[int]bool)
90 for _, group := range file.Comments {
91 for _, c := range group.List {
92 if strings.HasPrefix(c.Text, nolintDirective) {
93 exempt[pkg.Fset.Position(c.Slash).Line] = true
94 }
95 }
96 }
97
98 report := func(pos token.Pos, key, form string) {
99 canonical := http.CanonicalHeaderKey(key)
100 if key == canonical || exempt[pkg.Fset.Position(pos).Line] {
101 return
102 }
103 t.Errorf("%s: %s uses non-canonical key %q; want %q",
104 pkg.Fset.Position(pos), form, key, canonical)
105 }
106
107 // keyOf returns the constant string value of an index or argument
108 // expression, so both literals and named constants are covered.
109 keyOf := func(expr ast.Expr) (string, bool) {
110 tv, ok := pkg.TypesInfo.Types[expr]
111 if !ok || tv.Value == nil || tv.Value.Kind() != constant.String {
112 return "", false
113 }
114 return constant.StringVal(tv.Value), true
115 }
116
117 isHeader := func(expr ast.Expr) bool {
118 tv, ok := pkg.TypesInfo.Types[expr]
119 if !ok || tv.Type == nil {
120 return false
121 }
122 named, ok := types.Unalias(tv.Type).(*types.Named)
123 if !ok {
124 return false
125 }
126 obj := named.Obj()
127 return obj != nil && obj.Pkg() != nil &&
128 obj.Pkg().Path()+"."+obj.Name() == headerType
129 }
130
131 ast.Inspect(file, func(n ast.Node) bool {
132 switch node := n.(type) {
133 // h["Key"], including the assignment form h["Key"] = ...
134 // that SA1008 skips.
135 case *ast.IndexExpr:
136 if !isHeader(node.X) {
137 return true
138 }
139 key, ok := keyOf(node.Index)
140 if !ok {
141 return true
142 }
143 checked++
144 report(node.Index.Pos(), key, "http.Header index")
145
146 // http.Header{"Key": ...}, which stores the key verbatim.
147 case *ast.CompositeLit:
148 if !isHeader(node) {
149 return true
150 }
151 for _, elt := range node.Elts {
152 kv, ok := elt.(*ast.KeyValueExpr)
153 if !ok {
154 continue
155 }
156 key, ok := keyOf(kv.Key)
157 if !ok {
158 continue
159 }
160 checked++
161 report(kv.Key.Pos(), key, "http.Header literal")
162 }
163
164 // delete(h, "Key"), which no enabled linter inspects.
165 case *ast.CallExpr:
166 if ident, ok := node.Fun.(*ast.Ident); ok && ident.Name == "delete" && len(node.Args) == 2 {
167 if _, ok := pkg.TypesInfo.Uses[ident].(*types.Builtin); ok && isHeader(node.Args[0]) {
168 if key, ok := keyOf(node.Args[1]); ok {
169 checked++
170 report(node.Args[1].Pos(), key, "delete on http.Header")
171 }
172 }
173 }
174 }
175 return true
176 })
177
178 return checked
179 }