181 lines
5.4 KiB
Perl
181 lines
5.4 KiB
Perl
#!/usr/bin/perl
|
|
#
|
|
# This code was forked from the LiveJournal project owned and operated
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
|
# currently be found at:
|
|
#
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
|
#
|
|
# In accordance with the original license, this code and all its
|
|
# modifications are provided under the GNU General Public License.
|
|
# A copy of that license can be found in the LICENSE file included as
|
|
# part of this distribution.
|
|
|
|
package Apache::LiveJournal::PalImg;
|
|
|
|
use strict;
|
|
use Apache2::Const qw(:common REDIRECT HTTP_NOT_MODIFIED);
|
|
use PaletteModify;
|
|
|
|
# for callers to 'ping' as a class method for Class::Autouse to lazily load
|
|
sub load { 1 }
|
|
|
|
# URLs of form /palimg/somedir/file.gif[extra]
|
|
# where extras can be:
|
|
# /p... - palette modify
|
|
|
|
sub handler {
|
|
my $apache_r = shift;
|
|
my $uri = $apache_r->uri;
|
|
|
|
my ( $base, $ext, $extra ) = $uri =~ m!^/palimg/(.+)\.(\w+)(.*)$!;
|
|
return 404 unless $base && $base !~ m!\.\.!;
|
|
|
|
my $disk_file = "$LJ::HTDOCS/palimg/$base.$ext";
|
|
return 404 unless -e $disk_file;
|
|
|
|
my @st = stat(_);
|
|
my $size = $st[7];
|
|
my $modtime = $st[9];
|
|
my $etag = "$modtime-$size";
|
|
|
|
my $mime = {
|
|
'gif' => 'image/gif',
|
|
'png' => 'image/png',
|
|
}->{$ext};
|
|
|
|
my $palspec;
|
|
if ($extra) {
|
|
if ( $extra =~ m!^/p(.+)$! ) {
|
|
$palspec = $1;
|
|
}
|
|
else {
|
|
return 404;
|
|
}
|
|
}
|
|
|
|
return send_file(
|
|
$apache_r,
|
|
$disk_file,
|
|
{
|
|
'mime' => $mime,
|
|
'etag' => $etag,
|
|
'palspec' => $palspec,
|
|
'size' => $size,
|
|
'modtime' => $modtime,
|
|
}
|
|
);
|
|
}
|
|
|
|
sub parse_hex_color {
|
|
my $color = shift;
|
|
return [ map { hex( substr( $color, $_, 2 ) ) } ( 0, 2, 4 ) ];
|
|
}
|
|
|
|
sub send_file {
|
|
my ( $apache_r, $disk_file, $opts ) = @_;
|
|
|
|
my $etag = $opts->{'etag'};
|
|
|
|
# palette altering
|
|
my %pal_colors;
|
|
if ( my $pals = $opts->{'palspec'} ) {
|
|
my $hx = "[0-9a-f]";
|
|
if ( $pals =~ /^g($hx{2,2})($hx{6,6})($hx{2,2})($hx{6,6})$/ ) {
|
|
|
|
# gradient from index $1, color $2, to index $3, color $4
|
|
my $from = hex($1);
|
|
my $to = hex($3);
|
|
return 404 if $from == $to;
|
|
my $fcolor = parse_hex_color($2);
|
|
my $tcolor = parse_hex_color($4);
|
|
if ( $to < $from ) {
|
|
( $from, $to, $fcolor, $tcolor ) = ( $to, $from, $tcolor, $fcolor );
|
|
}
|
|
$etag .= ":pg$pals";
|
|
for ( my $i = $from ; $i <= $to ; $i++ ) {
|
|
$pal_colors{$i} = [
|
|
map {
|
|
int( $fcolor->[$_] +
|
|
( $tcolor->[$_] - $fcolor->[$_] ) *
|
|
( $i - $from ) /
|
|
( $to - $from ) )
|
|
} ( 0 .. 2 )
|
|
];
|
|
}
|
|
}
|
|
elsif ( $pals =~ /^t($hx{6,6})($hx{6,6})?$/ ) {
|
|
|
|
# tint everything towards color
|
|
my ( $t, $td ) = ( $1, $2 );
|
|
$pal_colors{'tint'} = parse_hex_color($t);
|
|
$pal_colors{'tint_dark'} = $td ? parse_hex_color($td) : [ 0, 0, 0 ];
|
|
}
|
|
elsif ( length($pals) > 42 || $pals =~ /[^0-9a-f]/ ) {
|
|
return 404;
|
|
}
|
|
else {
|
|
my $len = length($pals);
|
|
return 404 if $len % 7; # must be multiple of 7 chars
|
|
for ( my $i = 0 ; $i < $len / 7 ; $i++ ) {
|
|
my $palindex = hex( substr( $pals, $i * 7, 1 ) );
|
|
$pal_colors{$palindex} = [
|
|
hex( substr( $pals, $i * 7 + 1, 2 ) ),
|
|
hex( substr( $pals, $i * 7 + 3, 2 ) ),
|
|
hex( substr( $pals, $i * 7 + 5, 2 ) ),
|
|
substr( $pals, $i * 7 + 1, 6 ),
|
|
];
|
|
}
|
|
$etag .= ":p$_($pal_colors{$_}->[3])" for ( sort keys %pal_colors );
|
|
}
|
|
}
|
|
|
|
$etag = '"' . $etag . '"';
|
|
my $ifnonematch = $apache_r->headers_in->{'If-None-Match'};
|
|
return HTTP_NOT_MODIFIED
|
|
if defined $ifnonematch && $etag eq $ifnonematch;
|
|
|
|
# send the file
|
|
$apache_r->content_type( $opts->{'mime'} );
|
|
$apache_r->headers_out->{'Content-length'} = $opts->{'size'};
|
|
$apache_r->headers_out->{ETag} = $etag;
|
|
if ( $opts->{'modtime'} ) {
|
|
$apache_r->update_mtime( $opts->{'modtime'} );
|
|
$apache_r->set_last_modified();
|
|
}
|
|
|
|
# HEAD request?
|
|
return OK if $apache_r->method eq "HEAD";
|
|
|
|
open my ($fh), $disk_file;
|
|
return 404 unless $fh;
|
|
|
|
binmode($fh);
|
|
|
|
my $palette;
|
|
if (%pal_colors) {
|
|
if ( $opts->{'mime'} eq "image/gif" ) {
|
|
$palette = PaletteModify::new_gif_palette( $fh, \%pal_colors );
|
|
}
|
|
elsif ( $opts->{'mime'} == "image/png" ) {
|
|
$palette = PaletteModify::new_png_palette( $fh, \%pal_colors );
|
|
}
|
|
unless ($palette) {
|
|
return 404; # image isn't palette changeable?
|
|
}
|
|
}
|
|
|
|
$apache_r->print($palette) if $palette; # when palette modified.
|
|
|
|
# now read the rest of the file, but let's max on a 1MB image for sanity
|
|
read $fh, my $buf, 1024 * 1024;
|
|
$apache_r->print($buf) if $buf;
|
|
|
|
$fh->close();
|
|
return OK;
|
|
}
|
|
|
|
1;
|
|
|