github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1008/sa1008.go (about) 1 package sa1008 2 3 import ( 4 "fmt" 5 "go/ast" 6 "net/http" 7 "strconv" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/edit" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/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.Documentation{ 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.IsOfType(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.IsOfType(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 }