mirror of
https://framagit.org/fiat-tux/hat-softwares/lutim.git
synced 2026-03-28 17:42:54 +01:00
362 lines
10 KiB
Perl
362 lines
10 KiB
Perl
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
|
|
package Lutim::Plugin::Helpers;
|
|
use Mojo::Base 'Mojolicious::Plugin';
|
|
use Mojo::Util qw(quote);
|
|
use Mojo::File;
|
|
use Crypt::CBC;
|
|
use Data::Entropy qw(entropy_source);
|
|
use DateTime;
|
|
use Mojo::Util qw(decode);
|
|
use ISO::639_1;
|
|
use Digest::MD5 'md5';
|
|
|
|
sub register {
|
|
my ($self, $app) = @_;
|
|
|
|
|
|
if ($app->config('dbtype') eq 'postgresql') {
|
|
require Mojo::Pg;
|
|
$app->plugin('PgURLHelper');
|
|
$app->helper(pg => \&_pg);
|
|
|
|
# Database migration
|
|
my $migrations = Mojo::Pg::Migrations->new(pg => $app->pg);
|
|
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
|
|
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(0)->migrate(3);
|
|
} else {
|
|
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(3);
|
|
}
|
|
} elsif ($app->config('dbtype') eq 'sqlite') {
|
|
# SQLite database migration if needed
|
|
require Mojo::SQLite;
|
|
$app->helper(sqlite => \&_sqlite);
|
|
|
|
my $sql = Mojo::SQLite->new('sqlite:'.$app->config('db_path'));
|
|
my $migrations = $sql->migrations;
|
|
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
|
|
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(0)->migrate(2);
|
|
} else {
|
|
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(2);
|
|
}
|
|
}
|
|
|
|
$app->helper(render_file => \&_render_file);
|
|
$app->helper(ip => \&_ip);
|
|
$app->helper(provisioning => \&_provisioning);
|
|
$app->helper(shortener => \&_shortener);
|
|
$app->helper(stop_upload => \&_stop_upload);
|
|
$app->helper(max_delay => \&_max_delay);
|
|
$app->helper(default_delay => \&_default_delay);
|
|
$app->helper(is_selected => \&_is_selected);
|
|
$app->helper(is_wm_selected => \&_is_wm_selected);
|
|
$app->helper(crypt => \&_crypt);
|
|
$app->helper(decrypt => \&_decrypt);
|
|
$app->helper(delete_image => \&_delete_image);
|
|
$app->helper(iso639_native_name => \&_iso639_native_name);
|
|
$app->helper(prefix => \&_prefix);
|
|
}
|
|
|
|
sub _pg {
|
|
my $c = shift;
|
|
|
|
state $pg = Mojo::Pg->new($c->app->pg_url($c->app->config('pgdb')));
|
|
return $pg;
|
|
}
|
|
|
|
sub _sqlite {
|
|
my $c = shift;
|
|
|
|
state $sqlite = Mojo::SQLite->new('sqlite:'.$c->app->config('db_path'));
|
|
return $sqlite;
|
|
}
|
|
|
|
sub _render_file {
|
|
my $c = shift;
|
|
my ($im_loaded, $img, $dl, $key, $thumb) = @_;
|
|
|
|
my ($filename, $path, $iv, $mediatype, $no_cache) = ($img->filename, $img->path, $img->iv, $img->mediatype, $img->delete_at_first_view);
|
|
|
|
my $expires = ($img->delete_at_day) ? $img->delete_at_day : 360;
|
|
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $img->created_at);
|
|
$dt->set_time_zone('GMT');
|
|
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
|
|
|
|
$dl = 'attachment' if ($mediatype =~ m/svg/);
|
|
$filename = quote($filename);
|
|
|
|
unless (-f $path && -r $path) {
|
|
$c->app->log->error("Cannot read file [$path]. error [$!]");
|
|
$c->flash(
|
|
msg => $c->l('Unable to find the image: it has been deleted.')
|
|
);
|
|
return 500;
|
|
}
|
|
|
|
$mediatype =~ s/x-//;
|
|
|
|
my $headers = Mojo::Headers->new();
|
|
if ($no_cache || defined($thumb)) {
|
|
$headers->add('Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate');
|
|
} else {
|
|
$headers->add('Expires' => $expires);
|
|
}
|
|
$headers->add('Content-Type' => $mediatype.';name='.$filename);
|
|
$headers->add('Content-Disposition' => $dl.';filename='.$filename);
|
|
$c->res->content->headers($headers);
|
|
|
|
my $cache;
|
|
if ($c->config('cache_max_size') != 0 || scalar(@{$c->config('memcached_servers')})) {
|
|
$cache = $c->chi('lutim_images_cache')->compute($img->short, undef, sub {
|
|
if ($key) {
|
|
return {
|
|
asset => $c->decrypt($key, $path, $iv),
|
|
key => $key
|
|
};
|
|
} else {
|
|
return {
|
|
asset => Mojo::File->new($path)->slurp,
|
|
};
|
|
}
|
|
});
|
|
if ($key && $key ne $cache->{key}) {
|
|
my $tmp = $c->decrypt($key, $path, $iv);
|
|
$cache->{asset} = $tmp;
|
|
$c->chi('lutim_images_cache')->replace(
|
|
$img->short,
|
|
{
|
|
asset => $tmp,
|
|
key => $key
|
|
},
|
|
);
|
|
}
|
|
} else {
|
|
if ($key) {
|
|
$cache = {
|
|
asset => $c->decrypt($key, $path, $iv),
|
|
};
|
|
} else {
|
|
$cache = {
|
|
asset => Mojo::File->new($path)->slurp,
|
|
};
|
|
}
|
|
}
|
|
# Extend expiration time
|
|
my $asset = Mojo::Asset::Memory->new;
|
|
$asset->add_chunk($cache->{asset});
|
|
|
|
if (defined $thumb && $im_loaded && $mediatype ne 'image/svg+xml' && $mediatype !~ m#image/(x-)?xcf# && $mediatype ne 'image/avif') { # ImageMagick don't work in Debian with svg (for now?)
|
|
my $im = Image::Magick->new;
|
|
$im->BlobToImage($asset->slurp);
|
|
|
|
# Create the thumbnail
|
|
if ($thumb eq '') {
|
|
$im->Resize(geometry => 'x'.$c->config('thumbnail_size'));
|
|
} else {
|
|
$im->Resize(geometry => $thumb);
|
|
}
|
|
|
|
# Replace the asset with the thumbnail
|
|
$asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
|
|
}
|
|
|
|
$c->res->content->asset($asset);
|
|
$headers->add('Content-Length' => $asset->size);
|
|
|
|
return $c->rendered(200);
|
|
}
|
|
|
|
sub _ip {
|
|
my $c = shift;
|
|
my $ip_only = shift || 0;
|
|
|
|
my $proxy = $c->req->headers->header('X-Forwarded-For');
|
|
|
|
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
|
|
|
|
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
|
|
|
|
return ($ip_only) ? $ip : "$ip remote port:$remote_port";
|
|
}
|
|
|
|
sub _provisioning {
|
|
my $c = shift;
|
|
|
|
# Create some short patterns for provisioning
|
|
my $img = Lutim::DB::Image->new(app => $c->app);
|
|
if ($img->count_empty < $c->app->config('provisioning')) {
|
|
for (my $i = 0; $i < $c->app->config('provis_step'); $i++) {
|
|
my $short;
|
|
do {
|
|
$short = $c->shortener($c->app->config('length'));
|
|
} while ($img->count_short($short) || $short eq 'about' || $short eq 'stats' || $short eq 'd' || $short eq 'm' || $short eq 'gallery' || $short eq 'zip' || $short eq 'infos');
|
|
|
|
$img->short($short)
|
|
->counter(0)
|
|
->enabled(1)
|
|
->delete_at_first_view(0)
|
|
->delete_at_day(0)
|
|
->mod_token($c->shortener($c->app->config('token_length')))
|
|
->write('provisioning');
|
|
|
|
$img = Lutim::DB::Image->new(app => $c->app);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _shortener {
|
|
my $c = shift;
|
|
my $length = shift;
|
|
|
|
my @chars = ('a'..'z','A'..'Z','0'..'9');
|
|
my $result = '';
|
|
foreach (1..$length) {
|
|
$result .= $chars[entropy_source->get_int(scalar(@chars))];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
sub _stop_upload {
|
|
my $c = shift;
|
|
|
|
if (-f 'stop-upload' || -f 'stop-upload.manual') {
|
|
$c->stash(
|
|
stop_upload => $c->l('Uploading is currently disabled, please try later or contact the administrator (%1).', $c->app->config('contact'))
|
|
);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub _max_delay {
|
|
my $c = shift;
|
|
|
|
return $c->app->config('max_delay') if ($c->app->config('max_delay') >= 0);
|
|
|
|
warn "max_delay set to a negative value. Default to 0.";
|
|
return 0;
|
|
}
|
|
|
|
sub _default_delay {
|
|
my $c = shift;
|
|
|
|
return $c->app->config('default_delay') if ($c->app->config('default_delay') >= 0);
|
|
|
|
warn "default_delay set to a negative value. Default to 0.";
|
|
return 0;
|
|
}
|
|
|
|
sub _is_selected {
|
|
my $c = shift;
|
|
my $num = shift;
|
|
|
|
return ($num == $c->default_delay) ? 'selected="selected"' : '';
|
|
}
|
|
|
|
sub _is_wm_selected {
|
|
my $c = shift;
|
|
my $wm = shift;
|
|
|
|
return ($wm eq $c->config('watermark_default')) ? 'selected="selected"' : '';
|
|
}
|
|
|
|
sub _key_from_key {
|
|
my $key = shift;
|
|
|
|
# Key size for Blowfish is 56
|
|
my $ks = 56;
|
|
my $material = md5($key);
|
|
while (length($material) < $ks) {
|
|
$material .= md5($material);
|
|
}
|
|
return substr($material,0,$ks);
|
|
}
|
|
|
|
sub _crypt {
|
|
my $c = shift;
|
|
my $upload = shift;
|
|
my $filename = shift;
|
|
|
|
my $key = $c->shortener($c->config('crypto_key_length'));
|
|
my $iv = $c->shortener(8);
|
|
|
|
my $cipher = Crypt::CBC->new(
|
|
-key => _key_from_key($key),
|
|
-cipher => 'Blowfish',
|
|
-header => 'none',
|
|
-literal_key => 1,
|
|
-pbkdf => 'pbkdf2',
|
|
-iv => $iv
|
|
);
|
|
|
|
$cipher->start('encrypting');
|
|
|
|
my $crypt_asset = Mojo::Asset::File->new;
|
|
|
|
$crypt_asset->add_chunk($cipher->crypt($upload->slurp));
|
|
$crypt_asset->add_chunk($cipher->finish);
|
|
|
|
my $crypt_upload = Mojo::Upload->new;
|
|
$crypt_upload->filename($filename);
|
|
$crypt_upload->asset($crypt_asset);
|
|
|
|
return ($crypt_upload, $key, $iv);
|
|
}
|
|
|
|
sub _decrypt {
|
|
my $c = shift;
|
|
my $key = _key_from_key(shift);
|
|
my $file = shift;
|
|
my $iv = shift;
|
|
$iv = 'dupajasi' unless $iv;
|
|
|
|
my $cipher = Crypt::CBC->new(
|
|
-key => $key,
|
|
-cipher => 'Blowfish',
|
|
-header => 'none',
|
|
-literal_key => 1,
|
|
-pbkdf => 'pbkdf2',
|
|
-iv => $iv
|
|
);
|
|
|
|
$cipher->start('decrypting');
|
|
|
|
my $decrypt_asset = Mojo::Asset::File->new;
|
|
|
|
open(my $f, "<",$file) or die "Unable to read encrypted file: $!";
|
|
binmode $f;
|
|
while (read($f, my $buffer, 1024)) {
|
|
$decrypt_asset->add_chunk($cipher->crypt($buffer));
|
|
}
|
|
$decrypt_asset->add_chunk($cipher->finish) ;
|
|
|
|
return $decrypt_asset->slurp;
|
|
}
|
|
|
|
sub _delete_image {
|
|
my $c = shift;
|
|
my $img = shift;
|
|
if ($c->config('cache_max_size') != 0 || scalar(@{$c->config('memcached_servers')})) {
|
|
$c->chi('lutim_images_cache')->remove($img->short);
|
|
}
|
|
unlink $img->path or warn "Could not unlink ".$img->path.": $!";
|
|
$img->disable();
|
|
}
|
|
|
|
sub _iso639_native_name {
|
|
my $c = shift;
|
|
return ucfirst(decode 'UTF-8', get_iso639_1(shift)->{nativeName});
|
|
}
|
|
|
|
sub _prefix {
|
|
my $c = shift;
|
|
|
|
my $prefix = $c->url_for('/')->to_abs;
|
|
# Forced domain
|
|
$prefix->host($c->config('fixed_domain')) if (defined($c->config('fixed_domain')) && $c->config('fixed_domain') ne '');
|
|
# Hack for prefix (subdir) handling
|
|
$prefix .= '/' unless ($prefix =~ m#/$#);
|
|
return $prefix;
|
|
}
|
|
|
|
1;
|