forked from d4l3k/go-csp-engine
/
html.go
163 lines (143 loc) · 3.7 KB
/
html.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package csp
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/PuerkitoBio/goquery"
)
var (
htmlDirectiveElements = map[string]string{
"script-src": "script",
"img-src": "img",
"media-src": "audio, video, track",
"frame-src": "iframe",
"object-src": "object, embed, applet",
"style-src": "style",
}
htmlPassiveElements = map[string]bool{
"img": true,
"audio": true,
"video": true,
"object": true,
}
)
// ValidatePage checks that an HTML page passes the specified CSP policy.
func ValidatePage(p Policy, page url.URL, html io.Reader) (bool, []Report, error) {
doc, err := goquery.NewDocumentFromReader(html)
if err != nil {
return false, nil, err
}
var reports []Report
for directiveName, elems := range htmlDirectiveElements {
directive := p.Directive(directiveName)
var err2 error
doc.Find(elems).Each(func(i int, s *goquery.Selection) {
ctx := SourceContext{
Page: page,
Nonce: s.AttrOr("nonce", ""),
}
elementName := strings.ToLower(s.Nodes[0].Data)
passiveContent := htmlPassiveElements[elementName]
src := s.AttrOr("src", "")
if len(src) > 0 {
parsed, err := url.Parse(src)
if err != nil {
err2 = err
return
}
ctx.URL = *page.ResolveReference(parsed)
} else {
ctx.Body = []byte(s.Text())
ctx.UnsafeInline = true
}
// Upgrade insecure passive content http requests to correctly support
// mixed content.
if ctx.Page.Scheme == "https" && ctx.URL.Scheme == "http" && ((passiveContent && !p.BlockAllMixedContent) || p.UpgradeInsecureRequests) {
ctx.URL.Scheme = "https"
}
v, err := directive.Check(p, ctx)
if err != nil {
err2 = err
return
}
if !v {
reports = append(reports, ctx.Report(directiveName, directive))
}
if goquery.NodeName(s) == "style" {
_, reportsCSS, err := ValidateStylesheet(p, page, s.Text())
if err != nil {
err2 = err
return
}
reports = append(reports, reportsCSS...)
}
})
if err2 != nil {
return false, nil, err2
}
}
hrefTypes := map[string]string{
"base-uri": "base",
"style-src": "link[rel=stylesheet]",
"prefetch-src": "link[rel=prefetch], link[rel=prerender]",
"manifest-src": "link[rel=manifest]",
"img-src": "link[rel=icon], link[rel=apple-touch-icon]",
}
for directiveName, elems := range hrefTypes {
directive := p.Directive(directiveName)
var err2 error
doc.Find(elems).Each(func(i int, s *goquery.Selection) {
ctx := SourceContext{
Page: page,
Nonce: s.AttrOr("nonce", ""),
}
href := s.AttrOr("href", "")
if len(href) > 0 {
parsed, err := url.Parse(href)
if err != nil {
err2 = err
return
}
ctx.URL = *page.ResolveReference(parsed)
}
v, err := directive.Check(p, ctx)
if err != nil {
err2 = err
return
}
if !v {
reports = append(reports, ctx.Report(directiveName, directive))
}
})
if err2 != nil {
return false, nil, err2
}
}
return len(reports) == 0, reports, nil
}
// retrieves the current CSP setting from a web page
func GetCSPFromWeb(webaddress string) (string, string) {
// create an http client object
client := &http.Client{}
// create a new GET request
req, err := http.NewRequest("GET", webaddress, nil)
if err != nil {
return "", fmt.Sprintf("Error creating request: %s", err)
}
// make the request
resp, err := client.Do(req)
if err != nil {
return "", fmt.Sprintf("Error making request: %s", err)
}
// read the response body
// body, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// fmt.Println("Error reading response: ", err)
// return
// }
// print the response body
//fmt.Println(string(body))
return resp.Header.Get("content-security-policy"), ""
}