|
|
|
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 _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) = @_;
|
|
|
|
|
|
|
|
$self->access_allowed($self->app->config->{zerobin}->{access})
|
|
|
|
or return;
|
|
|
|
|
|
|
|
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') || 0;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
my $paste = $self->app->db->select('zerobin', '*', {created => $time})->hash;
|
|
|
|
die("paste not found\n") unless $paste;
|
|
|
|
$paste->{data} = $self->_content_load($paste->{created});
|
|
|
|
$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) = @_;
|
|
|
|
|
|
|
|
$self->access_allowed($self->app->config->{zerobin}->{access})
|
|
|
|
or return;
|
|
|
|
|
|
|
|
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->app->db->insert('zerobin', {
|
|
|
|
created => $time, syntax => $syntax,
|
|
|
|
expire => $time + ($expire * 86400),
|
|
|
|
});
|
|
|
|
$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 @expired = $self->app->db->select('zerobin', '*', {expire => {'<=' => time()}})->hashes;
|
|
|
|
foreach my $paste (@expired) {
|
|
|
|
my $path = $self->_content_path($paste->{created});
|
|
|
|
$self->app->log->info("Removing expired paste: $path");
|
|
|
|
unlink $path if -f $path;
|
|
|
|
$self->app->db->delete('zerobin', {id => $paste->{id}});
|
|
|
|
}
|
|
|
|
my $msg = sprintf '%d records pruned', scalar @expired;
|
|
|
|
$self->app->log->info($msg); 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;
|