lib/httputils/csp.go (75 lines of code) (raw):
// Copyright 2020 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httputils
import (
"net/http"
"sort"
"strings"
"bitbucket.org/creachadair/stringset" /* copybara-comment */
)
// CSP handles Content Security Policy headers.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
type CSP struct {
data map[string]*stringset.Set
}
// CSPFromString reads csp from header.
func CSPFromString(str string) *CSP {
c := &CSP{data: map[string]*stringset.Set{}}
for _, s := range strings.Split(str, ";") {
trimed := strings.TrimSpace(s)
ss := strings.Split(trimed, " ")
if len(ss) < 2 {
continue
}
name := ss[0]
for _, value := range ss[1:] {
c.add(name, value)
}
}
return c
}
func mergeCSP(a, b *CSP) *CSP {
if a == nil || len(a.data) == 0 {
return b
}
if b == nil || len(b.data) == 0 {
return a
}
c := &CSP{data: map[string]*stringset.Set{}}
for name, values := range a.data {
for _, v := range values.Unordered() {
c.add(name, v)
}
}
for name, values := range b.data {
for _, v := range values.Unordered() {
c.add(name, v)
}
}
return c
}
// add given name, value to CSP
func (s *CSP) add(name, value string) {
if len(name) == 0 || len(value) == 0 {
return
}
if _, ok := s.data[name]; !ok {
s.data[name] = &stringset.Set{}
}
s.data[name].Add(value)
}
func (s *CSP) addToHeader(w http.ResponseWriter) {
var list []string
// 1. each policy format as: name value1 value2
for name, values := range s.data {
vs := []string{name}
vs = append(vs, values.Elements()...)
list = append(list, strings.Join(vs, " "))
}
sort.Strings(list)
// 2. policies separated by ";"
v := strings.Join(list, ";")
w.Header().Set("Content-Security-Policy", v)
}
var (
// pageCSP : for page, we should only allow item we know.
pageCSP = CSPFromString(
// fallback policy
"default-src 'self';" +
// ajax.googleapis.com only includes small number of libs. https://developers.google.com/speed/libraries
// code.getmdl.io for Material Design Lite style
"script-src 'self' https://ajax.googleapis.com https://code.getmdl.io;" +
// fonts.googleapis.com for fonts
// code.getmdl.io for Material Design Lite style
"style-src 'self' https://fonts.googleapis.com https://code.getmdl.io;" +
// fonts.gstatic.com for fonts
"font-src https://fonts.gstatic.com;" +
// data: for Material Design Lite style icon inline css
"img-src 'self' data:;" +
// allow frame hosted on same host to contain this page
"frame-ancestors 'self'",
)
)