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;