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