You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

190 lines
4.8 KiB

package LDV::Zerobin;
use strict;
use warnings;
use utf8;
use POSIX qw(strftime tmpnam);
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Asset::File;
use Mojo::Util qw(b64_encode b64_decode decode encode);
sub _metadata_path {
my ($self, $time) = @_;
my $root = $self->app->config->{zerobin}->{root};
return $self->app->home->rel_file("$root/$time.json");
}
sub _metadata_save {
my ($self, $time, $data) = @_;
my $path = $self->_metadata_path($time);
my $asset = Mojo::Asset::File->new;
$asset->add_chunk($self->app->json->encode($data));
$asset->move_to($path);
return 1;
}
sub _metadata_load {
my ($self, $time) = @_;
my $path = $self->_metadata_path($time);
die("paste metadata not found\n") unless (-f $path);
my $asset = Mojo::Asset::File->new(path => $path);
$asset->cleanup(0);
return $self->app->json->decode($asset->slurp);
}
sub _content_path {
my ($self, $time) = @_;
return $self->app->home->rel_file("public/zerobin/$time.txt");
}
sub _content_save {
my ($self, $time, $content) = @_;
unless (ref $content) {
my $asset = Mojo::Asset::File->new;
utf8::encode($content);
$asset->add_chunk($content);
$content = $asset; # wrap plaintext to Mojo::Asset
}
my $path = $self->_content_path($time);
$content->move_to($path);
return 1;
}
sub _content_load {
my ($self, $time) = @_;
my $path = $self->_content_path($time);
die("paste content not found\n") unless (-f $path);
my $asset = Mojo::Asset::File->new(path => $path);
$asset->cleanup(0);
my $content = $asset->slurp;
utf8::decode($content);
return $content;
}
sub create {
my ($self) = @_;
my @syntax = ($self->c(text => ['plaintext', 'markdown']));
my $langs = $self->app->config->{zerobin}->{syntax};
push @syntax, $self->c(code => $langs)
if (ref($langs) eq 'ARRAY');
$self->stash({syntax => \@syntax});
$self->render;
}
sub view {
my ($self) = @_;
my $time = $self->stash('time');
eval {
die("paste not found\n") unless $time;
my $paste = $self->_metadata_load($time);
$paste->{data} = $self->_content_load($time);
$paste->{time} = $time;
utf8::decode($paste->{data});
$self->stash({paste => $paste});
1;
} or do {
chomp $@;
$self->app->log->error($@);
$@ = "internal error" if ($@ =~ m|at \S+ line \d+|oi);
$self->flash({'result' => $@});
$self->redirect_to("/zerobin");
return;
};
$self->render;
}
sub save {
my ($self) = @_;
eval {
my $source = $self->req->param('source') || '-';
my $expire = $self->req->param('expire') || 30; # 30 days or 1 month
my $syntax = $self->req->param('syntax') || '';
my $paste;
if ($source eq 'form') {
$paste = $self->req->param('paste') || '';
die("empty paste\n") unless $paste;
} elsif ($source eq 'file') {
require File::MimeInfo::Magic;
my $tmpfile = tmpnam();
my $maxsize = $self->app->config->{zerobin}->{maxsize};
my $upload = $self->req->upload('file');
die("empty uploaded file\n")
if $upload->size == 0;
die("uploaded file too large\n")
if $upload->size > $maxsize;
$upload->move_to($tmpfile);
$upload->asset->cleanup(1)
if $upload->asset->SUPER::can('cleanup');
my $mime = File::MimeInfo::Magic::mimetype($tmpfile);
die("uploaded file not looks like text\n")
unless $mime =~ m{^text/};
$paste = $upload->asset;
unlink $tmpfile;
} else {
die("unknown 'source'\n");
}
$expire = 30 unless ($expire =~ m|^\d+$|o);
$syntax = 'auto' unless ($syntax =~ m|^[a-z0-9]+$|oi);
my $time = time();
$self->_metadata_save($time, {
expire => $time + ($expire * 86400),
syntax => $syntax,
});
$self->_content_save($time, $paste);
$self->redirect_to("/zerobin/$time"); 1;
} or do {
chomp $@;
$self->app->log->error($@);
$@ = "internal error" if ($@ =~ m|at \S+ line \d+|oi);
$self->flash({'result' => $@});
$self->redirect_to("/zerobin");
};
$self->rendered;
}
sub prune {
my ($self) = @_;
eval {
my $currtime = time();
my $files = $self->app->home->list_files("data/zerobin");
foreach my $file (@{ $files }) {
next unless $file =~ m/^(\d+)\.json$/oi;
my $time = $1;
my $data = $self->_metadata_load($time);
next if $data->{expire} > $currtime; # not yet
my $date = strftime("%Y-%m-%d %H:%M", localtime($data->{expire}));
$self->app->log->info("Removing expired paste: $file ($date)");
unlink $self->_metadata_path($time);
unlink $self->_content_path($time);
} 1;
} or do {
chomp $@;
$self->app->log->error($@);
$@ = "internal error" if ($@ =~ m|at \S+ line \d+|oi);
$self->stash({result => $@});
};
$self->redirect_to("/zerobin");
$self->rendered;
}
1;