Class: Jekyll::JekyllContentSecurityPolicyGenerator::ContentSecurityPolicyGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/jekyll-content-security-policy-generator/hook.rb

Overview

Provides the ability to generate a content security policy for inline scripts and styles. Will reuse an existing CSP or generate a new one and insert in HEAD.

Instance Method Summary collapse

Constructor Details

#initialize(document_html) ⇒ ContentSecurityPolicyGenerator

Returns a new instance of ContentSecurityPolicyGenerator.



18
19
20
21
22
23
24
25
26
27
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 18

def initialize(document_html)
  @document_html = document_html
  @nokogiri = Nokogiri::HTML(document_html)

  @csp_frame_src = ['\'self\'']
  @csp_image_src = ['\'self\'']
  @csp_style_src = ['\'self\'']
  @csp_script_src = ['\'self\'']
  @csp_unknown = []
end

Instance Method Details

#convert_all_inline_styles_attributesObject

This function converts elements with style=“color:red” attributes into inline styles



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
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 118

def convert_all_inline_styles_attributes
  @nokogiri.css('*').each do |find|
    find_src = find.attr('style')

    if find_src
      if find.attr('id')
        element_id = find.attr('id')
      else
        hash = Digest::MD5.hexdigest find_src + "#{Random.rand(11)}"
        element_id = "csp-gen-" + hash
        find["id"] = element_id
      end

      new_element = "<style>#" + element_id + " { " + find_src + " } </style>"
      find.remove_attribute("style")

      if @nokogiri.at('head')
        @nokogiri.at('head') << new_element
        Jekyll.logger.info'Converting style attribute to inline style, inserted into HEAD.'
      else
        if @nokogiri.at('body')
          @nokogiri.at('body') << new_element
          Jekyll.logger.info'Converting style attribute to inline style, inserted into BODY.'
        else
          Jekyll.logger.warn'Unable to convert style attribute to inline style, no HEAD or BODY found.'
        end
      end
    end
  end
end

#find_iframesObject

Find all iframes



210
211
212
213
214
215
216
217
218
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 210

def find_iframes
  @nokogiri.css('iframe').each do |find|
    find_src = find.attr('src')

    if find_src and find_src.start_with?('http', 'https')
      @csp_frame_src.push find_src.match(/(.*\/)+(.*$)/)[1]
    end
  end
end

#find_imagesObject

Find all images



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 151

def find_images
  @nokogiri.css('img').each do |find|
    find_src = find.attr('src')

    if find_src and find_src.start_with?('http', 'https')
      @csp_image_src.push find_src.match(/(.*\/)+(.*$)/)[1]
    end
  end

  @nokogiri.css('style').each do |find|
    finds = find.content.scan(/url\(([^\)]+)\)/)

    finds.each do |innerFind|
      innerFind = innerFind[0]
      innerFind = innerFind.tr('\'"', '')
      if innerFind.start_with?('http', 'https')
        @csp_image_src.push self.get_domain(innerFind)
      end
    end
  end

end

#find_scriptsObject

Find all scripts



176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 176

def find_scripts
  @nokogiri.css('script').each do |find|
    if find.attr('src')
      find_src = find.attr('src')

      if find_src and find_src.start_with?('http', 'https')
        @csp_script_src.push find_src.match(/(.*\/)+(.*$)/)[1]
      end

    else
      @csp_script_src.push self.generate_sha256_content_hash find.content
    end
  end
end

#find_stylesObject

Find all stylesheets



193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 193

def find_styles
  @nokogiri.css('style').each do |find|
    if find.attr('src')
      find_src = find.attr('src')

      if find_src and find_src.start_with?('http', 'https')
        @csp_style_src.push find_src.match(/(.*\/)+(.*$)/)[1]
      end

    else
      @csp_style_src.push self.generate_sha256_content_hash find.content
    end
  end
end

#generate_convert_security_policy_meta_tagObject

Creates an HTML content security policy meta tag.



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
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 31

def generate_convert_security_policy_meta_tag
  meta_content = ""

  @csp_script_src = @csp_script_src.uniq
  @csp_image_src = @csp_image_src.uniq
  @csp_style_src = @csp_style_src.uniq
  @csp_script_src = @csp_script_src.uniq
  @csp_unknown = @csp_unknown.uniq

  if @csp_frame_src.length > 0
    @csp_script_src.uniq
    meta_content += "frame-src " + @csp_frame_src.join(' ') + '; '
  end

  if @csp_image_src.length > 0
    meta_content += "img-src " + @csp_image_src.join(' ') + '; '
  end

  if @csp_style_src.length > 0
    meta_content += "style-src " + @csp_style_src.join(' ') + '; '
  end

  if @csp_script_src.length > 0
    meta_content += "script-src " + @csp_script_src.join(' ') + '; '
  end

  if @csp_unknown.length > 0
    @csp_unknown.each do |find|
      find_name = find[0]
      find = find.drop(1)
      meta_content += find_name + " " + find.join(' ') + '; '
    end
  end

  if @nokogiri.at("head")
    Jekyll.logger.info "Generated content security policy, inserted in HEAD."
    @nokogiri.at("head") << "<meta http-equiv=\"Content-Security-Policy\" content=\"" + meta_content + "\">"
  elsif @nokogiri.at("body")
    Jekyll.logger.info "Generated content security policy, inserted in BODY."
    @nokogiri.at("body") << "<meta http-equiv=\"Content-Security-Policy\" content=\"" + meta_content + "\">"
  else
    Jekyll.logger.error "Generated content security policy but found no-where to insert it."
  end

end

#generate_sha256_content_hash(content) ⇒ Object

Generate a content hash



227
228
229
230
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 227

def generate_sha256_content_hash(content)
  hash = Digest::SHA2.base64digest content
  "'sha256-#{hash}'"
end

#get_domain(url) ⇒ Object



220
221
222
223
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 220

def get_domain(url)
  uri = URI.parse(url)
  "#{uri.scheme}://#{uri.host}"
end

#parse_existing_meta_elementObject

Parse an existing content security policy meta tag



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
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 79

def parse_existing_meta_element()
  csp = @nokogiri.at('meta[http-equiv="Content-Security-Policy"]')

  if csp
    content = csp.attr('content')
    content = content.strip! || content
    policies = content.split(';')

    policies.each do |policy|
      policy = policy.strip! || policy

      if policy.include? ' '
        policy_parts = policy.split(' ')

        if policy_parts[0] == 'script-src'
          @csp_script_src.concat(policy_parts.drop(1))
        elsif policy_parts[0] == 'style-src'
          @csp_style_src.concat(policy_parts.drop(1))
        elsif policy_parts[0] == 'img-src'
          @csp_image_src.concat(policy_parts.drop(1))
        elsif policy_parts[0] == 'frame-src'
          @csp_frame_src.concat(policy_parts.drop(1))
        else
          @csp_unknown.concat([policy_parts])
        end

      else
        Jekyll.logger.warn "Incorrect existing content security policy meta tag found, skipping."
      end
    end

    @nokogiri.search('meta[http-equiv="Content-Security-Policy"]').each do |el|
      el.remove
    end
  end
end

#runObject

Builds an HTML meta tag based on the found inline scripts and style hashes



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 234

def run
  self.parse_existing_meta_element

  self.convert_all_inline_styles_attributes

  # Find elements in document
  self.find_images
  self.find_styles
  self.find_scripts
  self.find_iframes

  self.generate_convert_security_policy_meta_tag

  @nokogiri.to_html
end