#!/usr/bin/perl use warnings; use strict; use Debbugs::Config qw(:config); use Debbugs::CGI qw(cgi_parameters); use Debbugs::Common; use Digest::MD5 qw(md5_hex); use Gravatar::URL; use File::LibMagic; use File::Temp qw(tempfile); use Libravatar::URL; use LWP::UserAgent; use HTTP::Request; use CGI::Simple; my $q = CGI::Simple->new(); my %param = cgi_parameters(query => $q, single => [qw(email avatar default)], default => {avatar => 'yes', default => $config{libravatar_uri_options}, }, ); # if avatar is no, serve the empty png if ($param{avatar} ne 'yes' or not defined $param{email} or not length $param{email}) { serve_cache('',$q); exit 0; } # figure out what the md5sum of the e-mail is. my $email_md5sum = md5_hex(lc($param{email})); my $cache_location = cache_location($email_md5sum); # if we've got it, and it's less than one hour old, return it. if (cache_valid($cache_location)) { serve_cache($cache_location,$q); exit 0; } # if we don't have it, get it, and store it in the cache $cache_location = retreive_libravatar(location => $cache_location, email => lc($param{email}), ); if (not defined $cache_location) { # failure, serve the default image serve_cache('',$q); exit 0; } else { serve_cache($cache_location,$q); exit 0; } sub cache_valid{ my ($cache_location) = @_; if (-e $cache_location) { if (time - (stat($cache_location))[9] < 60*60) { return 1; } } return 0; } sub retreive_libravatar{ my %type_mapping = (jpeg => 'jpg', png => 'png', gif => 'png', tiff => 'png', tif => 'png', pjpeg => 'jpg', jpg => 'jpg' ); my %param = @_; my $cache_location = $param{location}; $cache_location =~ s/\.[^\.]+$//; my $uri = libravatar_url(email => $param{email}, default => 404, size => 80); my $ua = LWP::UserAgent->new(agent => 'Debbugs libravatar service (not Mozilla)', ); $ua->from($config{maintainer}); # if we don't get an avatar within 10 seconds, return so we don't # block forever $ua->timeout(10); # if the avatar is bigger than 30K, we don't want it either $ua->max_size(30*1024); my $r = $ua->get($uri); if (not $r->is_success()) { return undef; } my $aborted = $r->header('Client-Aborted'); # if we exceeded max size, I'm not sure if we'll be successfull or # not, but regardless, there will be a Client-Aborted header. Stop # here if that header is defined. return undef if defined $aborted; my $type = $r->header('Content-Type'); # if there's no content type, or it's not one we like, we won't # bother going further return undef if not defined $type; return undef if not $type =~ m{^image/([^/]+)$}; my $dest_type = $type_mapping{$1}; return undef if not defined $dest_type; # undo any content encoding $r->decode() or return undef; # ok, now we need to convert it from whatever it is into a format # that we actually like my ($temp_fh,$temp_fn) = tempfile() or return undef; eval { print {$temp_fh} $r->content() or die "Unable to print to temp file"; close ($temp_fh); system('convert','-resize','80x80', '-strip', $temp_fn, $cache_location.'.'.$dest_type) == 0 or die "convert file failed"; unlink($temp_fh); }; if ($@) { unlink($cache_location.'.'.$dest_type) if -e $cache_location.'.'.$dest_type; unlink($temp_fn) if -e $temp_fn; return undef; } return $cache_location.'.'.$dest_type; } sub cache_location { my ($md5sum) = @_; for my $ext (qw(.png .jpg)) { if (-e $config{libravatar_cache_dir}.'/'.$md5sum.$ext) { return $config{libravatar_cache_dir}.'/'.$md5sum.$ext; } } return $config{libravatar_cache_dir}.'/'.$md5sum; } sub serve_cache { my ($cache_location,$q) = @_; if (not defined $cache_location or not length $cache_location) { # serve the default image $cache_location = $config{libravatar_default_image}; } my $fh = IO::File->new($cache_location,'r') or error(404, "Failed to open cached image $cache_location"); my $m = File::LibMagic->new() or error(500,'Unable to create File::LibMagic object'); my $mime_string = $m->checktype_filename($cache_location) or error(500,'Bad file; no mime known'); print $q->header(-type => $mime_string, -expires => '+1d', ); print STDOUT <$fh>; close($fh); } sub error { my ($error,$text) = @_; $text //= ''; print $q->header(-status => $error); print "

$error: $text

"; exit 0; }