]> www.fi.muni.cz Git - paste.git/blob - paste.pl
Initial revision
[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         $c->stash(mtime => POSIX::strftime('%Y-%m-%d %H:%M:%S',
60                 localtime($stat->mtime)));
61         $c->stash(file_content => $file->slurp);
62         my $lang = $c->param('ext');
63
64         $c->stash(language => "language-$lang");
65         $c->render;
66 } => 'default';
67
68 app->start;
69
70 __DATA__
71 @@ index.html.ep
72 % layout 'default';
73 <h1><%= config->{appname} %></h1>
74 <form method="POST" enctype="multipart/form-data">
75 <label for="text">Enter some text or source code here:</label>
76 <textarea name="text" id="input_text">
77 </textarea>
78 <label for="file">Or select a file to upload:</label>
79 <input type="file" name="file"/>
80 <label for="filename">Name the file:</label>
81 <input type="text" name="filename"/>
82 <label for="password">Password:</label>
83 <input type="password" name="password" />
84 <input type="submit" value="Submit"/>
85 </form>
86
87
88 @@ default.html.ep
89 % layout 'default';
90
91 %= content_for header => begin
92     <base href="<%= config->{base} %>/">
93     <link rel="stylesheet" href="highlight/qtcreator-dark.min.css">
94     <script src="highlight/highlight.min.js"></script>
95     <script>hljs.highlightAll();</script>
96 % end
97
98 <h1><tt><%= $filename%>.<%= $ext %></tt>
99    <span class="unimportant">— <%= config->{appname} %></span><br/>
100 <small class="unimportant">Created: <%= $mtime %></small></h1>
101 <pre><code class="<%= $language %>"><%= $file_content %></code></pre>
102
103
104 @@ forbidden.html.ep
105 % layout 'default';
106
107 <h1>Forbidden</h1>
108
109
110 @@ layouts/default.html.ep
111 <html>
112   <head>
113     <meta charset="utf-8" />
114     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
115     <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
116     <meta name="viewport" content="width=device-width" />
117
118     <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
119     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
120     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css">
121     <link rel="icon" href="img/shebang-favicon.svg" />
122     <%= content 'header' %>
123     <style>
124       body {
125         background: #1c1f24;
126         color: #f2f2f2;
127       }
128       .wrapper {
129         max-width: 90rem;
130         margin: 0 auto;
131         margin-top: 1.5rem;
132       }
133       #input_text {
134         height: 40%;
135       }
136       .button, input[type="submit"] {
137         background-color: #0030c0;
138         border-color: #202080;
139       }
140       input {
141         color: #f2f2f2;
142       }
143       textarea {
144         color: #f2f2f2;
145         font-family: monospace;
146       }
147       h1 small {
148         font-size: 2.0rem;
149       }
150       .unimportant {
151         color: #999;
152       }
153       pre {
154         border: 0;
155       }
156       pre code {
157         padding: 0;
158         margin-left: 0;
159         margin-right: 0;
160       }
161       div.footer {
162         color: #999;
163         text-align: right;
164       } 
165       a {
166         color: #90c0ff;
167       }
168     </style>
169     <title><%= config->{appname} %></title>
170   </head>
171   <body><div class="wrapper">
172     <%= content %>
173     <div class="footer">
174       Created by <b>Yenya's Paste Bin</b>,
175         <a href="https://www.fi.muni.cz/~kas/git/paste/">www.fi.muni.cz/~kas/git/paste</a>
176     </div>
177   </div></body>
178 </html>