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;