Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/Trog/Renderer/Base.pm
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ sub render (%options) {
return $out ? $out : $template_string;
};

$options{extra_functions} //= {};
$options{extra_functions}{render_it} = $options{child_renderer};
$renderers{$template_dir} //= Text::Xslate->new(
path => $template_dir,
function => {
render_it => $options{child_renderer},
},
function => $options{extra_functions},
);

my $code = $options{code};
Expand Down
23 changes: 20 additions & 3 deletions lib/Trog/Renderer/html.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,35 @@ use feature qw{signatures state};
use parent qw{Trog::Renderer::Base};

use Text::Xslate;
use Digest::SHA;
use MIME::Base64 qw(encode_base64);

=head1 Trog::Renderer::html

Render HTML. TODO: support inlining everything like you would want when emailing a post.

=cut

sub sri_hash ($url_path) {
state %cache;
return '' unless defined $url_path && $url_path =~ m{^/};
return $cache{$url_path} if exists $cache{$url_path};
my $file = "www$url_path";
unless ( -f $file ) {
return $cache{$url_path} = '';
}
my $digestor = Digest::SHA->new(384);
$digestor->addfile($file);
return $cache{$url_path} = 'sha384-' . encode_base64( $digestor->digest, '' );
}

sub render (%options) {
state $child_processor = Text::Xslate->new(

# Prevent a recursive descent. If the renderer is hit again, just do nothing
# XXX unfortunately if the post tries to include itself, it will die.
function => {
embed => sub {
embed => sub {
my ( $this_id, $style ) = @_;
$style //= 'embed';

Expand All @@ -35,6 +50,7 @@ sub render (%options) {
)
);
},
sri_hash => \&sri_hash,
}
);
state $child_renderer = sub {
Expand All @@ -45,8 +61,9 @@ sub render (%options) {
return $out ? $out : $template_string;
};

$options{child_processor} = $child_processor;
$options{child_renderer} = $child_renderer;
$options{child_processor} = $child_processor;
$options{child_renderer} = $child_renderer;
$options{extra_functions} = { sri_hash => \&sri_hash };

return Trog::Renderer::Base::render(%options);
}
Expand Down
50 changes: 50 additions & 0 deletions t/Trog-Renderer-html.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use strict;
use warnings;

use Test::More;
use FindBin;

use lib "$FindBin::Bin/../lib";
use lib '/tmp/perlstub';

use Digest::SHA qw(sha384);
use MIME::Base64 qw(encode_base64);

require_ok('Trog::Renderer::html') or BAIL_OUT("Can't load Trog::Renderer::html");

subtest 'sri_hash — existing file' => sub {
my $path = '/styles/screen.css';
my $result = Trog::Renderer::html::sri_hash($path);

like( $result, qr/^sha384-[A-Za-z0-9+\/]+=*$/, 'returns sha384- prefixed base64' );

open( my $fh, '<:raw', "www$path" ) or BAIL_OUT("Cannot open www$path: $!");
local $/;
my $content = <$fh>;
my $expected = 'sha384-' . encode_base64( sha384($content), '' );
is( $result, $expected, 'hash value matches independent SHA-384 computation' );
};

subtest 'sri_hash — caching' => sub {
my $path = '/styles/screen.css';
my $first = Trog::Renderer::html::sri_hash($path);
my $second = Trog::Renderer::html::sri_hash($path);
is( $second, $first, 'repeated call returns cached result' );
};

subtest 'sri_hash — non-existent file' => sub {
my $result = Trog::Renderer::html::sri_hash('/styles/no-such-file.css');
is( $result, '', 'returns empty string for missing file' );
};

subtest 'sri_hash — non-absolute path' => sub {
my $result = Trog::Renderer::html::sri_hash('styles/screen.css');
is( $result, '', 'returns empty string for non-absolute path' );
};

subtest 'sri_hash — undef input' => sub {
my $result = Trog::Renderer::html::sri_hash(undef);
is( $result, '', 'returns empty string for undef input' );
};

done_testing();
28 changes: 17 additions & 11 deletions www/templates/html/components/header.tx
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,32 @@
: }
<!-- Stylesheets !-->
: for $stylesheets -> $stylesheet {
<link rel="preload" type="text/css" href="<: $stylesheet :>" as="style" />
<link rel="stylesheet" type="text/css" href="<: $stylesheet :>" media="screen" />
: my $sri = sri_hash($stylesheet);
<link rel="preload" type="text/css" href="<: $stylesheet :>" as="style"<: $sri ? ' integrity="' ~ $sri ~ '"' : '' | mark_raw :> />
<link rel="stylesheet" type="text/css" href="<: $stylesheet :>" media="screen"<: $sri ? ' integrity="' ~ $sri ~ '"' : '' | mark_raw :> />
: }
<!-- Print Styles !-->
: for $print_styles -> $print_style {
<link rel="preload" type="text/css" href="<: $print_style :>" as="style" />
<link rel="stylesheet" type="text/css" href="<: $print_style :>" media="print" />
: my $sri = sri_hash($print_style);
<link rel="preload" type="text/css" href="<: $print_style :>" as="style"<: $sri ? ' integrity="' ~ $sri ~ '"' : '' | mark_raw :> />
<link rel="stylesheet" type="text/css" href="<: $print_style :>" media="print"<: $sri ? ' integrity="' ~ $sri ~ '"' : '' | mark_raw :> />
: }
<!-- For highlight.js !-->
<link rel="preload" type="text/css" href="/styles/obsidian.min.css" as="style" />
<link rel="stylesheet" type="text/css" href="/styles/obsidian.min.css" />
: my $obsidian_sri = sri_hash("/styles/obsidian.min.css");
<link rel="preload" type="text/css" href="/styles/obsidian.min.css" as="style"<: $obsidian_sri ? ' integrity="' ~ $obsidian_sri ~ '"' : '' | mark_raw :> />
<link rel="stylesheet" type="text/css" href="/styles/obsidian.min.css"<: $obsidian_sri ? ' integrity="' ~ $obsidian_sri ~ '"' : '' | mark_raw :> />
<!-- For reveal.js !-->
<link rel="preload" type="text/css" href="/styles/reveal.css" as="style" />
<link rel="stylesheet" type="text/css" href="/styles/reveal.css" />
<link rel="preload" type="text/css" href="/styles/reveal-white.css" as="style" />
<link rel="stylesheet" type="text/css" href="/styles/reveal-white.css" />
: my $reveal_sri = sri_hash("/styles/reveal.css");
<link rel="preload" type="text/css" href="/styles/reveal.css" as="style"<: $reveal_sri ? ' integrity="' ~ $reveal_sri ~ '"' : '' | mark_raw :> />
<link rel="stylesheet" type="text/css" href="/styles/reveal.css"<: $reveal_sri ? ' integrity="' ~ $reveal_sri ~ '"' : '' | mark_raw :> />
: my $reveal_white_sri = sri_hash("/styles/reveal-white.css");
<link rel="preload" type="text/css" href="/styles/reveal-white.css" as="style"<: $reveal_white_sri ? ' integrity="' ~ $reveal_white_sri ~ '"' : '' | mark_raw :> />
<link rel="stylesheet" type="text/css" href="/styles/reveal-white.css"<: $reveal_white_sri ? ' integrity="' ~ $reveal_white_sri ~ '"' : '' | mark_raw :> />

<!-- Javascript !-->
: for $scripts -> $script {
<script type="text/javascript" src="<: $script :>"></script>
: my $script_sri = sri_hash($script);
<script type="text/javascript" src="<: $script :>"<: $script_sri ? ' integrity="' ~ $script_sri ~ '"' : '' | mark_raw :>></script>
: }
</head>
<body>