sa1008.go raw
1 package sa1008
2
3 import (
4 "fmt"
5 "go/ast"
6 "net/http"
7 "strconv"
8
9 "honnef.co/go/tools/analysis/code"
10 "honnef.co/go/tools/analysis/edit"
11 "honnef.co/go/tools/analysis/lint"
12 "honnef.co/go/tools/analysis/report"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 "golang.org/x/tools/go/ast/inspector"
17 )
18
19 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
20 Analyzer: &analysis.Analyzer{
21 Name: "SA1008",
22 Run: run,
23 Requires: []*analysis.Analyzer{inspect.Analyzer},
24 },
25 Doc: &lint.RawDocumentation{
26 Title: `Non-canonical key in \'http.Header\' map`,
27 Text: `Keys in \'http.Header\' maps are canonical, meaning they follow a specific
28 combination of uppercase and lowercase letters. Methods such as
29 \'http.Header.Add\' and \'http.Header.Del\' convert inputs into this canonical
30 form before manipulating the map.
31
32 When manipulating \'http.Header\' maps directly, as opposed to using the
33 provided methods, care should be taken to stick to canonical form in
34 order to avoid inconsistencies. The following piece of code
35 demonstrates one such inconsistency:
36
37 h := http.Header{}
38 h["etag"] = []string{"1234"}
39 h.Add("etag", "5678")
40 fmt.Println(h)
41
42 // Output:
43 // map[Etag:[5678] etag:[1234]]
44
45 The easiest way of obtaining the canonical form of a key is to use
46 \'http.CanonicalHeaderKey\'.`,
47 Since: "2017.1",
48 Severity: lint.SeverityWarning,
49 MergeIf: lint.MergeIfAny,
50 },
51 })
52
53 var Analyzer = SCAnalyzer.Analyzer
54
55 func run(pass *analysis.Pass) (interface{}, error) {
56 fn := func(node ast.Node, push bool) bool {
57 if !push {
58 return false
59 }
60 if assign, ok := node.(*ast.AssignStmt); ok {
61 // TODO(dh): This risks missing some Header reads, for
62 // example in `h1["foo"] = h2["foo"]` – these edge
63 // cases are probably rare enough to ignore for now.
64 for _, expr := range assign.Lhs {
65 op, ok := expr.(*ast.IndexExpr)
66 if !ok {
67 continue
68 }
69 if code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
70 return false
71 }
72 }
73 return true
74 }
75 op, ok := node.(*ast.IndexExpr)
76 if !ok {
77 return true
78 }
79 if !code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
80 return true
81 }
82 s, ok := code.ExprToString(pass, op.Index)
83 if !ok {
84 return true
85 }
86 canonical := http.CanonicalHeaderKey(s)
87 if s == canonical {
88 return true
89 }
90 var fix analysis.SuggestedFix
91 switch op.Index.(type) {
92 case *ast.BasicLit:
93 fix = edit.Fix("canonicalize header key", edit.ReplaceWithString(op.Index, strconv.Quote(canonical)))
94 case *ast.Ident:
95 call := &ast.CallExpr{
96 Fun: edit.Selector("http", "CanonicalHeaderKey"),
97 Args: []ast.Expr{op.Index},
98 }
99 fix = edit.Fix("wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
100 }
101 msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s)
102 if fix.Message != "" {
103 report.Report(pass, op, msg, report.Fixes(fix))
104 } else {
105 report.Report(pass, op, msg)
106 }
107 return true
108 }
109 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn)
110 return nil, nil
111 }
112