Copyright 2020 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
csphash computes the hashes of script tags in files, and checks that they are added to our content security policy.
package main

import (
	
	
	
	
	
	
	
	
	
)

var hashFile = flag.String("hf", "internal/middleware/secureheaders.go", "file with hashes for CSP header")

func () {
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "usage: %s [flags] FILES\n", os.Args[0])
		fmt.Fprintf(flag.CommandLine.Output(), "suggestion for FILES: content/static/html/**/*.tmpl\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() == 0 {
		flag.Usage()
		os.Exit(1)
	}

	,  := extractHashes(*hashFile)
	if  != nil {
		log.Fatal()
	}
	 := map[string]bool{}
	for ,  := range  {
		[] = true
	}

	 := true
	for ,  := range flag.Args() {
		,  := scripts()
		if  != nil {
			log.Fatal()
		}
		for ,  := range  {
			if bytes.Contains(.tag, []byte("src=")) {
				fmt.Printf("%s: has script with src attribute: %s\n", , .tag)
				 = false
			}
			if bytes.Contains(.body, []byte("{{")) {
				fmt.Printf("%s: has script with template expansion:\n%s\n", , .body)
				fmt.Printf("Scripts must be static so they have a constant hash.\n")
				 = false
				continue
			}
			 := cspHash(.body)
			if ![] {
				fmt.Printf("missing hash: add the lines below to %s:\n", *hashFile)
				fmt.Printf("    // From %s\n", )
				fmt.Printf(`    "'sha256-%s'",`, )
				fmt.Println()
				 = false
			} else {
				delete(, )
			}
		}
	}
	for  := range  {
		fmt.Printf("unused hash %s\n", )
		 = false
	}
	if ! {
		fmt.Printf("Add missing hashes to %s and remove unused ones.\n", *hashFile)
		os.Exit(1)
	}
}

var hashRegexp = regexp.MustCompile(`'sha256-([^']+)'`)
extractHashes scans the given file for CSP-style hashes and returns them.
func ( string) ([]string, error) {
	,  := ioutil.ReadFile()
	if  != nil {
		return nil, 
	}
	var  []string
	 := hashRegexp.FindAllSubmatch(, -1)
	for ,  := range  {
		 = append(, string([1]))
	}
	return , nil
}

func ( []byte) string {
	 := sha256.Sum256()
	return base64.StdEncoding.EncodeToString([:])
}
script represents an HTML script element.
type script struct {
	tag  []byte // `<script attr="a"...>`
	body []byte // text between open and close script tags
}
scripts returns all the script elements in the given file.
func ( string) ([]*script, error) {
	,  := ioutil.ReadFile()
	if  != nil {
		return nil, fmt.Errorf("%s: %v", , )
	}
	return scriptsReader()
}

Assume none of the attribute values contain a '>'. Regexp flag `i` means case-insensitive.
Assume all scripts end with a full close tag.
	scriptEndRegexp = regexp.MustCompile(`(?i:</script>)`)
)

func ( []byte) ([]*script, error) {
	var  []*script
	 := 0
	for {
		 := scriptStartRegexp.FindIndex()
		if  == nil {
			return , nil
		}
		 := [[0]:[1]]
		 = [[1]:]
		 += [1]
		 := scriptEndRegexp.FindIndex()
		if  == nil {
			return nil, fmt.Errorf("%s is missing an end tag", )
		}
		 = append(, &script{
			tag:  ,
			body: [:[0]],
		})
		 = [[1]:]
		 += [1]
	}