use strict;
our $VERSION = '5999.000_001';
-use CSS::Minifier::XS;
use Encode qw/encode decode/;
use File::Slurp qw/read_file/;
-use JavaScript::Minifier::XS;
use JSON::MaybeXS qw/encode_json/;
use PerlX::Maybe;
use Scope::Upper qw/unwind SUB UP/;
use Gruntmaster::Data;
use Plack::App::Gruntmaster::HTML;
+use Email::Sender::Simple qw/sendmail/;
+use Email::Simple;
+
use warnings NONFATAL => 'all';
no warnings 'illegalproto';
-no if $] >= 5.017011, warnings => 'experimental::smartmatch';
##################################################
pas => 'text/x-pascal',
pl => 'text/x-perl',
py => 'text/x-python',
+ l => 'text/plain',
};
use constant FORMAT_EXTENSION => {
PASCAL => 'pas',
PERL => 'pl',
PYTHON => 'py',
+ SBCL => 'l',
};
-use constant NOT_FOUND => [404, ['Content-Type' => 'text/plain'], ['Not found']];
-use constant FORBIDDEN => [401, ['Content-Type' => 'text/plain', 'WWW-Authenticate' => 'Basic realm="Gruntmaster 6000"'], ['Forbidden']];
-
-sub development() { ($ENV{PLACK_ENV} // 'development') eq 'development' }
+use constant NOT_FOUND => [404, ['X-Forever' => 1, 'Content-Type' => 'text/plain'], ['Not found']];
my ($env, $privacy);
sub db { $env->{'gruntmaster.dbic'} }
sub remote_user {
- my $user = $env->{'gruntmaster.user'};
+ my $user = $env->{REMOTE_USER};
$user &&= db->user($user);
$user
}
sub user { db->user ($_{user}) }
sub redirect { [301, ['X-Forever' => 1, 'Location' => $_[0]], []] }
-sub reply { [200, ['Content-Type' => 'text/plain'], \@_] }
+sub reply { [200, ['Content-Type' => 'text/plain; charset=utf-8'], \@_] }
sub response {
my ($template, $title, $params, $maxage) = @_;
unless ($params) {
my ($condition) = @_;
$privacy = 'private' if $condition;
return if !$condition || admin;
- unwind FORBIDDEN, SUB UP
+ unwind $env->{authcomplex}->unauthorized, SUB UP
}
sub dispatch_request{
$privacy = 'public';
sub (GET) {
- sub (/css/:theme) {
- my $theme = $_{theme};
- return NOT_FOUND unless -e "css/themes/$theme.css";
- my $css = read_file "css/themes/$theme.css";
- $css .= read_file $_ for <css/*.css>;
- my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'text/css');
- [200, \@headers, [development ? $css : CSS::Minifier::XS::minify $css]]
- },
-
- sub (/js.js) {
- my $js;
- $js .= read_file $_ for <js/*.js>;
- my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'application/javascript');
- [200, \@headers, [development ? $js : JavaScript::Minifier::XS::minify $js]]
- },
+ sub (/robots.txt) { NOT_FOUND },
sub (/src/:job) {
return NOT_FOUND if !job;
- forbid job->private || job->problem->private || job->contest && job->contest->is_running;
- my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => CONTENT_TYPES->{job->format});
+ my $isowner = remote_user && remote_user->id eq job->rawowner;
+ my $private = job->private || job->problem->private || job->contest && job->contest->is_running;
+ forbid !$isowner && $private;
+ my $privacy = $private ? 'private' : 'public';
+ my @headers = ('X-Forever' => 1, 'Cache-Control' => "$privacy, max-age=604800", 'Content-Type' => CONTENT_TYPES->{job->extension});
+ push @headers, (Vary => 'Authorization') if $private;
[200, \@headers, [job->source]]
},
my ($r) = @_;
return $r if ref $r ne 'Plack::App::Gruntmaster::Response';
my @hdrs = ('X-Forever' => 1, 'Cache-Control' => "$privacy, max-age=$r->{maxage}");
- return [200, ['Content-Type' => 'application/json', @hdrs], [encode_json $r->{params}]] if $format eq 'json';
+ push @hdrs, Vary => 'Authorization' if $privacy eq 'private';
+ return [200, ['Content-Type' => 'application/json; charset=utf-8', @hdrs], [encode_json $r->{params}]] if $format eq 'json';
my $ret = render $r->{template}, 'en', title => $r->{title}, %{$r->{params}};
- [200, ['Content-Type' => 'text/html', @hdrs], [encode 'UTF-8', $ret]]
+ [200, ['Content-Type' => 'text/html; charset=utf-8', @hdrs], [encode 'UTF-8', $ret]]
},
},
},
sub (/ed/:contest) {
- forbid contest->is_running;
- response ed => 'Editorial of ' . contest->name, db->problem_list(contest => $_{contest}, solution => 1);
+ forbid !contest->is_finished;
+ my $pblist = db->problem_list(contest => $_{contest}, solution => 1);
+ response ed => 'Editorial of ' . contest->name, {%$pblist, editorial => contest->editorial};
},
sub (/login) {
sub (/) { redispatch_to '/index' },
sub (/favicon.ico) { redirect '/static/favicon.ico' },
- sub (/:article) { [200, ['Content-Type' => 'text/html', 'Cache-Control' => 'public, max-age=60', 'X-Forever' => 1], [render_article $_{article}, 'en']] }
+ sub (/:article) { [200, ['Content-Type' => 'text/html; charset=utf-8', 'Cache-Control' => 'public, max-age=60', 'X-Forever' => 1], [render_article $_{article}, 'en']] }
},
sub (POST) {
- sub (/action/register + %:username=&:password=&:confirm_password=&:name=&:email=&:phone=&:town=&:university=&:country=&:level=) {
- return reply 'Parameter too long' if grep { length > 200 } values %_;
- return reply 'Bad username. Allowed characters are letters, digits and underscores, and the username must be between 2 and 20 characters long.' unless $_{username} =~ USER_REGEX;
- return reply 'Username already in use' if db->user($_{username});
- return reply 'The two passwords do not match' unless $_{password} eq $_{confirm_password};
-
- db->users->create({id => $_{username}, name => $_{name}, email => $_{email}, phone => $_{phone}, town => $_{town}, university => $_{university}, country => $_{country}, level => $_{level}});
- db->user($_{username})->set_passphrase($_{password});
-
- purge '/us/';
- reply 'Registered successfully';
- },
-
- sub (/action/passwd + %:password=&:new_password=&:confirm_new_password=) {
- forbid !remote_user;
- return reply 'Incorrect password' unless remote_user->check_passphrase($_{password});
- return reply 'The two passwords do not match' unless $_{new_password} eq $_{confirm_new_password};
- remote_user->set_passphrase($_{new_password});
- reply 'Password changed successfully';
- },
-
sub (/action/submit + %:problem=&:contest~&:prog_format=&:source_code~ + *prog~) {
my (undef, undef, $prog) = @_;
forbid !remote_user;
my $source = $prog ? read_file $prog->path : $_{source_code};
unlink $prog->path if $prog;
+ my $private = (problem->private && !$_{contest}) ? 1 : 0;
+ $private = 1 if contest && contest->is_pending;
my $newjob = db->jobs->create({
maybe contest => $_{contest},
- maybe private => problem->private && !$_{contest},
+ private => $private,
date => time,
extension => FORMAT_EXTENSION->{$_{prog_format}},
format => $_{prog_format},
owner => remote_user->id,
});
- purge '/log/';
[303, [Location => '/log/' . $newjob->id], []]
- }
+ },
}
}