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.



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

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



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

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
        element_id = Digest::MD5.hexdigest find_src + "#{Random.rand(11)}"
        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



188
189
190
191
192
193
194
195
196
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 188

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

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

#find_imagesObject

Find all images



142
143
144
145
146
147
148
149
150
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 142

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

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

#find_scriptsObject

Find all scripts



154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 154

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

      if 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



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 171

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

      if 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.



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

def generate_convert_security_policy_meta_tag
  meta_content = ""

  if @csp_frame_src.length > 0
    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



200
201
202
203
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 200

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

#parse_existing_meta_elementObject

Parse an existing content security policy meta tag



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

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))
          @csp_script_src = @csp_script_src.uniq
        elsif policy_parts[0] == 'style-src'
          @csp_style_src.concat(policy_parts.drop(1))
          @csp_style_src = @csp_style_src.uniq
        elsif policy_parts[0] == 'image-src'
          @csp_image_src.concat(policy_parts.drop(1))
          @csp_image_src = @csp_image_src.uniq
        elsif policy_parts[0] == 'frame-src'
          @csp_frame_src.concat(policy_parts.drop(1))
          @csp_frame_src = @csp_frame_src.uniq
        else
          @csp_unknown.concat([policy_parts])
        end

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

#runObject

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



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

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