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  }