zeroclickinfo-goodies/lib/DDG/GoodieRole/ImageLoader.pm

95 lines
3.0 KiB
Perl

package DDG::GoodieRole::ImageLoader;
# ABSTRACT: A role to allow Goodies to load images to pretty up their output.
use strict;
use warnings;
use Moo::Role;
use Devel::StackTrace;
use List::MoreUtils qw( all );
use MIME::Base64;
use MIME::Types;
use Package::Stash;
use Scalar::Util qw( reftype );
use Try::Tiny;
# Key and whether or not it is required
my %known_keys = (
filename => 1,
alt => 0,
height => 0,
width => 0,
class => 0,
);
my $mt = MIME::Types->new(
only_complete => 1,
only_iana => 1
);
# Returns an empty tag ('') in case of error.
my $cannot = '';
sub goodie_img_tag {
my $req_ref = shift;
# They must give us a hashref.
my $ref_val = reftype($req_ref) || '';
return $cannot unless ($ref_val eq 'HASH');
my %request = %$req_ref;
# And we must know what they all mean.
return $cannot unless (all { exists $known_keys{$_} } (keys %request));
# And all required ones must be present;
return $cannot unless (all { exists $request{$_} } grep { $known_keys{$_} } (keys %known_keys));
# We need to know the type of the file and that it's an image
my $filename = $request{filename};
my $type = $mt->mimeTypeOf($request{filename});
return $cannot unless ($type && (split '/', $type)[0] eq 'image');
# Now we need to hook into the role consumer's share dir, which we do in a ugly way here.
my $their_share = _grab_package_function('share');
return $cannot unless $their_share;
# Now we need to be sure that we can get at the file
my $file = $their_share->($filename);
return $cannot unless $file;
my $contents = scalar $file->slurp(iomode => '<:bytes');
# Reckon it's possible they tried to trick us with an empty file
return $cannot unless $contents;
my $b64_contents = encode_base64($contents, '');
return $cannot unless $b64_contents;
my $their_enc = _grab_package_function('html_enc');
return $cannot unless $their_enc;
my $goodie_tag = '<img src="data:' . $type . ';base64,' . $b64_contents . '"';
foreach my $img_attr (grep { defined $request{$_} } qw(alt class height width)) {
$goodie_tag .= ' ' . $img_attr . '="' . $their_enc->($request{$img_attr}) . '"';
}
$goodie_tag .= '/>';
return $goodie_tag;
}
sub _grab_package_function {
my $function_name = shift;
my $func = try {
my $hit = 0;
# We only care about the most recent caller who is some kinda goodie-looking thing.
my $frame_filter = sub {
my $frame_info = shift;
if (!$hit && $frame_info->{caller}[0] =~ /^DDG::Goodie::/) { $hit++; return 1; }
else { return 0; }
};
my $trace = Devel::StackTrace->new(
frame_filter => $frame_filter,
no_args => 1,
);
my $stash = Package::Stash->new($trace->frame(0)->package); # Get the package info for our caller.
$stash->get_symbol('&' . $function_name);
};
return $func;
}
1;