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



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

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



206
207
208
209
210
211
212
213
214
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 206

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



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

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

  @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



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

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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 189

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.



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
# 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
    Jekyll.logger.warn @csp_image_src
    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



223
224
225
226
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 223

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

#get_domain(url) ⇒ Object



216
217
218
219
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 216

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



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

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] == 'image-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
  end
end

#runObject

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



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/jekyll-content-security-policy-generator/hook.rb', line 230

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