]> www.fi.muni.cz Git - paste.git/blob - paste.pl
Download link
[paste.git] / paste.pl
1 #!/usr/bin/perl
2
3 use Mojolicious::Lite -signatures;
4 use Mojo::File qw(curfile);
5
6 plugin NotYAMLConfig => { file => 'config.yml',
7         default => {
8                 appname => 'Paste Bin',
9         } };
10
11 my $datadir = curfile->sibling('data');
12 if (app->config->{datadir}) {
13         $datadir = Mojo::File->new(app->config->{datadir});
14 }
15
16 chdir curfile->dirname;
17
18 get '/' => sub ($c) {
19         $c->render(template => 'forbidden', status => 403)
20                 if !length app->config->{password};
21 } => 'index';
22
23 post '/' => sub ($c) {
24         # print STDERR "pass=" . $c->param('password') . "\n";
25         return $c->render(template => 'forbidden', status => 403)
26                 if !defined app->config->{password}
27                         || !length $c->param('password')
28                         || $c->param('password') ne app->config->{password};
29
30         my $file_content = $c->param('text');
31         my $filename = $c->param('filename');
32         my $upload = $c->param('file');
33
34         if (defined $upload && $upload->size) {
35                 # print STDERR "FILENAME = " . $upload->filename . "\n";
36                 $filename = $upload->filename;
37                 $file_content = $upload->slurp;
38         }
39
40         if ($filename !~ /\A\w[\w-\.]*\.\w+\z/) {
41                 # print STDERR "FILENAME2 = " . $upload->filename . "\n";
42                 return $c->render(template => 'forbidden', status => 403);
43         }
44
45         $datadir->child($filename)->spurt($file_content);
46         $c->redirect_to($c->req->url->base . "$filename");
47 };
48
49 get '/<filename>.<ext>'
50         => [ filename => qr/\w[\w-\.]*/, ext => qr/\w+/ ]
51         => sub ($c) {
52         my $fullname = $c->param('filename').'.'.$c->param('ext');
53         my $file = $datadir->child($fullname);
54         my $stat = $file->stat;
55         
56         return $c->reply->not_found
57                 if !defined $stat;
58
59         if (defined $c->param('download')) {
60                 $c->res->headers->content_disposition(
61                         "attachment; filename=$fullname"
62                 );
63                 $c->reply->file($file);
64                 return;
65         }
66         $c->stash(mtime => POSIX::strftime('%Y-%m-%d %H:%M:%S',
67                 localtime($stat->mtime)));
68         my $content = $file->slurp;
69         $content = Encode::decode('utf-8', $content);
70         $c->stash(file_content => $content);
71         my $lang = $c->param('ext');
72
73         $c->stash(language => "language-$lang");
74         $c->render;
75 } => 'default';
76
77 app->mode(app->config->{mode});
78 app->start;
79
80 __DATA__
81 @@ index.html.ep
82 % layout 'default';
83 <h1><%= config->{appname} %></h1>
84 <form method="POST" enctype="multipart/form-data">
85 <label for="text">Enter some text or source code here:</label>
86 <textarea name="text" id="input_text">
87 </textarea>
88 <label for="file">Or select a file to upload:</label>
89 <input type="file" name="file"/>
90 <label for="filename">Name the file:</label>
91 <input type="text" name="filename"/>
92 <label for="password">Password:</label>
93 <input type="password" name="password" />
94 <input type="submit" value="Submit"/>
95 </form>
96
97
98 @@ default.html.ep
99 % layout 'default';
100
101 %= content_for header => begin
102     <base href="<%= config->{base} %>/">
103     <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/qtcreator-dark.min.css">
104     <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
105     <script>hljs.highlightAll();</script>
106 % end
107
108 <h1><tt><%= $filename%>.<%= $ext %></tt>
109    <span class="unimportant">— <%= config->{appname} %></span><br/>
110 <small class="unimportant">Created: <%= $mtime %>
111 <%= link_to "Download" => url_for->query(download => 1), class => 'downl' %>
112 </small></h1>
113 <pre><code class="<%= $language %>"><%= $file_content %></code></pre>
114
115
116 @@ forbidden.html.ep
117 % layout 'default';
118
119 <h1>Forbidden</h1>
120
121 @@ not_found.html.ep
122 % layout 'default';
123
124 <h1>Not found!</h1>
125
126 @@ layouts/default.html.ep
127 <html>
128   <head>
129     <meta charset="utf-8" />
130     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
131     <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
132     <meta name="viewport" content="width=device-width" />
133
134 <!--    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">-->
135     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
136     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css">
137     <link rel="icon" href="favicon.svg" />
138     <%= content 'header' %>
139     <style>
140       body {
141         background: #1c1f24;
142         color: #f2f2f2;
143       }
144       .wrapper {
145         max-width: 90rem;
146         margin: 0 auto;
147         margin-top: 1.5rem;
148       }
149       #input_text {
150         height: 40%;
151       }
152       .button, input[type="submit"] {
153         background-color: #0030c0;
154         border-color: #202080;
155       }
156       input {
157         color: #f2f2f2;
158       }
159       input[type="text"], input[type="password"] {
160         background-color: black;
161       }
162       textarea {
163         color: #f2f2f2;
164         background-color: black;
165         font-family: monospace;
166       }
167       h1 small {
168         font-size: 2.0rem;
169       }
170       .unimportant {
171         color: #999;
172       }
173       pre {
174         border: 0.1rem solid #d1d1d1;
175         border-radius: .4rem;
176       }
177       pre code {
178         padding: 1em;
179         margin-left: 0;
180         margin-right: 0;
181         background: black;
182         color: #aaa;
183       }
184       div.footer {
185         color: #999;
186         text-align: right;
187       } 
188       a {
189         color: #90c0ff;
190       }
191       a.downl {
192         display: inline-block;
193         float: right;
194       }
195     </style>
196     <title><%= config->{appname} %></title>
197   </head>
198   <body><div class="wrapper">
199     <%= content %>
200     <div class="footer">
201       <a href="https://www.fi.muni.cz/~kas/">Yenya</a>'s Paste Bin,
202       <a href="https://www.fi.muni.cz/~kas/git/paste.git/">www.fi.muni.cz/~kas/git/paste.git/</a>
203     </div>
204   </div></body>
205 </html>
206
207 @@ favicon.svg (base64)
208 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjwh
209 LS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoK
210 PHN2ZwogICB3aWR0aD0iMTkuOTc3NzIybW0iCiAgIGhlaWdodD0iMTkuOTc3NzIybW0iCiAgIHZp
211 ZXdCb3g9IjAgMCAxOS45Nzc3MjEgMTkuOTc3NzIxIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJz
212 dmc1IgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEgKGM2OGUyMmMzODcsIDIwMjEtMDUtMjMpIgog
213 ICBzb2RpcG9kaTpkb2NuYW1lPSJzaGViYW5nLWZhdmljb24uc3ZnIgogICB4bWxuczppbmtzY2Fw
214 ZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6
215 c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAu
216 ZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0i
217 aHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBp
218 ZD0ibmFtZWR2aWV3NyIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9
219 IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFk
220 b3c9IjIiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdl
221 Y2hlY2tlcmJvYXJkPSIwIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJtbSIKICAgICBz
222 aG93Z3JpZD0iZmFsc2UiCiAgICAgZml0LW1hcmdpbi10b3A9IjAiCiAgICAgZml0LW1hcmdpbi1s
223 ZWZ0PSIwIgogICAgIGZpdC1tYXJnaW4tcmlnaHQ9IjAiCiAgICAgZml0LW1hcmdpbi1ib3R0b209
224 IjAiCiAgICAgaW5rc2NhcGU6em9vbT0iMi42ODMyNDkyIgogICAgIGlua3NjYXBlOmN4PSIzMS4x
225 MTg5ODgiCiAgICAgaW5rc2NhcGU6Y3k9IjMwLjkzMjY0NyIKICAgICBpbmtzY2FwZTp3aW5kb3ct
226 d2lkdGg9IjE4MzUiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTA1MCIKICAgICBpbmtz
227 Y2FwZTp3aW5kb3cteD0iNjUiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2Nh
228 cGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXll
229 cjEiCiAgICAgaW5rc2NhcGU6c25hcC1wYWdlPSJ0cnVlIiAvPgogIDxkZWZzCiAgICAgaWQ9ImRl
230 ZnMyIiAvPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6
231 Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIgogICAgIHRyYW5zZm9ybT0idHJhbnNs
232 YXRlKC01NC41NjY5OTQsLTk3Ljk1MDE2NSkiPgogICAgPHJlY3QKICAgICAgIHN0eWxlPSJmaWxs
233 OiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjIuMDI5MDQ7
234 c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZz
235 ZXQ6MDtzdHJva2Utb3BhY2l0eToxIgogICAgICAgaWQ9InJlY3QxMjk0OSIKICAgICAgIHdpZHRo
236 PSIxOS45Nzc3MjIiCiAgICAgICBoZWlnaHQ9IjE5Ljk3NzcyMiIKICAgICAgIHg9IjU0LjU2Njk5
237 NCIKICAgICAgIHk9Ijk3Ljk1MDE2NSIKICAgICAgIHJ5PSIwLjUzNjg1NTI4IiAvPgogICAgPHRl
238 eHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zdHlsZTpu
239 b3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpib2xkO2ZvbnQtc3RyZXRjaDpu
240 b3JtYWw7Zm9udC1zaXplOjE3LjE3OTJweDtsaW5lLWhlaWdodDoxMjUlO2ZvbnQtZmFtaWx5OidE
241 ZWphVnUgU2FucyBNb25vJzstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidEZWphVnUgU2Fu
242 cyBNb25vIEJvbGQnO3RleHQtYWxpZ246c3RhcnQ7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3Bh
243 Y2luZzowcHg7dGV4dC1hbmNob3I6c3RhcnQ7ZmlsbDojMDBmZmZmO2ZpbGwtb3BhY2l0eToxO3N0
244 cm9rZTpub25lO3N0cm9rZS13aWR0aDowLjUzNjg0OXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ry
245 b2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4PSI1NS44NTM0MDki
246 CiAgICAgICB5PSIxMTQuMTE5OSIKICAgICAgIGlkPSJ0ZXh0NzgxIj48dHNwYW4KICAgICAgICAg
247 c29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNzc5IgogICAgICAgICBzdHls
248 ZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpib2xk
249 O2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1zaXplOjE3LjE3OTJweDtmb250LWZhbWlseTonRGVq
250 YVZ1IFNhbnMgTW9ubyc7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjonRGVqYVZ1IFNhbnMg
251 TW9ubyBCb2xkJztmaWxsOiMwMGZmZmY7c3Ryb2tlLXdpZHRoOjAuNTM2ODQ5cHgiCiAgICAgICAg
252 IHg9IjU1Ljg1MzQwOSIKICAgICAgICAgeT0iMTE0LjExOTkiPiMhPC90c3Bhbj48L3RleHQ+CiAg
253 PC9nPgo8L3N2Zz4K
254