Commit | Line | Data |
---|---|---|
7dc32473 MG |
1 | package Plack::App::Gruntmaster; |
2 | ||
3 | use 5.014000; | |
4 | use strict; | |
5 | use warnings; | |
6 | use parent qw/Plack::Component/; | |
7 | no if $] >= 5.017011, warnings => 'experimental::smartmatch'; | |
8 | our $VERSION = '5999.000_001'; | |
9 | ||
e03e380b MG |
10 | use Apache2::Authen::Passphrase qw/pwcheck pwset/; |
11 | use CSS::Minifier::XS; | |
f34254b8 | 12 | use File::Slurp qw/read_file/; |
7dc32473 | 13 | use HTTP::Negotiate qw/choose/; |
e03e380b | 14 | use JavaScript::Minifier::XS; |
7dc32473 | 15 | use Plack::Request; |
fdbf59e5 | 16 | use Gruntmaster::Page::Generic; |
7dc32473 MG |
17 | |
18 | my %handlers; | |
19 | ||
20 | sub call { | |
21 | my $env = $_[1]; | |
22 | my $r = Plack::Request->new($env); | |
23 | my @handlers = @{ $handlers{$r->method} // [] }; | |
24 | for my $handler (@handlers) { | |
25 | my ($re, $obj) = @$handler; | |
26 | my @args; | |
27 | next unless @args = $r->path =~ m/^$re$/a; | |
e03e380b | 28 | return $obj->($env, map { $_ // '' } @args); |
7dc32473 MG |
29 | } |
30 | ||
f34254b8 MG |
31 | if ($r->method eq 'GET' || $r->method eq 'HEAD') { |
32 | my $article = $r->path eq '/' ? '/index' : $r->path; | |
33 | $article = substr $article, 1; | |
34 | $article =~ tr,/,_,; | |
35 | my @variants = grep { !/\.title$/ } <a/$article.*>; | |
36 | if (@variants) { | |
37 | my $lang = choose [ map { [$_, 1, 'text/html', undef, undef, $_, undef] } map { /\.(.+)$/ } @variants ], $r->headers; | |
38 | my $content = read_file "a/$article.$lang"; | |
39 | my $title = read_file "a/$article.$lang.title"; | |
40 | my $html = Gruntmaster::Page::Base::header($lang, $title) . $content . Gruntmaster::Page::Base::footer($lang); | |
21d35251 | 41 | return [200, ['Content-Type' => 'text/html', 'Content-Language' => $lang, 'Vary' => 'Accept-Language', 'X-Forever' => 1, 'Cache-Control' => 'max-age=300'], [$html] ] |
f34254b8 MG |
42 | } |
43 | } | |
44 | ||
7dc32473 MG |
45 | [404, ['Content-Type' => 'text/plain'], ['Not found']] |
46 | } | |
47 | ||
e03e380b MG |
48 | sub get { push @{$handlers{GET }}, [ @_ ] } |
49 | sub post { push @{$handlers{POST}}, [ @_ ] } | |
50 | sub db { $_[0]->{'gruntmaster.dbic'} } | |
51 | sub reply { [200, ['Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache'], [ @_ ] ] } | |
52 | sub admin { 0 } | |
7dc32473 | 53 | |
e03e380b MG |
54 | sub old_handler { |
55 | my ($pkg) = @_; | |
56 | $pkg = "Gruntmaster::Page::$pkg"; | |
57 | eval "require $pkg" or die $@; | |
58 | sub { | |
59 | my ($env, @args) = @_; | |
60 | my $format = choose $pkg->variants, Plack::Request->new($env)->headers; | |
61 | $pkg->generate($format, $env, @args) | |
62 | } | |
7dc32473 MG |
63 | } |
64 | ||
e03e380b MG |
65 | use constant CONTENT_TYPES => +{ |
66 | c => 'text/x-csrc', | |
67 | cpp => 'text/x-c++src', | |
68 | cs => 'text/x-csharp', # Used by GNOME. Not in mime.types. | |
69 | java => 'text/x-java', | |
70 | pas => 'text/x-pascal', | |
71 | pl => 'text/x-perl', | |
72 | py => 'text/x-python', | |
73 | }; | |
74 | use constant NOT_FOUND => [404, ['Content-Type' => 'text/plain'], ['Not found']]; | |
75 | use constant FORBIDDEN => [403, ['Content-Type' => 'text/plain'], ['Forbidden']]; | |
76 | ||
7dc32473 | 77 | BEGIN{ |
491e82eb MG |
78 | my $word = qr,(\w+),a; |
79 | my $number = qr,(\d+),a; | |
7dc32473 | 80 | |
e03e380b MG |
81 | get qr,/css/$word\.css, => sub { |
82 | my ($env, $theme) = @_; | |
83 | return [404, ['Content-Type' => 'text/plain'], [ 'Not found' ]] unless -e "css/themes/$theme.css"; | |
84 | my $css = read_file "css/themes/$theme.css"; | |
85 | $css .= read_file $_ for <css/*.css>; | |
86 | [200, ['Content-Type' => 'text/css', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [CSS::Minifier::XS::minify $css] ] | |
87 | }; | |
88 | ||
89 | get qr,/js\.js, => sub { | |
90 | my $js; | |
91 | $js .= read_file $_ for <js/*.js>; | |
92 | [200, ['Content-Type' => 'application/javascript', 'Cache-Control' => 'public, max-age=604800', 'X-Forever' => 1], [JavaScript::Minifier::XS::minify $js] ] | |
93 | }; | |
94 | ||
95 | get qr,/log/src/$number\.$word, => sub { | |
96 | my ($env, $job, $ext) = @_; | |
97 | $job = db($env)->job($job); | |
98 | return NOT_FOUND if $job->contest; | |
99 | return FORBIDDEN if !admin && ($job->private || $job->problem->private); | |
100 | [200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ] | |
101 | }; | |
102 | ||
103 | get qr,/ct/$word/log/src/$number\.$word, => sub { | |
104 | my ($env, $ct, $job, $ext) = @_; | |
105 | $job = db($env)->job($job); | |
106 | $ct = db($env)->contest($ct); | |
107 | return NOT_FOUND if $job->contest->id ne $ct->id; | |
108 | return FORBIDDEN if !admin && ($job->private || !$ct->is_finished); | |
109 | [200, ['Content-Type' => CONTENT_TYPES->{$ext}, 'Cache-Control' => 'max-age=604800', 'X-Forever' => 1], [$job->source] ] | |
110 | }; | |
111 | ||
112 | get qr,/ct/$word/log/st, => sub { | |
113 | my($env, $ct) = @_; | |
114 | $env->{'gruntmaster.contest'} = $ct; | |
115 | old_handler('St')->($env); | |
116 | }; | |
117 | ||
118 | get qr,/us/, => old_handler 'Us'; | |
119 | get qr,/us/$word, => old_handler 'Us::Entry'; | |
120 | ||
121 | get qr,/pb/, => old_handler 'Pb'; | |
122 | ||
123 | get qr,/us/$word/pb/, => sub { | |
124 | my ($env, $us) = @_; | |
125 | $env->{'gruntmaster.user'} = $us; | |
126 | old_handler('Pb')->($env); | |
127 | }; | |
128 | ||
129 | get qr,/ct/$word/pb/, => sub { | |
130 | my ($env, $ct) = @_; | |
131 | return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending; | |
132 | $env->{'gruntmaster.contest'} = $ct; | |
133 | old_handler('Pb')->($env); | |
134 | }; | |
135 | ||
136 | get qr,/pb/$word, => sub { | |
137 | my ($env, $pb) = @_; | |
138 | return NOT_FOUND if !db($env)->problem($pb)->is_in_archive; | |
139 | return FORBIDDEN if !admin && db($env)->problem($pb)->private; | |
140 | old_handler('Pb::Entry')->(@_); | |
141 | }; | |
142 | ||
143 | get qr,/ct/$word/pb/$word, => sub{ | |
144 | my ($env, $ct, $pb) = @_; | |
145 | return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb); | |
146 | return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending; | |
147 | $env->{'gruntmaster.contest'} = $ct; | |
148 | old_handler('Pb::Entry')->($env, $pb); | |
149 | }; | |
150 | ||
151 | post qr,/pb/$word/submit, => sub { | |
152 | my ($env, $pb) = @_; | |
153 | return NOT_FOUND if !db($env)->problem($pb)->is_in_archive; | |
154 | old_handler('Submit')->($env, $pb); | |
155 | }; | |
156 | ||
157 | post qr,/ct/$word/pb/$word/submit, => sub { | |
158 | my ($env, $ct, $pb) = @_; | |
159 | return NOT_FOUND if !db($env)->contest_problems->find($ct, $pb); | |
160 | return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending; | |
161 | $env->{'gruntmaster.contest'} = $ct; | |
162 | old_handler('Submit')->($env, $pb); | |
163 | }; | |
164 | ||
165 | get qr,/log/(?:page-$number)?, => sub { | |
166 | my ($env, $page) = @_; | |
167 | $env->{'gruntmaster.page'} = $page || -1; | |
168 | old_handler('Log')->($env) | |
169 | }; | |
170 | ||
171 | get qr,/ct/$word/log/(?:page-$number)?, => sub { | |
172 | my ($env, $ct, $page) = @_; | |
173 | return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending; | |
174 | $env->{'gruntmaster.contest'} = $ct; | |
175 | $env->{'gruntmaster.page'} = $page || -1; | |
176 | old_handler('Log')->($env) | |
177 | }; | |
178 | ||
179 | get qr,/pb/$word/log/(?:page-$number)?, => sub { | |
180 | my ($env, $pb, $page) = @_; | |
181 | #return FORBIDDEN if !admin && db($pb)-> TODO | |
182 | $env->{'gruntmaster.problem'} = $pb; | |
183 | $env->{'gruntmaster.page'} = $page || -1; | |
184 | old_handler('Log')->($env) | |
185 | }; | |
186 | ||
187 | get qr,/us/$word/log/(?:page-$number)?, => sub { | |
188 | my ($env, $us, $page) = @_; | |
189 | $env->{'gruntmaster.user'} = $us; | |
190 | $env->{'gruntmaster.page'} = $page || -1; | |
191 | old_handler('Log')->($env); | |
192 | }; | |
193 | ||
194 | get qr,/log/$number, => sub{ | |
195 | my ($env, $job) = @_; | |
196 | my $j = db($env)->job($job); | |
197 | return FORBIDDEN if !admin && ($j->private || $j->problem->private || ($j->contest && !$j->contest->is_finished)); | |
198 | old_handler('Log::Entry')->($env, $job); | |
199 | }; | |
200 | ||
201 | get qr,/ct/, => old_handler 'Ct'; | |
202 | ||
203 | get qr,/ct/$word, => sub { | |
204 | my ($env, $ct) = @_; | |
205 | return FORBIDDEN if !admin && db($env)->contest($ct)->is_pending; | |
206 | old_handler('Ct::Entry')->($env, $ct); | |
207 | }; | |
208 | ||
209 | post qr,/action/passwd, => sub { | |
210 | my ($env) = @_; | |
211 | my $r = Plack::Request->new($env); | |
212 | my ($oldpass, $newpass, $confirm) = map {scalar $r->param($_)} 'password', 'new_password', 'confirm_new_password'; | |
81cce380 | 213 | |
e03e380b MG |
214 | return reply 'Incorrect password' unless eval { pwcheck $r->user, $oldpass; 1 }; |
215 | return reply 'The two passwords do not match' unless $newpass eq $confirm; | |
7dc32473 | 216 | |
e03e380b MG |
217 | pwset $r->user, $newpass; |
218 | reply 'Password changed successfully'; | |
219 | }; | |
7dc32473 | 220 | |
e03e380b | 221 | post qr,/action/register, => old_handler 'Register'; |
31d70015 | 222 | |
7dc32473 MG |
223 | } |
224 | ||
225 | 1; | |
226 | __END__ |