Class: Mailscope::BodyRenderer

Inherits:
Object
  • Object
show all
Defined in:
lib/mailscope/body_renderer.rb

Overview

Builds the document that goes inside the preview iframe.

The body is never rewritten with blind gsubs the way letter_opener_web did it. Inline (cid:) images become data: URIs so the frame can stay in a fully sandboxed opaque origin, and everything else is controlled with a Content-Security-Policy response header rather than by editing the markup.

Constant Summary collapse

MAX_INLINE_BYTES =
2 * 1024 * 1024
REMOTE_ASSET =
/\b(?:src|background)\s*=\s*["']?\s*https?:/i
REMOTE_CSS_ASSET =
/url\(\s*["']?\s*https?:/i
URL_IN_TEXT =
%r{\bhttps?://[^\s<>"')\]]+}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(message, part: nil, block_remote: true, theme: 'light') ⇒ BodyRenderer

Returns a new instance of BodyRenderer.



21
22
23
24
25
26
# File 'lib/mailscope/body_renderer.rb', line 21

def initialize(message, part: nil, block_remote: true, theme: 'light')
  @message      = message
  @part         = %w[html text].include?(part.to_s) ? part.to_s : message.default_part
  @block_remote = block_remote
  @theme        = theme.to_s == 'dark' ? 'dark' : 'light'
end

Instance Attribute Details

#block_remoteObject (readonly)

Returns the value of attribute block_remote.



19
20
21
# File 'lib/mailscope/body_renderer.rb', line 19

def block_remote
  @block_remote
end

#messageObject (readonly)

Returns the value of attribute message.



19
20
21
# File 'lib/mailscope/body_renderer.rb', line 19

def message
  @message
end

#partObject (readonly)

Returns the value of attribute part.



19
20
21
# File 'lib/mailscope/body_renderer.rb', line 19

def part
  @part
end

#themeObject (readonly)

Returns the value of attribute theme.



19
20
21
# File 'lib/mailscope/body_renderer.rb', line 19

def theme
  @theme
end

Instance Method Details

#blocked_resourcesObject

How many remote assets the current policy is holding back. Surfaced in the UI so "why is this email blank?" answers itself.



63
64
65
66
67
# File 'lib/mailscope/body_renderer.rb', line 63

def blocked_resources
  return 0 unless block_remote

  @blocked_resources ||= raw_body.to_s.scan(REMOTE_ASSET).size + raw_body.to_s.scan(REMOTE_CSS_ASSET).size
end

#content_security_policyObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/mailscope/body_renderer.rb', line 45

def content_security_policy
  directives = [
    "default-src 'none'",
    "style-src 'unsafe-inline' data:",
    'font-src data:',
    "frame-src 'none'",
    "object-src 'none'",
    "script-src 'none'",
    "form-action 'none'",
    "base-uri 'none'"
  ]
  directives << (block_remote ? 'img-src data:' : 'img-src data: https: http:')
  directives << (block_remote ? "media-src 'none'" : 'media-src data: https: http:')
  directives.join('; ')
end

#dark?Boolean

Returns:

  • (Boolean)


28
# File 'lib/mailscope/body_renderer.rb', line 28

def dark? = theme == 'dark'

#documentObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mailscope/body_renderer.rb', line 30

def document
  <<~HTML
    <!doctype html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <base target="_blank">
    <style>#{frame_styles}</style>
    </head>
    <body class="mailscope-frame mailscope-frame--#{part} mailscope-frame--#{theme}">#{body}</body>
    </html>
  HTML
end

#empty?Boolean

Returns:

  • (Boolean)


69
# File 'lib/mailscope/body_renderer.rb', line 69

def empty? = raw_body.to_s.strip.empty?