mirror of
https://framagit.org/fiat-tux/hat-softwares/lutim.git
synced 2026-03-28 17:42:54 +01:00
@@ -58,7 +58,8 @@ vi lutim.conf
|
||||
* broadcast\_message: put some string (not HTML) here and this message will be displayed on all LUTIm pages (not in JSON responses) ;
|
||||
* allowed\_domains: array of authorized domains for API calls. Example: `['http://1.example.com', 'http://2.example.com']`. If you want to authorize everyone to use the API: `['*']`.
|
||||
* default\_delay: what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365;
|
||||
* max\_delay: if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max\_delay) option and a warning message will be displayed on homepage.
|
||||
* max\_delay: if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max\_delay) option and a warning message will be displayed on homepage;
|
||||
# always\_encrypt: if set to 1, all images will be encrypted.
|
||||
|
||||
##Usage
|
||||
```
|
||||
@@ -142,6 +143,11 @@ carton exec hypnotoad script/lutim
|
||||
|
||||
It may take a few reloads of page before the message is displayed.
|
||||
|
||||
##Encryption
|
||||
LUTIm do encryption on the server if asked to, but does not store the key.
|
||||
|
||||
The encryption is made on the server since LUTIm is made to be usable even without javascript. If you want to add client-side encryption for javascript-enabled browsers, patches are welcome.
|
||||
|
||||
##API
|
||||
You can add images by using the API. Here's the parameters of the `POST` request to `/` adress:.
|
||||
* format: json
|
||||
|
||||
1
cpanfile
1
cpanfile
@@ -8,3 +8,4 @@ requires 'DateTime';
|
||||
requires 'Filesys::DiskUsage';
|
||||
requires 'Switch';
|
||||
requires 'Data::Validate::URI';
|
||||
requires 'Crypt::CBC';
|
||||
|
||||
@@ -26,6 +26,13 @@ DISTRIBUTIONS
|
||||
Class::Singleton 1.4
|
||||
requirements:
|
||||
ExtUtils::MakeMaker 0
|
||||
Crypt-CBC-2.33
|
||||
pathname: L/LD/LDS/Crypt-CBC-2.33.tar.gz
|
||||
provides:
|
||||
Crypt::CBC 2.33
|
||||
requirements:
|
||||
Digest::MD5 2.00
|
||||
ExtUtils::MakeMaker 0
|
||||
DBD-SQLite-1.40
|
||||
pathname: I/IS/ISHIGAKI/DBD-SQLite-1.40.tar.gz
|
||||
provides:
|
||||
|
||||
88
lib/Lutim.pm
88
lib/Lutim.pm
@@ -2,6 +2,7 @@ package Lutim;
|
||||
use Mojo::Base 'Mojolicious';
|
||||
use Mojo::Util qw(quote);
|
||||
use LutimModel;
|
||||
use Crypt::CBC;
|
||||
|
||||
$ENV{MOJO_TMPDIR} = 'tmp';
|
||||
mkdir($ENV{MOJO_TMPDIR}, 0700) unless (-d $ENV{MOJO_TMPDIR});
|
||||
@@ -14,10 +15,11 @@ sub startup {
|
||||
my $config = $self->plugin('Config');
|
||||
|
||||
# Default values
|
||||
$config->{provisioning} = 100 unless (defined($config->{provisionning}));
|
||||
$config->{provisioning} = 100 unless (defined($config->{provisioning}));
|
||||
$config->{provis_step} = 5 unless (defined($config->{provis_step}));
|
||||
$config->{length} = 8 unless (defined($config->{length}));
|
||||
$config->{provisioning} = 100 unless (defined($config->{provisionning}));
|
||||
$config->{provisioning} = 100 unless (defined($config->{provisioning}));
|
||||
$config->{provis_step} = 5 unless (defined($config->{provis_step}));
|
||||
$config->{length} = 8 unless (defined($config->{length}));
|
||||
$config->{always_encrypt} = 0 unless (defined($config->{always_encrypt}));
|
||||
|
||||
die "You need to provide a contact information in lutim.conf !" unless (defined($config->{contact}));
|
||||
|
||||
@@ -28,7 +30,7 @@ sub startup {
|
||||
$self->helper(
|
||||
render_file => sub {
|
||||
my $c = shift;
|
||||
my ($filename, $path, $mediatype, $dl, $expires, $nocache) = @_;
|
||||
my ($filename, $path, $mediatype, $dl, $expires, $nocache, $key) = @_;
|
||||
|
||||
$filename = quote($filename);
|
||||
|
||||
@@ -43,18 +45,25 @@ sub startup {
|
||||
|
||||
$mediatype =~ s/x-//;
|
||||
|
||||
$asset = Mojo::Asset::File->new(path => $path);
|
||||
my $headers = Mojo::Headers->new();
|
||||
if ($nocache) {
|
||||
$headers->add('Cache-Control' => 'no-cache');
|
||||
$headers->add('Cache-Control' => 'no-cache');
|
||||
} else {
|
||||
$headers->add('Expires' => $expires);
|
||||
$headers->add('Expires' => $expires);
|
||||
}
|
||||
$headers->add('Content-Type' => $mediatype.';name='.$filename);
|
||||
$headers->add('Content-Disposition' => $dl.';filename='.$filename);
|
||||
$headers->add('Content-Length' => $asset->size);
|
||||
$c->res->content->headers($headers);
|
||||
|
||||
$c->app->log->debug($key);
|
||||
if ($key) {
|
||||
$asset = $c->decrypt($key, $path);
|
||||
} else {
|
||||
$asset = Mojo::Asset::File->new(path => $path);
|
||||
}
|
||||
$c->res->content->asset($asset);
|
||||
$headers->add('Content-Length' => $asset->size);
|
||||
|
||||
return $c->rendered(200);
|
||||
}
|
||||
);
|
||||
@@ -169,6 +178,64 @@ sub startup {
|
||||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
crypt => sub {
|
||||
my $c = shift;
|
||||
my $upload = shift;
|
||||
my $filename = shift;
|
||||
|
||||
my $key = $c->shortener(8);
|
||||
|
||||
my $cipher = Crypt::CBC->new(
|
||||
-key => $key,
|
||||
-cipher => 'Blowfish',
|
||||
-header => 'none',
|
||||
-iv => 'dupajasi'
|
||||
);
|
||||
|
||||
$cipher->start('encrypting');
|
||||
|
||||
my $crypt_asset = Mojo::Asset::File->new;
|
||||
|
||||
$crypt_asset->add_chunk($cipher->crypt($upload->slurp));
|
||||
$crypt_asset->add_chunk($cipher->finish);
|
||||
|
||||
my $crypt_upload = Mojo::Upload->new;
|
||||
$crypt_upload->filename($filename);
|
||||
$crypt_upload->asset($crypt_asset);
|
||||
|
||||
return ($crypt_upload, $key);
|
||||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
decrypt => sub {
|
||||
my $c = shift;
|
||||
my $key = shift;
|
||||
my $file = shift;
|
||||
|
||||
my $cipher = Crypt::CBC->new(
|
||||
-key => $key,
|
||||
-cipher => 'Blowfish',
|
||||
-header => 'none',
|
||||
-iv => 'dupajasi'
|
||||
);
|
||||
|
||||
$cipher->start('decrypting');
|
||||
|
||||
my $decrypt_asset = Mojo::Asset::File->new;
|
||||
|
||||
open(my $f, "<",$file) or die "Unable to read encrypted file: $!";
|
||||
binmode $f;
|
||||
while (read($f, my $buffer,1024)) {
|
||||
$decrypt_asset->add_chunk($cipher->crypt($buffer));
|
||||
}
|
||||
$decrypt_asset->add_chunk($cipher->finish) ;
|
||||
|
||||
return $decrypt_asset;
|
||||
}
|
||||
);
|
||||
|
||||
$self->hook(
|
||||
before_dispatch => sub {
|
||||
my $c = shift;
|
||||
@@ -227,6 +294,9 @@ sub startup {
|
||||
$r->get('/:short')->
|
||||
to('Controller#short')->
|
||||
name('short');
|
||||
|
||||
$r->get('/:short/:key')->
|
||||
to('Controller#short');
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -126,6 +126,10 @@ sub add {
|
||||
my $filename = unidecode($upload->filename);
|
||||
my $ext = ($filename =~ m/([^.]+)$/)[0];
|
||||
my $path = 'files/'.$records[0]->short.'.'.$ext;
|
||||
my $key;
|
||||
if ($c->param('crypt') || $c->config->{always_encrypt}) {
|
||||
($upload, $key) = $c->crypt($upload, $filename);
|
||||
}
|
||||
$upload->move_to($path);
|
||||
$records[0]->update(
|
||||
path => $path,
|
||||
@@ -143,7 +147,8 @@ sub add {
|
||||
$c->app->log->info('[CREATION] '.$c->ip.' pushed '.$filename.' (path: '.$path.')');
|
||||
|
||||
# Give url to user
|
||||
$short = $records[0]->short;
|
||||
$short = $records[0]->short;
|
||||
$short .= '/'.$key if (defined($key));
|
||||
} else {
|
||||
# Houston, we have a problem
|
||||
$msg = $c->l('no_more_short', $c->config->{contact});
|
||||
@@ -201,6 +206,7 @@ sub short {
|
||||
my $c = shift;
|
||||
my $short = $c->param('short');
|
||||
my $touit = $c->param('t');
|
||||
my $key = $c->param('key');
|
||||
my $dl = (defined($c->param('dl'))) ? 'attachment' : 'inline';
|
||||
|
||||
my @images = LutimModel::Lutim->select('WHERE short = ? AND ENABLED = 1 AND path IS NOT NULL', $short);
|
||||
@@ -224,10 +230,12 @@ sub short {
|
||||
my $test;
|
||||
if (defined($touit)) {
|
||||
$test = 1;
|
||||
my $short = $images[0]->short;
|
||||
$short .= '/'.$key if (defined($key));
|
||||
$c->render(
|
||||
template => 'twitter',
|
||||
layout => undef,
|
||||
short => $images[0]->short,
|
||||
short => $short,
|
||||
filename => $images[0]->filename
|
||||
);
|
||||
} else {
|
||||
@@ -235,7 +243,8 @@ sub short {
|
||||
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
|
||||
$dt->set_time_zone('GMT');
|
||||
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
|
||||
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view);
|
||||
|
||||
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key);
|
||||
}
|
||||
|
||||
if ($test != 500) {
|
||||
|
||||
@@ -71,6 +71,8 @@ our %Lexicon = (
|
||||
'delay_30' => '30 days',
|
||||
'delay_365' => '1 year',
|
||||
'max_delay' => 'Warning! The maximum time limit for an image is [_1] day(s), even if you choose "no time limit".',
|
||||
'crypt_image' => 'Encrypt the image (LUTIm does not keep the key).',
|
||||
'always_encrypt' => 'The images are encrypted on the server (LUTIm does not keep the key).',
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
@@ -71,6 +71,8 @@ our %Lexicon = (
|
||||
'delay_30' => '30 jours',
|
||||
'delay_365' => '1 an',
|
||||
'max_delay' => 'Attention ! Le délai maximal de rétention d\'une image est de [_1] jour(s), même si vous choisissez « pas de limitation de durée ».',
|
||||
'crypt_image' => 'Chiffrer l\'image (LUTIm ne stocke pas la clé).',
|
||||
'always_encrypt' => 'Les images sont chiffrées sur le serveur (LUTIm ne stocke pas la clé).',
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
#allowed_domains => ['http://1.example.com', 'http://2.example.com'], #optional, array of authorized domains for API calls. If you want to authorize everyone to use the API: ['*']
|
||||
#default_delay => 0, #optional: what is the default time limit for files? Valid values are 0, 1, 7, 30 and 365.
|
||||
#max_delay => 0, #optional, if defined, the images will be deleted after that delay (in days), even if they were uploaded with "no delay" (or value superior to max\_delay) option and a warning message will be displayed on homepage.
|
||||
#always_encrypt => 0, #optional, if set to 1, all the images will be encrypted
|
||||
};
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
//});
|
||||
fd.append('format', 'json');
|
||||
fd.append('first-view', ($("#first-view").prop('checked')) ? 1 : 0);
|
||||
fd.append('crypt', ($("#crypt").prop('checked')) ? 1 : 0);
|
||||
fd.append('delete-day', ($("#delete-day").val()));
|
||||
|
||||
widget.settings.onBeforeUpload.call(widget.element, widget.queuePos);
|
||||
|
||||
1
public/js/dmuploader.min.js
vendored
1
public/js/dmuploader.min.js
vendored
@@ -11,4 +11,5 @@
|
||||
f.append('format', 'json');
|
||||
f.append('first-view', ($("#first-view").prop('checked')) ? 1 : 0);
|
||||
f.append('delete-day', ($("#delete-day").val()));
|
||||
f.append('crypt', ($("#crypt").prop('checked')) ? 1 : 0);
|
||||
h.settings.onBeforeUpload.call(h.element,h.queuePos);h.queueRunning=true;c.ajax({url:h.settings.url,type:h.settings.method,dataType:h.settings.dataType,data:f,cache:false,contentType:false,processData:false,forceSync:false,xhr:function(){var i=c.ajaxSettings.xhr();if(i.upload){i.upload.addEventListener("progress",function(m){var l=0;var j=m.loaded||m.position;var k=m.total||e.totalSize;if(m.lengthComputable){l=Math.ceil(j/k*100)}h.settings.onUploadProgress.call(h.element,h.queuePos,l)},false)}return i},success:function(j,i,k){h.settings.onUploadSuccess.call(h.element,h.queuePos,j)},error:function(k,i,j){h.settings.onUploadError.call(h.element,h.queuePos,j)},complete:function(i,j){h.processQueue()}})};c.fn.dmUploader=function(f){return this.each(function(){if(!c.data(this,b)){c.data(this,b,new a(this,f))}})};c(document).on("dragenter",function(f){f.stopPropagation();f.preventDefault()});c(document).on("dragover",function(f){f.stopPropagation();f.preventDefault()});c(document).on("drop",function(f){f.stopPropagation();f.preventDefault()})})(jQuery);
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<strong><%=l('max_delay', max_delay) %></strong>
|
||||
</div>
|
||||
% }
|
||||
% if ($self->config->{always_encrypt}) {
|
||||
<p><%=l 'always_encrypt' %></p>
|
||||
% }
|
||||
% if (defined(flash('short'))) {
|
||||
<div class="alert alert-success">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
@@ -35,8 +38,12 @@
|
||||
% }
|
||||
</select>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
|
||||
<input type="checkbox" name="crypt_image"> <%=l 'crypt_image' %>
|
||||
<label>
|
||||
<input type="checkbox" name="first-view"> <%=l 'delete-first' %>
|
||||
</label>
|
||||
<label class="always-encrypt">
|
||||
<input type="checkbox" name="crypt"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -63,7 +70,9 @@
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="first-view"> <%=l 'delete-first' %>
|
||||
<input type="checkbox" name="crypt_image"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
<label class="always-encrypt">
|
||||
<input type="checkbox" id="crypt"> <%=l 'crypt_image' %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,6 +168,7 @@
|
||||
'lutim-file-url' : val,
|
||||
'format' : 'json',
|
||||
'first-view' : ($("#first-view").prop('checked')) ? 1 : 0,
|
||||
'crypt' : ($("#crypt").prop('checked')) ? 1 : 0,
|
||||
'delete-day' : $("#delete-day").val()
|
||||
},
|
||||
success: function(data) {
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
ul.no-bullet {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
% if ($self->config->{always_encrypt}) {
|
||||
label.always-encrypt {
|
||||
display: none;
|
||||
}
|
||||
% }
|
||||
% end
|
||||
%= javascript 'js/jquery-2.1.0.min.js'
|
||||
%= javascript 'js/bootstrap.min.js'
|
||||
|
||||
Reference in New Issue
Block a user