src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa1008.go (3194B)


      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) (any, 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 }