Repository: ldidry/lufi
Branch: master
Commit: 8f97fcc2f497
Files: 142
Total size: 2.6 MB
Directory structure:
gitextract_s0gul96a/
├── .gitignore
├── .gitlab-ci.yml
├── .provision/
│ ├── README.md
│ ├── ansible-role-lufi/
│ │ ├── README.md
│ │ ├── defaults/
│ │ │ └── main.yml
│ │ ├── files/
│ │ │ ├── cronjob
│ │ │ └── robots.txt
│ │ ├── handlers/
│ │ │ └── main.yml
│ │ ├── tasks/
│ │ │ ├── apprun.yml
│ │ │ ├── cron.yml
│ │ │ ├── dependencies.yml
│ │ │ ├── gitclone.yml
│ │ │ └── main.yml
│ │ ├── templates/
│ │ │ ├── lufi.conf.j2
│ │ │ └── update.sh
│ │ └── vars/
│ │ └── main.yml
│ └── terraform-aws-lufi/
│ ├── README.md
│ ├── lufi_startup.sh
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── vars.tf
├── AUTHORS.md
├── CHANGELOG
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── cpanfile
├── cpanfile.snapshot
├── lib/
│ ├── Date/
│ │ └── Language/
│ │ └── Occitan.pm
│ ├── Lufi/
│ │ ├── Command/
│ │ │ ├── copyFilesToSwift.pm
│ │ │ ├── cron/
│ │ │ │ ├── cleanbdd.pm
│ │ │ │ ├── cleanfiles.pm
│ │ │ │ └── watch.pm
│ │ │ ├── cron.pm
│ │ │ ├── sqliteToOtherDB.pm
│ │ │ └── theme.pm
│ │ ├── Controller/
│ │ │ ├── Auth.pm
│ │ │ ├── Files.pm
│ │ │ ├── Invitation.pm
│ │ │ ├── Mail.pm
│ │ │ └── Misc.pm
│ │ ├── DB/
│ │ │ ├── File/
│ │ │ │ ├── Mysql.pm
│ │ │ │ ├── Pg.pm
│ │ │ │ └── SQLite.pm
│ │ │ ├── File.pm
│ │ │ ├── Invitation/
│ │ │ │ ├── Mysql.pm
│ │ │ │ ├── Pg.pm
│ │ │ │ └── SQLite.pm
│ │ │ ├── Invitation.pm
│ │ │ ├── Slice/
│ │ │ │ ├── Mysql.pm
│ │ │ │ ├── Pg.pm
│ │ │ │ └── SQLite.pm
│ │ │ └── Slice.pm
│ │ ├── DefaultConfig.pm
│ │ └── Plugin/
│ │ ├── Headers.pm
│ │ └── Helpers.pm
│ ├── Lufi.pm
│ └── Mounter.pm
├── lufi.conf.template
├── script/
│ ├── application
│ └── lufi
├── t/
│ ├── lufi.passwd
│ └── test.t
├── themes/
│ └── default/
│ ├── lib/
│ │ └── Lufi/
│ │ ├── I18N/
│ │ │ ├── ar.po
│ │ │ ├── br.po
│ │ │ ├── ca.po
│ │ │ ├── co.po
│ │ │ ├── de.po
│ │ │ ├── el.po
│ │ │ ├── en.po
│ │ │ ├── es.po
│ │ │ ├── fa.po
│ │ │ ├── fr.po
│ │ │ ├── fr_FR.po
│ │ │ ├── hr.po
│ │ │ ├── it.po
│ │ │ ├── ja_JP.po
│ │ │ ├── lufi.pot
│ │ │ ├── nl.po
│ │ │ ├── oc.po
│ │ │ ├── pl.po
│ │ │ ├── pt.po
│ │ │ ├── ru.po
│ │ │ ├── sk.po
│ │ │ ├── sv.po
│ │ │ ├── zh_Hans.po
│ │ │ └── zh_Hant.po
│ │ └── I18N.pm
│ ├── public/
│ │ ├── MATERIALIZE_LICENSE
│ │ ├── css/
│ │ │ ├── cover.css
│ │ │ ├── lufi.css
│ │ │ ├── materialize.css
│ │ │ └── materialize.min.css
│ │ ├── font/
│ │ │ └── material-design-icons/
│ │ │ └── LICENSE.txt
│ │ ├── img/
│ │ │ └── lufi.xcf
│ │ └── js/
│ │ ├── filesize.min.js
│ │ ├── filesize.min.js.map
│ │ ├── ie-detection.js
│ │ ├── jquery-3.7.1.min.js
│ │ ├── jquery-3.7.1.min.map
│ │ ├── jszip.js
│ │ ├── jszip.min.js
│ │ ├── lufi-common.js
│ │ ├── lufi-down.js
│ │ ├── lufi-files.js
│ │ ├── lufi-list-invitations.js
│ │ ├── lufi-notifications.js
│ │ ├── lufi-up.js
│ │ ├── materialize.js
│ │ ├── materialize.min.js
│ │ ├── sidenav.js
│ │ └── sjcl.js
│ └── templates/
│ ├── about.html.ep
│ ├── delays.html.ep
│ ├── delete_file.html.ep
│ ├── files.html.ep
│ ├── index.html.ep
│ ├── invitations/
│ │ ├── exception.html.ep
│ │ ├── invite.html.ep
│ │ ├── invite.mail.ep
│ │ ├── my_invitations.html.ep
│ │ └── notification_files_sent.mail.ep
│ ├── layouts/
│ │ └── default.html.ep
│ ├── login.html.ep
│ ├── logout.html.ep
│ ├── mail.html.ep
│ ├── msg.html.ep
│ ├── partial/
│ │ ├── files.js.ep
│ │ ├── index.js.ep
│ │ ├── invitations.js.ep
│ │ ├── layout.js.ep
│ │ ├── mail.js.ep
│ │ └── render.js.ep
│ └── render.html.ep
└── utilities/
├── lufi.default
├── lufi.init
├── lufi.service
└── migrations/
├── mysql.sql
├── pg.sql
└── sqlite.sql
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
local/*
files/*
cover_db/*
*.conf
*.db
*.db-shm
*.db-wal
*.bak
*.swp
script/*.pid
stop-upload
.zanata-cache/
themes/*
!themes/default
!themes/default/*
================================================
FILE: .gitlab-ci.yml
================================================
image: hatsoftwares/lufi-test-ci:bullseye
stages:
- create_release
- publish_changelog
- pouet_it
- carton
- carton_bdd
- tests
variables:
POSTGRES_DB: lufi_db
POSTGRES_USER: lufi
POSTGRES_PASSWORD: lufi_pwd
MYSQL_DATABASE: lufi_db
MYSQL_USER: lufi
MYSQL_PASSWORD: lufi_pwd
MYSQL_ROOT_PASSWORD: root
### Jobs templates
##
#
.retry: &retry
retry: 2
except:
- tags
.carton_bdd_template: &carton_bdd_definition
<<: *retry
stage: carton_bdd
cache:
key: "$CI_COMMIT_REF_NAME"
paths:
- local/
policy: pull
artifacts:
paths:
- local.tar
expire_in: 3 hours
needs:
- carton
after_script:
- tar cf local.tar local/
.tests_template: &tests_template
<<: *retry
stage: tests
coverage: '/Total.* (\d+\.\d+)$/'
before_script:
- tar xf local.tar && rm local.tar
- which mariadb_config && cd $(dirname $(which mariadb_config)) && ln -s mariadb_config mysql_config
- cd $CI_PROJECT_DIR
- pwd
script:
- MOJO_CONFIG="t/${CI_JOB_NAME}.conf" make test
- MOJO_CONFIG="t/${CI_JOB_NAME}.conf" make cover
.sqlite_template: &sqlite_definition
<<: *tests_template
needs:
- carton_sqlite
services:
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
# - name: openstackswift/saio
# alias: swiftstack-picoswiftstack
.pg_template: &pg_definition
<<: *tests_template
needs:
- carton_postgresql
services:
- name: postgres:11
alias: postgres
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
# - name: openstackswift/saio
# alias: swiftstack-picoswiftstack
.mysql_template: &mysql_definition
<<: *tests_template
needs:
- carton_mysql
services:
- name: mariadb:10.3
alias: mariadb
- name: rroemhild/test-openldap
alias: rroemhild-test-openldap
# - name: openstackswift/saio
# alias: swiftstack-picoswiftstack
### Publish tag changelog and create a toot
##
#
include:
- 'https://framagit.org/fiat-tux/gitlabci-snippets/-/raw/2aac6c1f3dd725d9aed57549da67a92759f9f9ec/create-release-from-ci.gitlab-ci.yml'
- 'https://framagit.org/fiat-tux/gitlabci-snippets/-/raw/4e4e03322e95e9b0124c714456ebf1bdc02ad43f/pouet-it-from-ci.gitlab-ci.yml'
### Podcheck
##
#
podcheck:
stage: carton
script:
- make podcheck
except:
- tags
### Cpanfile.snapshot
## Used to get a cpanfile.snapshot from a fresh server (not like my dev VM)
#
#cpanfile_snapshot:
# stage: carton
# script:
# - rm cpanfile.snapshot
# - which mariadb_config && cd $(dirname $(which mariadb_config)) && ln -s mariadb_config mysql_config
# - carton install
# - cat cpanfile.snapshot
# except:
# - tags
### Install common dependencies
##
#
carton:
<<: *retry
stage: carton
cache:
key: "$CI_COMMIT_REF_NAME"
paths:
- local/
script:
- carton install --deployment --without=sqlite --without=postgresql --without=mysql
### Install DB related dependencies
##
#
carton_sqlite:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=postgresql --without=mysql
carton_postgresql:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=sqlite --without=mysql
carton_mysql:
<<: *carton_bdd_definition
before_script:
- which mariadb_config && cd $(dirname $(which mariadb_config)) && ln -s mariadb_config mysql_config
- cd $CI_PROJECT_DIR
script:
- carton install --deployment --without=sqlite --without=postgresql
### Tests
##
#
sqlite:
<<: *sqlite_definition
postgresql:
<<: *pg_definition
mysql:
<<: *mysql_definition
================================================
FILE: .provision/README.md
================================================
## ansible-role-lufi
An ansible role deploy the application on host machine(Ubuntu 20.04)
## terraform-aws-lufi
A terraform plan creates necessary AWS infrastructure and deploy the lufi. This terraform plan uses the `lufi_startup.sh` script to deploy application on AWS and also uses above ansible roles `ansible-role-lufi` to configure the application on AWS.
================================================
FILE: .provision/ansible-role-lufi/README.md
================================================
Ansible-Role-Lufi
=========
This role installs the and configures Lufi on Debian/Ubuntu servers with nginx web server configuration.
Role Variables
--------------
| Variable name | Value | Description |
| ------------- | ----- | ----------- |
| `app_dir` | /var/www/lufi | Set the application directory for the best practice |
| `lufi_owner` | www-data | Set the application user for the best practice |
| `lufi_group` | www-data | Set the application group for the best practice |
| `_contact` | contact.example.com | Contact option (mandatory), where you have to put some way for the users to contact you. |
| `_report` | report@example.com | report option (mandatory) Put an email address or an URL to let people report illegal files |
| `_project_version` | master | We can chose the project version either Master branch, Dev branch or tag based |
| `_server_name` | IP address (or) CNAME/FQDN | Mention the Server Name for the Nginx configurations |
Sample example of use in a playbook
--------------
The following code has been tested with Ubuntu 20.04
```yaml
- name: "install lufi"
hosts: enter your hosts file
become: yes
role:
- ansible-role-lufi
vars:
lufi_owner: "www-data"
lufi_group: "www-data"
contact: "contact.example.com"
report: "report@example.com"
app_dir: "/var/www/lufi"
project_version: "master"
servername: "IP address (or) CNAME/FQDN"
```
Contributing
------------
Don’t hesitate to create a pull request
================================================
FILE: .provision/ansible-role-lufi/defaults/main.yml
================================================
---
# defaults file for roles/servers
robots_text: /var/www/html/
================================================
FILE: .provision/ansible-role-lufi/files/cronjob
================================================
#Path of the script
PATH=/var/www/lufi
carton exec script/lufi cron cleanbdd --mode production
carton exec script/lufi cron cleanfiles --mode production
carton exec script/lufi cron watch --mode production
================================================
FILE: .provision/ansible-role-lufi/files/robots.txt
================================================
User-agent: *
Allow: /$
Allow: /js/
Allow: /css/
Allow: /font/
Allow: /img/
Disallow: /r/
================================================
FILE: .provision/ansible-role-lufi/handlers/main.yml
================================================
---
# handlers file for roles/servers
- name: restart nginx
service: name=nginx state=restarted
================================================
FILE: .provision/ansible-role-lufi/tasks/apprun.yml
================================================
#apprun.yml
---
- name: This command will install the postgress module
ansible.builtin.shell:
cmd: carton install --deployment --without=test --without=sqlite --without=mysql
chdir: "{{ app_dir }}"
- name: Upload application file
ansible.builtin.template:
src: ../templates/lufi.conf.j2
dest: "{{ app_dir }}/lufi.conf"
- name: Run the command for app_executes
ansible.builtin.shell:
cmd: carton exec hypnotoad script/lufi
chdir: "{{ app_dir }}"
- name: Nginx configuration file add
ansible.builtin.template:
src: ../templates/app.conf
dest: /etc/nginx/conf.d/
mode: '0644'
notify: restart nginx
================================================
FILE: .provision/ansible-role-lufi/tasks/cron.yml
================================================
#cron.yml
---
- name: Copy the cronjob file
ansible.builtin.copy:
src: ../files/cronjob
dest: /etc/cron.d/
owner: www-data
group: www-data
- name: "example cronjob"
ansible.builtin.cron:
name: "cronjob"
state: present
user: www-data
minute: "0"
hour: "0"
day: "*"
month: "*"
weekday: "*"
job: |
carton exec script/lufi cron cleanbdd --mode production; carton exec script/lufi cron cleanfiles --mode production; carton exec script/lufi cron watch --mode production
#- name: Crontab file exists
# cron:
# name: Add date and time to a file.
# minute: "*/2"
# hour: 9-16
# weekday: 1-5
# user: devops
# job: df >> /home/devops/disk_usage
# cron_file: disk_usage
# state: present
================================================
FILE: .provision/ansible-role-lufi/tasks/dependencies.yml
================================================
#dependencies.yml
---
- name: Install Dependencies
ansible.builtin.apt:
name:
- nginx
- build-essential
- libssl-dev
- libio-socket-ssl-perl
- liblwp-protocol-https-perl
- zlib1g-dev
- libmojo-sqlite-perl
- carton
state: present
- name: Install Postgress Dev Packages
ansible.builtin.apt:
name:
- libpq-dev
================================================
FILE: .provision/ansible-role-lufi/tasks/gitclone.yml
================================================
#gitclone
---
- name: clone the repository
ansible.builtin.git:
repo: 'https://framagit.org/fiat-tux/hat-softwares/lufi.git'
dest: "{{ app_dir }}"
clone: yes
update: yes
version: "{{ project_version }}"
- name: Change the owner
ansible.builtin.file:
path: "{{ app_dir }}"
owner: "{{ lufi_owner }}"
group: "{{ lufi_group }}"
state: directory
recurse: yes
- name: Add the robots.txt file
ansible.builtin.copy:
src: ../files/robots.txt
dest: "{{ robots_text }}"
================================================
FILE: .provision/ansible-role-lufi/tasks/main.yml
================================================
---
# tasks file for roles/servers
- include: dependencies.yml
- include: gitclone.yml
- include: apprun.yml
- include: cron.yml
================================================
FILE: .provision/ansible-role-lufi/templates/lufi.conf.j2
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
# you can specify a unix socket too, like 'http+unix://%2Ftmp%2Flufi.sock'
listen => ['http://0.0.0.0:8081'],
# if you use Lufi behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lufi directly, let it commented
#proxy => 1,
# Please read http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers
# to adjust this to your server
workers => 30,
clients => 1,
},
# Put a way to contact you here and uncomment it
# You can put some HTML in it
# MANDATORY
contact => 'Contact page',
# Put an URL or an email address to receive file reports and uncomment it
# It's for make reporting illegal files easy for users
# MANDATORY
report => '{{ _report }}',
# Array of random strings used to encrypt cookies
# optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT
#secrets => ['fdjsofjoihrei'],
# Name of the instance, displayed next to the logo
# optional, default is Lufi
#instance_name => 'Lufi',
# Choose a theme. See the available themes in `themes` directory
# Optional, default is 'default'
#theme => 'default',
# Length of the random URL
# optional, default is 8
#length => 8,
# How many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# Max number of URLs to be provisioned
# optional, default is 100
#provisioning => 100,
# Length of the modify/delete token
# optional, default is 32
#token_length => 32,
# Max file size, in octets
# You can write it 100*1024*1024
# optional, no default
#max_file_size => 104857600,
# If you want to have piwik statistics, provide a piwik image tracker
# Only the image tracker is allowed, no javascript
# optional, no default
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&rec=1',
# Broadcast_message which will displayed on the index page
# optional, no default
#broadcast_message => 'Maintenance',
# Default time limit for files
# Valid values are 0, 1, 7, 30 and 365
# optional, default is 0 (no limit)
#default_delay => 0,
# Number of days after which the files will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# A warning message will be displayed on homepage
# optional, default is 0 (no limit)
#max_delay => 0,
# Size thresholds: if you want to define max delays for different sizes of file
# The keys are size in Bytes, you can't have 10*1000*10000 as key
# If a file is smaller than the smallest configured size, it will have a expiration delay of max_delay (see above)
# optional, default is using max_delay (see above) for all sizes
#delay_for_size => {
# 10000000 => 90, # between 10MB and 50MB => max is 90 days, less than 10MB => max is max_delay (see above)
# 50000000 => 60, # between 50MB ans 1GB => max is 60 days
# 1000000000 => 2, # more than 1GB => max is 2 days
#},
# URL sub-directory in which you want Lufi to be accessible
# example: you want to have Lufi under https://example.org/lufi/
# => set prefix to '/lufi' or to '/lufi/', it doesn't matter
# optional, defaut is /
#prefix => '/',
# Array of authorized domains for API calls.
# If you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# String of the URL to be redirected to when accessing /logout
# optional, default is no redirection after logging out
#logout_custom => 'https://sso.example.com/logout?redirect_uri=https%3A%2F%2Fexample.com',
# Define a path to the upload directory, where the uploaded files will be stored
# You can define it relative to lufi directory or set an absolute path
# Remember that it has to be in a directory writable by Lufi user
# optional, default is 'files'
#upload_dir => 'files',
#!!!!!!!!!!!!!!!
# EXPERIMENTAL !
#!!!!!!!!!!!!!!!
# You can store files on Swift object storage (https://en.wikipedia.org/wiki/OpenStack#Swift) instead of filesystem
# Please read https://metacpan.org/pod/Net::OpenStack::Swift#SYNOPSIS to know how to configure this setting
# IMPORTANT: add a `container` key in it, to let Lufi know which container to use. This is not a regular Net::OpenStack::Swift setting, but Lufi need it.
# EXPERIMENTAL: if the upload or download of files are stucked, reload Lufi and create a cron task to reload Lufi once a day
# You can copy Lufi files to Swift object storage by launching the command `carton exec script/lufi copyFilesToSwift` (can take a long time)
# optional, no default
#swift => {
# auth_url => 'https://auth-endpoint-url/v2.0',
# user => 'userid',
# password => 'password',
# tenant_name => 'project_id',
# container => 'lufi'
#},
# Allow to add a password on files, asked before allowing to download files
# optional, default is 0
#allow_pwd_on_files => 0,
# Force all files to be in "Burn after reading mode"
# optional, default is 0
#force_burn_after_reading => 0,
# If set, the files' URLs will always use this domain
# optional, no default
#fixed_domain => 'example.org',
# Abuse reasons
# Set an integer in the abuse field of a file in the database and it will not be downloadable anymore
# The reason will be displayed to the downloader, according to the reasons you will configure here.
# optional, no default
#abuse => {
# 0 => 'Copyright infringment',
# 1 => 'Illegal content',
#},
###############
# Mail settings
###############
# Mail configuration
# See https://metacpan.org/pod/Mojolicious::Plugin::Mail#EXAMPLES
# optional, default to sendmail method with no arguments
#mail => {
# # Valid values are 'sendmail' and 'smtp'
# how => 'smtp',
# howargs => ['smtp.example.org']
#},
# Email sender address
# optional, default to no-reply@lufi.io
#mail_sender => 'no-reply@lufi.io',
# Disable sending mail through the server
# optional, default is false
#disable_mail_sending => 0,
#############
# DB settings
#############
# Choose what database you want to use
# Valid choices are sqlite, postgresql and mysql (all lowercase)
# optional, default is sqlite
# dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# Define a path to the SQLite database
# You can define it relative to lufi directory or set an absolute path
# Remember that it has to be in a directory writable by Lufi user
# optional, default is lufi.db
# db_path => 'lufi.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# These are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
pgdb => {
database => 'lufi',
host => 'localhost',
# optional, default is 5432
port => 5432,
user => 'DBUSER',
pwd => 'DBPASSWORD',
# https://mojolicious.org/perldoc/Mojo/Pg#max_connections
# optional, default is 1
#max_connections => 1,
},
# MySQL ONLY - only used if dbtype is set to mysql
# These are the credentials to access the MySQL database
# mandatory if you choosed mysql as dbtype
#mysqldb => {
# database => 'lufi',
# host => 'localhost',
# # optional, default is 3306
# #port => 3306,
# user => 'DBUSER',
# pwd => 'DBPASSWORD',
# # https://metacpan.org/pod/Mojo::mysql#max_connections
# # optional, default is 5 (set to 0 to disable persistent connections)
# #max_connections => 5,
#},
#############################################
# LDAP settings (authentication and features)
#############################################
# Set `ldap` if you want that only authenticated users can upload files
# Please note that everybody can still download files
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org', # server URI
# user_tree => 'ou=users,dc=example,dc=org', # search base DN
# bind_dn => 'uid=ldap_user,ou=users,dc=example,dc=org', # search bind DN
# bind_pwd => 'secr3t', # search bind password
# user_attr => 'uid', # user attribute (uid, mail, sAMAccountName, etc.)
# user_filter => '(!(uid=ldap_user))', # user filter (to exclude some users, etc.)
# # optional start_tls configuration. See https://metacpan.org/pod/distribution/perl-ldap/lib/Net/LDAP.pod#start_tls
# # don't set or uncomment if you don't want to configure it
# start_tls => {
# verify => 'optional',
# clientcert => '/etc/ssl/certs/ca-bundle.pem'
# }
#},
# If you've set ldap above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
# If you use `ldap` for authentication, you can map some attributes from LDAP to be able to access them in Lufi
# Those attributes will be accessible with:
# $c->current_user->{lufi_attribute_name} in Lufi backend files (all that is in `lib` directory)
# <%= $self->current_user->{lufi_attribute_name} %> in templates files (in `themes` directory)
#
# Define the attributes like this: `lufi_attribute_name => 'LDAP_attribute_name'`
# Note that you can’t use `username` as a Lufi attribute name: this name is reserved and will contain the login of the user
# optional, no default
#ldap_map_attr => {
# displayname => 'cn',
# mail => 'mail'
#},
# When using LDAP authentication, LDAP users can invite people (by mail) to use Lufi to send them files without
# being authenticated.
# This is where you configure the behavior of the invitations.
# You may need to fetch some attributes from LDAP to use some invitations settings. See `ldap_map_attr` above.
# optional, no default
#invitations => {
# # The name of the key set in `ldap_map_attr` (above) that corresponds to the mail of the LDAP user
# # optional, default is `mail`
# mail_attr => 'mail',
# # The `From` header of invitation mail can be the mail of the LDAP user
# # Be sure to have a mail system that will correctly send the mail from your users! (DKIM, SPF…)
# # To enable this feature, set it to 1
# # optional, disabled by default
# send_invitation_with_ldap_user_mail => 1,
# # The user is able to set an expiration delay for the invitation.
# # This expiration delay can’t be more than this setting (in days).
# # optional, default is 30 days
# max_invitation_expiration_delay => 30,
# # Once the guest has submitted his files, he has an additional period of time to submit forgotten files.
# # You can set that additional period of time in minutes here.
# # To disable that feature, set it to 0 or less
# # optional, default is 10 minutes
# max_additional_period => 10,
# # Lufi follows privacy-by-design, so, by default, no files URLs (with the decode secret) are stored in database.
# # However, the concern is different for this case. Storing files URLs makes users able to retrieve the guests’ sent files
# # from their `invitations` page.
# # Set to 1 to store guests’ files URLs in database
# # optional, default is 0 (disabled)
# save_files_url_in_db => 0,
# # Users can resend the invitation to their guest. This does not extend the invitation’s expiration delay unless you
# # set this option to 1.
# # optional, default is 0 (disabled)
# extend_invitation_expiration_on_resend => 0,
#},
#########################
# Htpasswd authentication
#########################
# Set `htpasswd` if you want to use an htpasswd file instead of ldap
# See 'man htpasswd' to know how to create such file
#htpasswd => 'lufi.passwd',
#######################
# HTTP Headers settings
#######################
# Content-Security-Policy header that will be sent by Lufi
# Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
#csp => "",
# X-Frame-Options header that will be sent by Lufi
# Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/'
# Set to '' to disable X-Frame-Options header
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
# Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly
# to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
# optional, default is 'DENY'
#x_frame_options => 'DENY',
# X-Content-Type-Options that will be sent by Lufi
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
# Set to '' to disable X-Content-Type-Options header
# optional, default is 'nosniff'
#x_content_type_options => 'nosniff',
# X-XSS-Protection that will be sent by Lufi
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
# Set to '' to disable X-XSS-Protection header
# optional, default is '1; mode=block'
#x_xss_protection => '1; mode=block',
#########################
# Lufi cron jobs settings
#########################
# Expired files will be kept for 2 additional days after the expiration time has passed!
# The reasoning behind this is to allow downloads to complete and avoid deleting them while
# they are still being tranfered.
# Number of days senders' IP addresses are kept in database
# After that delay, they will be deleted from database (used with script/lufi cron cleanbdd)
# optional, default is 365
keep_ip_during => 1,
# Max size of the files directory, in octets
# Used by script/lufi cron watch to trigger an action
# optional, no default
max_total_size => 10*1024*1024*1024,
# Default action when files directory is over max_total_size (used with script/lufi cron watch)
# Valid values are 'warn', 'stop-upload' and 'delete'
# Please, see README.md
# optional, default is 'warn'
policy_when_full => 'warn',
# Files which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task
# If delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted
# optional, no default
delete_no_longer_viewed_files => 1,
};
================================================
FILE: .provision/ansible-role-lufi/templates/update.sh
================================================
# install perl dependencies
apt install liblwp-protocol-https-perl carton
sleep 5
git pull
sleep 5
carton install --deployment --without=test --without=sqlite --without=mysql
sleep5
carton exec hypnotoad script/lufi
================================================
FILE: .provision/ansible-role-lufi/vars/main.yml
================================================
---
# vars file for roles/servers
lufi_owner: "www-data"
lufi_group: "www-data"
app_dir: ""
_contact: "contact.example.com"
_report: "report@example.com"
_project_version: ""
_servername: ""
================================================
FILE: .provision/terraform-aws-lufi/README.md
================================================
# Terraform-AWS-Lufi
This terraform plan create the resourcess of EC2 instance
## Terraform Variables
Edit the `vars.tf` file to add the variables as per your need.
| Variable name | Value | Description |
| ------------- | ----- | ----------- |
| `aws_region` | us-east-1 | Set the region |
| `vpc_cidr` | 10.0.0.0/16 | Set the cidr value for the vpc |
| `public_subnet_cidr` | 10.0.2.0/24 | Set the cidr value for the public subnet |
| `user` | ubuntu | Set the EC2 instance user name |
| `public_key` | /home/user_name/.ssh/id_rsa_pub | Set the publickey value for the ec2 instance from the host machine |
| `private_key` | /home/user_name/.ssh/id_rsa | Set the private key value for the ec2 instance from the hostmachine |
| `aws_access_key` | AWSACCESSKEY | Enter your aws access key |
| `aws_secrete_key` | AWSSECRETEKEY | Enter your aws secrete key |
| `instance_name` | Lufi_app_instance | Set the name for instance |
| `app_dir` | /var/www/ | Set the application directory for the best practice |
| `lufi_owner` | www-data | Set the application user for the best practice |
| `lufi_group` | www-data | Set the application group for the best practice |
| `contact` | contact.example.com | Contact option (mandatory), where you have to put some way for the users to contact you. |
| `report` | report@example.com | report option (mandatory) Put an email address or an URL to let people report illegal files |
## Usage of terraform plan with lufi deploy script
```sh
git clone https://framagit.org/fiat-tux/hat-softwares/lufi.git
cd lufi/.provision/terraform-aws-lufi
terraform init
terraform plan
terraform apply
```
## Usage of terraform plan with ansible role
- Comment out the below `data template` and `user_data` source in __main.tf__ file
```hcl
locals {
user_data_vars = {
user = var.lufi_owner
group = var.lufi_group
directory = var.app_dir
git_branch = var.project_version
contact_lufi = var.contact
report_lufi = var.report
}
}
```
```hcl
user_data = templatefile("${path.module}/lufi_startup.sh", local.user_data_vars)
```
- Add the below provisioner data in __main.tf__ file at the `aws_instance` resource
```sh
connection {
agent = false
type = "ssh"
host = aws_instance.ec2_instance.public_dns
private_key = "${file(var.private_key)}"
user = "${var.user}"
}
provisioner "remote-exec" {
inline = [
"sudo apt update -y",
"sudo apt install python3.9 -y",
]
}
provisioner "local-exec" {
command = < hosts && \
echo "[Lufi]" | tee -a hosts && \
echo "${aws_instance.ec2_instance.public_ip} ansible_user=${var.user} ansible_ssh_private_key_file=${var.private_key}" | tee -a hosts && \
export ANSIBLE_HOST_KEY_CHECKING=False && \
ansible-playbook -u ${var.user} --private-key ${var.private_key} -i hosts site.yml
EOT
}
```
================================================
FILE: .provision/terraform-aws-lufi/lufi_startup.sh
================================================
#!/usr/bin/env bash
set -euo pipefail
echo "**********************************************************************"
echo " *"
echo "Install dependencies *"
echo " *"
echo "**********************************************************************"
SUDO=sudo
$SUDO apt update
$SUDO apt install jq wget unzip carton build-essential nginx libssl-dev libio-socket-ssl-perl liblwp-protocol-https-perl zlib1g-dev libmojo-sqlite-perl libpq-dev -y
echo "**********************************************************************"
echo " *"
echo "Configuring the Application *"
echo " *"
echo "**********************************************************************"
sleep 10;
version=$(curl -s https://framagit.org/api/v4/projects/1998/releases | jq '.[]' | jq -r '.name' | head -1)
echo $version
pushd ${directory}
$SUDO wget https://framagit.org/fiat-tux/hat-softwares/lufi/-/archive/$version/lufi-$version.zip
$SUDO unzip lufi-$version.zip
$SUDO chown ${user} lufi-$version
$SUDO chgrp ${group} lufi-$version
pushd lufi-$version
echo "**********************************************************************"
echo " *"
echo "Install Carton Packages *"
echo " *"
echo "**********************************************************************"
$SUDO carton install --deployment --without=test --without=sqlite --without=mysql
sleep 10;
$SUDO cp lufi.conf.template lufi.conf
sed -i 's/127.0.0.1/0.0.0.0/' lufi.conf
sed -i 's/#contact/contact/g' lufi.conf
sed -i "s/contact.example.com/${contact_lufi}/g" lufi.conf
sed -i 's/#report/report/' -i lufi.conf
sed -i "s/report@example.com/${report_lufi}/g" lufi.conf
sed -i "192 , 194 s/#/ /g" lufi.conf && \
sed -i "195 s/# / /g" lufi.conf && \
sed -i "196 , 198 s/#/ /g" lufi.conf && \
sed -i "199 , 201 s/# / /g" lufi.conf && \
sed -i "202 s/#/ /g" lufi.conf
echo "**********************************************************************"
echo " *"
echo "Run the Application *"
echo " *"
echo "**********************************************************************"
$SUDO carton exec hypnotoad script/lufi
================================================
FILE: .provision/terraform-aws-lufi/main.tf
================================================
locals {
user_data_vars = {
user = var.lufi_owner
group = var.lufi_group
directory = var.app_dir
contact_lufi = var.contact
report_lufi = var.report
}
}
#Create the VPC
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr}"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {
Name = "lufi-master-vpc"
}
}
# Create InternetGateWay and attach to VPC
resource "aws_internet_gateway" "IGW" {
vpc_id = "${aws_vpc.vpc.id}"
tags = {
"Name" = "lufi-master-igw"
}
}
# Create a public subnet
resource "aws_subnet" "publicsubnet" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${var.public_subnet_cidr}"
map_public_ip_on_launch = true
tags = {
Name = "lufi-master-us-east-1-public"
}
}
# Create routeTable
resource "aws_route_table" "public" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.IGW.id}"
}
tags = {
Name = "lufi-master-us-east-1-public-rt"
}
}
resource "aws_main_route_table_association" "mainRTB" {
vpc_id = "${aws_vpc.vpc.id}"
route_table_id = "${aws_route_table.public.id}"
}
## Create security group
resource "aws_security_group" "security" {
name = "lufi-master-sg"
description = "allow all traffic"
vpc_id = "${aws_vpc.vpc.id}"
ingress {
description = "allow all traffic"
from_port = "0"
to_port = "65535"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "allow port SSH"
from_port = "22"
to_port = "22"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
#Create key_pair for the instance
resource "aws_key_pair" "genkey" {
key_name = "lufi.webapp"
public_key = "${file(var.public_key)}"
}
# Add ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
# Craete ec2 instance
resource "aws_instance" "ec2_instance" {
ami = "${data.aws_ami.ubuntu.id}"
instance_type = "t2.medium"
associate_public_ip_address = "true"
subnet_id = "${aws_subnet.publicsubnet.id}"
vpc_security_group_ids = ["${aws_security_group.security.id}"]
user_data = templatefile("${path.module}/lufi_startup.sh", local.user_data_vars)
key_name = "lufi.webapp"
tags = {
Name = "${var.instance_name}"
}
}
================================================
FILE: .provision/terraform-aws-lufi/output.tf
================================================
output "public_ip" {
value = "${aws_instance.ec2_instance.public_ip}"
}
output "App_running_at" {
value = "http://${aws_instance.ec2_instance.public_ip}:8081"
}
================================================
FILE: .provision/terraform-aws-lufi/provider.tf
================================================
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
provider "aws" {
access_key = "${var.aws_access_key}"
secret_key = "${var.aws_secret_key}"
region = "${var.aws_region}"
}
================================================
FILE: .provision/terraform-aws-lufi/vars.tf
================================================
variable "aws_region" {
default = "aws_region"
}
variable "vpc_cidr" {
default = "cidr_value"
}
variable "public_subnet_cidr" {
default = "cidr_value"
}
variable "public_subnet1_cidr" {
default = "cidr_value"
}
variable "user" {
default = "user_of_instance"
}
variable "public_key" {
default = "$PWD_publickey"
}
variable "private_key" {
default = "$PWD_privatekey"
}
variable "aws_access_key" {
default = "aws_access_key"
}
variable "aws_secret_key" {
default = "aws_secrete_key"
}
variable "instance_name" {
default = "instance_name"
}
variable "lufi_owner" {
default = ""
}
variable "lufi_group" {
default = ""
}
variable "app_dir" {
default = ""
}
variable "contact" {
default = ""
}
variable "report" {
default = ""
}
================================================
FILE: AUTHORS.md
================================================
# Lufi's authors
## Main developer 🤪
- Luc Didry, aka Sky (), core developer, [@framasky@framapiaf.org](https://framapiaf.org/@framasky/) on Mastodon
## Contributors
### Translations 🌐
- Nikos Filopoulos (italian translation)
- Framartin (fix french translation)
- Sébastien Duthil (fix english translation)
- Armando Lüscher, https://noplanman.ch/ (german translation)
- Quentin Pagès (occitan translation)
- Jéssica Da Cunha (portuguese translation)
- Ilker Kulgu (dutch translation)
- Butterfly of Fire (arabic translation)
- Frju365 (german translation)
- pi2 (german translation)
### Code ⌨️
- Armando Lüscher, https://noplanman.ch/ (fix css)
- Yann Le Brech (htpasswd file support)
- Ilker Kulgu (fix IE11 compatibility)
- Stéphane Baron (bugfix)
- alexandre.LG, http://inios.fr/
- Mildis (bugfix)
- TECH'advantage, https://www.tech-advantage.com/ (paid for LDAP invitations feature)
- pi2 (typo)
- Rain (bugfix)
- Nicolas Constant (notifications)
## Vulnerabilities / bug hunters 🐛
Lufi participated to a [Hackpéro](https://hackpero.com/) (sort of a bug bounty hackathon), thanks to [Bounty factory](https://hackpero.com/).
Many thanks to those who found bugs and vulnerabilities:
- joker2a
- March
- Nicknam3
- SaxX
- tfairane
================================================
FILE: CHANGELOG
================================================
Revision history for Lufi
0.08.0 ????-??-??
0.07.3 2025-11-15
- 🔖 — Fix 0.07.2 release (tag on wrong commit)
0.07.2 2025-07-29
- 🐛 — Fix bug in POST file deletion (#319)
0.07.1 2025-07-16
- 🚚 — Use POST to delete a file instead of GET
0.07.0 2023-12-25
- ⬆️ — Update jQuery
- 🩹 — Fix a format query parameter
- 🎨 — Use template literals in js
- ➖ — Replace moment.js with Date().toLocaleDateString(…)
- 🩹 — Fix Roboto font warnings in js console
- ♿️ — Add autofocus on login input field
- ✨ — Add a `lockfile_dir` setting (fix #242)
- 👷 — Update the create-release snippet’s URL
0.06.00 2023-12-18
- ⬆️ — Update deps
- 🌐 Update translations
0.05.21 2023-02-21
- ⬆️ — Update deps (still #284)
0.05.20 2023-02-21
- ⬆️ — Update deps (#284)
0.05.19 2023-01-11
- 🌐 Update translations
- 👷 — Deactivate Swift tests in CI
- Terraform and Ansible provisioning (@arunodhayamsam)
- Support for header authentication (@mildis)
0.05.18 2022-03-19
- ✨ Add support for header authentication, thanks to @mildis (!69)
- 🌐 Update translations
0.05.17 2022-03-02
- 🌐 Update translations
0.05.16 2021-11-08
- 👷🐛 Create release in CI before publishing changelog
- 🌐 Update translations
0.05.15 2021-11-03
- 🐛 Fix mail signature separator
- 💄 Disable signature when using LDAP (#249)
- 🌐 Update translations
- 🔒 Fix XSS where using zip feature (#254)
- 🔒 Fix unauthorized manipulations of invitations (#254)
- 🔒 Detect schemeless URL in mail (#254)
0.05.14 2021-06-16
- 🔧 Set default morbo port to 3000 (as it should have stay)
- 🐛 Fix `Notification not defined` in Duckduckgo browser (Fix #224 again)
- ✨ Add `disable_mail_sending` config parameter
- 🌐 Update translations
0.05.13 2021-01-28
- 🐛 Fix latest git tag improperly fetched
0.05.12 2021-01-13
- 🌐 Update translations
- 🐛 Fix `Notification not defined`
0.05.11 2020-12-07
- 🌐 Update translations
- 🐛 Try to avoid clients constantly hitting /download/XXX
0.05.10 2020-11-28
- 🌐 Update translations
0.05.9 2020-11-25
- 🐛 Invitation, error 500 when guest send file with special character (#229)
- 🌐 Update translations
0.05.8 2020-11-18
- 🌐 Update translations
0.05.7 2020-10-06
- 🐛 Remove breakingchanges migration from sqliteToOtherDB (no need for now)
0.05.6 2020-09-28
- 🌐 Update translations
- 📝 Document file deletion behavior
0.05.5 2020-08-20
- 🐛 Change lufi-provisioning.lock handling process to avoid error messages (#210)
0.05.4 2020-08-17
- 🐛 Fix incorrect HTML in delays.html.ep (#207)
0.05.3 2020-08-17
- 🐛 Check if provisioning lockfile mod time exists before using it (#208)
0.05.2 2020-07-25
- 🔥 Remove code from abandonned feature
- Fix regression introduced in 0.05.1 (#201)
0.05.1 2020-06-30
- Update german translation
- Fix possible high load due to concurrent provisioning
0.05.0 2020-06-02
- Notifications when uploading and downloading files (#181)
- Use Weblate instead of Zanata for translations (https://weblate.framasoft.org/projects/lufi/development/)
- Add config API endpoint (#183)
- Show latest tag and commit of the instance in about page and config API endpoint (#174)
- Add support for Swift object storage (EXPERIMENTAL)
0.04.6 2019-11-07
- Now can send large files (>2Gio) while using a DB other than SQLite (#165)
- Use customized instance name in
0.04.5 2019-10-14
- Update german language
- Smoother progress of progress bar (use smaller chunks)
0.04.4 2019-10-11
- Fix invitations sorting order (#163)
- Same behavior on files and invitations tables (striped, invert selection
button) (#166)
- Change row color on hover
- Fix typo that leads to error in Safari (#164)
- Fix (for good, I hope) the problem with badly detected URLs in mail
sending (#159)
- Fix unstranslated string (#167)
0.04.3 2019-09 29
- Translate dates in invitations (#161)
- Ignore all .conf files in .gitignore
- Remove redundant fixed_domain setting in conf template
0.04.2 2019-08-14
- Update german language
- Fix logout button color on mobile view (#157)
0.04.1 2019-08-12
- LocalStorage key is now prefix-dependant (#154)
This allow to not mix up files in localStorage for two instances if they
are on the same domain
- Fix bug on cookie-based language choice when using two instances on same
domain, with one’s path is the beginning of the other instance’s path.
WARNING! You need to do a `carton install --deployment …` to have the fix.
- Fix navbar bug (there can be too much items in it, depending on Lufi
settings (LDAP, invitations…), trouble beginning at screens < 1800px)
- Add setting to customize the instance name (#156)
0.04.0 2019-08-04
- Allow to zip the files before upload
- Allow to see what’s in zip file on download page
- Allow to individually download files from zip file (only if zip created by Lufi)
- Allow to invite people to send you files on Lufi when using LDAP auth (#150).
Feature paid for by TECH’advantage (https://www.tech-advantage.com/)
0.03.7 2019-08-01
- Fix missing default values for some settings (mildis)
0.03.6 2019-07-30
- Update german translation
- Use configured url prefix when using mail sending interface
- Avoid collision between startup and recurring provisionings
- Avoid files password autocomplete by browsers (tested with Firefox and
Chromium) (#138)
0.03.5 2018-12-06
- Fix CI
0.03.4 2018-12-06
- Update arabic, german and italian translations
0.03.3 2018-11-02
- Update deps, seems to fix install on Debian Jessie
0.03.2 2018-11-02
- Fix dependences in Lufi.pm
0.03.1 2018-10-29
- Fix default CSP Header
0.03 2018-10-28
- Use Mojo::SQLite instead of ORLite
- Use FiatTux plugins
- Option to force "Burn after reading" for each uploaded file
- Use GzipStatic and StaticCache plugins for speed
- Allow to block files by setting an abuse field in DB
- Display file size when uploading
- Add Content-Security-Policy header
- Update sjcl.js
- Mitigate genRandomKey exception risk
- Add report file link in the navbar
- Allow to choose your language
- Use a recurrent task to provision shorts
- Add a command to migrate data from SQLite to an other database
- Add a test suite
- MySQL support
- Display max size on upload page
- Add CSRF token challenge on login
- Add CSRF token challenge on logout
- Add constraints on mail sending to prevent spam sending (not perfect, but
should be good enough)
- Add Code of Conduct
- Add arabic translation
- Add german translation
0.02.2 2017-09-18
- Fix cron tasks bug
0.02.1 2017-09-14
- Fix DB abstraction layer bug
0.02 2017-09-13
- Database abstraction layer
- PostgreSQL support
- IE 11 and Edge support
- Fix encoding error (#83)
- Htpassword authentication support
- Ability to add a password to a file
- Portuguese translation
- Catalan translation
- Dutch translation
- Deleting files from "My files" is now done with Ajax (#23)
- Allow bulk files deletion from "My files" (#24)
- Bugfixes
0.01 2017-01-09
- Upload files
- Download files
- Preview files in browser depending on mimetype (images and videos)
- LDAP authentication support
================================================
FILE: CONTRIBUTING.md
================================================
# CONTRIBUTING
Please, read about contributing at
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
================================================
FILE: Makefile
================================================
EXTRACTDIR ?= -D lib -D themes/default/templates
POT ?= themes/default/lib/Lufi/I18N/lufi.pot
ENPO ?= themes/default/lib/Lufi/I18N/en.po
XGETTEXT ?= carton exec local/bin/xgettext.pl -u
CARTON ?= carton exec
REAL_LUFI ?= script/application
LUFI ?= script/lufi
LDAP_CONTAINER_IMAGE ?= docker.io/rroemhild/test-openldap:latest
LOCAL_LDAP_PORT ?= 10389
LOCAL_SWIFT_PORT ?= 8080
SWIFT_CONTAINER_IMAGE ?= docker.io/openstackswift/saio:latest
MORBO_HOST ?= 0.0.0.0
MORBO_PORT ?= 3000
locales:
$(XGETTEXT) $(EXTRACTDIR) -o $(POT) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(ENPO) 2>/dev/null
podcheck:
podchecker lib/Lufi/DB/File.pm lib/Lufi/DB/Slice.pm lib/Lufi/DB/Invitation.pm
cover:
PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) cover --ignore_re '^local'
test:
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='-MDevel::Cover' $(CARTON) prove -l -f -o t/test.t
clean:
rm -rf lufi.db files/
dev: clean
$(CARTON) morbo $(LUFI) --listen http://$(MORBO_HOST):$(MORBO_PORT) --watch lib/ --watch script/ --watch themes/ --watch lufi.conf
ldap:
podman run -d -p $(LOCAL_LDAP_PORT):10389 $(LDAP_CONTAINER_IMAGE); exit 0
ldapdev: ldap dev
swift:
podman run -d --rm -p $(LOCAL_SWIFT_PORT):8080 --hostname="picoswiftstack" --name="picoswiftstack" $(SWIFT_CONTAINER_IMAGE); exit 0
@echo "Sleeping 20 seconds to let picoswiftstack start"
@sleep 20
podman exec picoswiftstack get_auth
swiftdev: swift dev
devlog:
multitail log/development.log
prod:
$(CARTON) hypnotoad -f $(LUFI)
================================================
FILE: README.md
================================================
# Lufi
## What does Lufi mean?
Lufi means Let's Upload that FIle. It's a E2E encrypted file sharing software.
## Which browsers are compatible?
Lufi is tested and working on the following browsers / devices :
- Firefox
- Chrome
- Internet Explorer 11
- Microsoft Edge
- Safari
- iOS devices (ipad, iphone)
- Android devices (Galaxy tab, Galaxy S8)
## What does it do?
It stores files and allows you to download them.
Is that all? No. All the files are encrypted **by the browser**! It means that your files **never** leave your computer unencrypted.
The administrator of the Lufi instance you use will not be able to see what is in your file, neither will your network administrator, or your ISP.
The encryption key part of the URL is a anchor (Cf. [Fragment Identifier](https://en.wikipedia.org/wiki/Fragment_identifier)), that means this part is only processed client-side and does not reach the server. :-)
## License
Lufi is licensed under the terms of the AGPL. See the [LICENSE](LICENSE) file.
## Official instance
There is a demonstration site, available at , with strong limitations on time and file size.
## Logo
Because Lufi is quite similar to Luffy, like in "[Monkey D. Luffy](https://en.wikipedia.org/wiki/Monkey_D._Luffy)" from [One Piece](https://en.wikipedia.org/wiki/One_Piece) manga, the logo is a straw hat, made with pain, love and [Inkscape](https://inkscape.org/).
## Wiki (work in progress)
The official wiki will contain all you need to know about Lufi (installation, configuration, etc.). Go to or clone it:
```
git clone https://framagit.org/fiat-tux/hat-softwares/lufi.wiki.git
```
## Encryption
All the encryption/decryption processes take place in your browser. The encryption key is never sent over the network.
However please note that some metadata are sent unencrypted:
* the file name
* its size
* its mimetype
## Client
There is the web interface, but you can use a CLI client too! Have a look at [lufi-cli](https://framagit.org/fiat-tux/hat-softwares/lufi-cli) or install it directly with `sudo npm install -g lufi-cli`.
There is another client in Python too: .
## Internationalization
Lufi comes with several languages.
Please, see [this wiki page](https://framagit.org/fiat-tux/hat-softwares/lufi/wikis/contribute#internationalization) to know how to contribute to internationalization.
## Authors
See [AUTHORS.md](AUTHORS.md) file.
## Contribute!
Please consider contributing, either by [reporting issues](https://framagit.org/fiat-tux/hat-softwares/lufi/issues) or by helping the internationalization. And of course, code contributions are welcome!
The details on how to contribute are on the [wiki](https://framagit.org/fiat-tux/hat-softwares/lufi/wikis/contribute).
This software uses [Fiat Tux Code of conduct](https://framagit.org/fiat-tux/code-of-conduct/blob/master/README.md).
## Make a donation
You can make a donation to the author on [Tipeee](https://www.tipeee.com/fiat-tux) or on [Liberapay](https://liberapay.com/sky/).
## Other dependencies
Lufi is written in Perl with the [Mojolicious](http://mojolicio.us) framework.
It uses:
* [Materialize](http://materializecss.com/) framework to look not too ugly
* [jQuery](https://jquery.com)
* [Stanford Javascript Crypto Library](http://bitwiseshiftleft.github.com/sjcl/)
* [Moment.js](http://momentjs.com/) for displaying real dates instead of unix timestamps.
* [Filesize.js](http://filesizejs.com/) for displaying file sizes
## Deploy Lufi
An ansible role and a terraform plan reside under the `.provision` directory. An user could utilize the terraform plan if they chose to deploy lufi on AWS, if that's not the goal, they could simply execute the ansible role in part. Usage docs for both are present in their respective directories.
You can use Docker by using the recipe on .
================================================
FILE: cpanfile
================================================
requires 'inc::Module::Install';
requires 'Mojolicious', '>= 8.05';
requires 'Mojolicious::Plugin::DebugDumperHelper';
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::Mail';
requires 'Mojolicious::Plugin::GzipStatic';
requires 'Mojolicious::Plugin::StaticCache';
requires 'Mojolicious::Plugin::CSPHeader', '>= 0.06';
requires 'Mojolicious::Plugin::FiatTux::Helpers', '== 0.12', url => 'https://framagit.org/fiat-tux/mojolicious/fiat-tux/mojolicious-plugin-fiattux-helpers/-/archive/0.12/mojolicious-plugin-fiattux-helpers-0.12.tar.gz';
requires 'Mojolicious::Plugin::FiatTux::GrantAccess', '== 0.10', url => 'https://framagit.org/fiat-tux/mojolicious/fiat-tux/mojolicious-plugin-fiattux-grantaccess/-/archive/0.10/mojolicious-plugin-fiattux-grantaccess-0.10.tar.gz';
requires 'Mojolicious::Plugin::FiatTux::Themes', '== 0.02', url => 'https://framagit.org/fiat-tux/mojolicious/fiat-tux/mojolicious-plugin-fiattux-themes/-/archive/0.02/mojolicious-plugin-fiattux-themes-0.02.tar.gz';
requires 'Filesys::DiskUsage';
requires 'Switch';
requires 'Locale::Maketext';
requires 'Locale::Maketext::Extract';
requires 'Net::DNS';
requires 'Email::Valid';
requires 'Number::Bytes::Human';
requires 'Filesys::DfPortable';
requires 'Data::Entropy';
requires 'Crypt::SaltedHash';
requires 'Data::Validate::URI';
requires 'Term::ProgressBar';
requires 'URI::Find';
# Mojolicious optional deps
feature 'optional_deps' => sub {
requires 'Cpanel::JSON::XS';
requires 'EV';
requires 'IO::Socket::Socks';
requires 'Role::Tiny';
};
feature 'test' => sub {
requires 'Devel::Cover';
requires 'B::Debug';
};
feature 'ldap', 'LDAP authentication support' => sub {
requires 'Net::LDAP';
requires 'Mojolicious::Plugin::Authentication';
requires 'Date::Language';
};
feature 'htpasswd', 'Htpasswd authentication support' => sub {
requires 'Apache::Htpasswd';
requires 'Mojolicious::Plugin::Authentication';
};
feature 'auth_headers', 'Header authentication support' => sub {
requires 'Mojolicious::Plugin::Authentication';
};
feature 'postgresql', 'PostgreSQL support' => sub {
requires 'Mojo::Pg';
requires 'Mojolicious::Plugin::PgURLHelper';
};
feature 'sqlite', 'SQLite support' => sub {
requires 'Mojo::SQLite', '>= 3.000';
};
feature 'mysql', 'MySQL support' => sub {
requires 'DBD::mysql', '== 4.050';
requires 'Mojo::mysql';
requires 'Mojolicious::Plugin::PgURLHelper';
};
feature 'swift-storage', 'Openstack Swift object storage support' => sub {
requires 'Net::OpenStack::Swift';
};
================================================
FILE: cpanfile.snapshot
================================================
# carton snapshot format: version 1.0
DISTRIBUTIONS
Apache-Htpasswd-1.9
pathname: K/KM/KMELTZ/Apache-Htpasswd-1.9.tar.gz
provides:
Apache::Htpasswd 1.9
requirements:
Crypt::PasswdMD5 0
Digest::SHA 2
ExtUtils::MakeMaker 0
MIME::Base64 0
App-Rad-1.05
pathname: G/GA/GARU/App-Rad-1.05.tar.gz
provides:
App::Rad 1.05
App::Rad::Config undef
App::Rad::Exclude 0.01
App::Rad::Help 0.03
App::Rad::Include 0.01
requirements:
Attribute::Handlers 0
B::Deparse 0
Carp 0
ExtUtils::MakeMaker 0
File::Temp 0
FindBin 0
Getopt::Long 2.36
Test::More 0
Authen-SASL-2.1700
pathname: E/EH/EHUELS/Authen-SASL-2.1700.tar.gz
provides:
Authen::SASL 2.1700
Authen::SASL::CRAM_MD5 2.1700
Authen::SASL::EXTERNAL 2.1700
Authen::SASL::Perl 2.1700
Authen::SASL::Perl::ANONYMOUS 2.1700
Authen::SASL::Perl::CRAM_MD5 2.1700
Authen::SASL::Perl::DIGEST_MD5 2.1700
Authen::SASL::Perl::EXTERNAL 2.1700
Authen::SASL::Perl::GSSAPI 2.1700
Authen::SASL::Perl::LOGIN 2.1700
Authen::SASL::Perl::PLAIN 2.1700
requirements:
Digest::HMAC_MD5 0
Digest::MD5 0
ExtUtils::MakeMaker 0
perl 5.006000
B-Debug-1.26
pathname: R/RU/RURBAN/B-Debug-1.26.tar.gz
provides:
B::Debug 1.26
requirements:
B 0
ExtUtils::MakeMaker 0
Test::More 0
deprecate 0.03
B-Hooks-EndOfScope-0.26
pathname: E/ET/ETHER/B-Hooks-EndOfScope-0.26.tar.gz
provides:
B::Hooks::EndOfScope 0.26
B::Hooks::EndOfScope::PP 0.26
B::Hooks::EndOfScope::XS 0.26
requirements:
ExtUtils::MakeMaker 0
Hash::Util::FieldHash 0
Module::Implementation 0.05
Scalar::Util 0
Sub::Exporter::Progressive 0.001006
Text::ParseWords 0
Tie::Hash 0
Variable::Magic 0.48
perl 5.006001
strict 0
warnings 0
Canary-Stability-2013
pathname: M/ML/MLEHMANN/Canary-Stability-2013.tar.gz
provides:
Canary::Stability 2013
requirements:
ExtUtils::MakeMaker 0
Capture-Tiny-0.48
pathname: D/DA/DAGOLDEN/Capture-Tiny-0.48.tar.gz
provides:
Capture::Tiny 0.48
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 6.17
File::Spec 0
File::Temp 0
IO::Handle 0
Scalar::Util 0
perl 5.006
strict 0
warnings 0
Class-Accessor-Lite-0.08
pathname: K/KA/KAZUHO/Class-Accessor-Lite-0.08.tar.gz
provides:
Class::Accessor::Lite 0.08
requirements:
ExtUtils::MakeMaker 6.36
Class-Method-Modifiers-2.15
pathname: E/ET/ETHER/Class-Method-Modifiers-2.15.tar.gz
provides:
Class::Method::Modifiers 2.15
requirements:
B 0
Carp 0
Exporter 0
ExtUtils::MakeMaker 0
base 0
perl 5.006
strict 0
warnings 0
Class-MethodMaker-2.24
pathname: S/SC/SCHWIGON/class-methodmaker/Class-MethodMaker-2.24.tar.gz
provides:
Class::MethodMaker 2.24
Class::MethodMaker::Constants undef
Class::MethodMaker::Engine 2.24
Class::MethodMaker::OptExt undef
Class::MethodMaker::V1Compat undef
Generate undef
requirements:
ExtUtils::MakeMaker 0
perl 5.006
Clone-0.46
pathname: G/GA/GARU/Clone-0.46.tar.gz
provides:
Clone 0.46
requirements:
ExtUtils::MakeMaker 0
Clone-Choose-0.010
pathname: H/HE/HERMES/Clone-Choose-0.010.tar.gz
provides:
Clone::Choose 0.010
requirements:
ExtUtils::MakeMaker 0
Storable 0
perl 5.008001
Convert-ASN1-0.34
pathname: T/TI/TIMLEGGE/Convert-ASN1-0.34.tar.gz
provides:
Convert::ASN1 0.34
requirements:
ExtUtils::MakeMaker 0
Cpanel-JSON-XS-4.37
pathname: R/RU/RURBAN/Cpanel-JSON-XS-4.37.tar.gz
provides:
Cpanel::JSON::XS 4.37
Cpanel::JSON::XS::Type undef
requirements:
Carp 0
Config 0
Encode 1.9801
Exporter 0
ExtUtils::MakeMaker 0
Pod::Text 2.08
XSLoader 0
overload 0
strict 0
warnings 0
Crypt-PasswdMD5-1.42
pathname: R/RS/RSAVAGE/Crypt-PasswdMD5-1.42.tgz
provides:
Crypt::PasswdMD5 1.42
requirements:
Digest::MD5 2.53
ExtUtils::MakeMaker 0
strict 0
warnings 0
Crypt-Rijndael-1.16
pathname: L/LE/LEONT/Crypt-Rijndael-1.16.tar.gz
provides:
Crypt::Rijndael 1.16
requirements:
ExtUtils::MakeMaker 0
perl 5.006
Crypt-SaltedHash-0.09
pathname: G/GS/GSHANK/Crypt-SaltedHash-0.09.tar.gz
provides:
Crypt::SaltedHash 0.09
requirements:
Digest 0
ExtUtils::MakeMaker 6.30
Test::Fatal 0
Test::More 0
DBD-MariaDB-1.23
pathname: P/PA/PALI/DBD-MariaDB-1.23.tar.gz
provides:
DBD::MariaDB 1.23
requirements:
Config 0
DBI 1.608
Data::Dumper 0
Devel::CheckLib 1.12
DynaLoader 0
ExtUtils::MakeMaker 0
File::Spec 0
Getopt::Long 0
perl 5.008001
strict 0
utf8 0
warnings 0
DBD-Pg-3.18.0
pathname: T/TU/TURNSTEP/DBD-Pg-3.18.0.tar.gz
provides:
Bundle::DBD::Pg v3.18.0
DBD::Pg v3.18.0
requirements:
DBI 1.614
ExtUtils::MakeMaker 6.58
File::Temp 0
Test::More 0.88
Time::HiRes 0
version 0
DBD-SQLite-1.74
pathname: I/IS/ISHIGAKI/DBD-SQLite-1.74.tar.gz
provides:
DBD::SQLite 1.74
DBD::SQLite::Constants undef
DBD::SQLite::GetInfo undef
DBD::SQLite::VirtualTable 1.74
DBD::SQLite::VirtualTable::Cursor 1.74
DBD::SQLite::VirtualTable::FileContent undef
DBD::SQLite::VirtualTable::FileContent::Cursor undef
DBD::SQLite::VirtualTable::PerlData undef
DBD::SQLite::VirtualTable::PerlData::Cursor undef
requirements:
DBI 1.57
ExtUtils::MakeMaker 0
File::Spec 0.82
Test::More 0.88
Tie::Hash 0
perl 5.006
DBD-mysql-4.050
pathname: D/DV/DVEEDEN/DBD-mysql-4.050.tar.gz
provides:
Bundle::DBD::mysql 4.050
DBD::mysql 4.050
DBD::mysql::GetInfo undef
DBD::mysql::db 4.050
DBD::mysql::dr 4.050
DBD::mysql::st 4.050
requirements:
DBI 1.609
Data::Dumper 0
Devel::CheckLib 1.09
ExtUtils::MakeMaker 0
perl 5.008001
DBI-1.643
pathname: T/TI/TIMB/DBI-1.643.tar.gz
provides:
Bundle::DBI 12.008696
DBD::DBM 0.08
DBD::DBM::Statement 0.08
DBD::DBM::Table 0.08
DBD::DBM::db 0.08
DBD::DBM::dr 0.08
DBD::DBM::st 0.08
DBD::ExampleP 12.014311
DBD::ExampleP::db 12.014311
DBD::ExampleP::dr 12.014311
DBD::ExampleP::st 12.014311
DBD::File 0.44
DBD::File::DataSource::File 0.44
DBD::File::DataSource::Stream 0.44
DBD::File::Statement 0.44
DBD::File::Table 0.44
DBD::File::TableSource::FileSystem 0.44
DBD::File::db 0.44
DBD::File::dr 0.44
DBD::File::st 0.44
DBD::Gofer 0.015327
DBD::Gofer::Policy::Base 0.010088
DBD::Gofer::Policy::classic 0.010088
DBD::Gofer::Policy::pedantic 0.010088
DBD::Gofer::Policy::rush 0.010088
DBD::Gofer::Transport::Base 0.014121
DBD::Gofer::Transport::corostream undef
DBD::Gofer::Transport::null 0.010088
DBD::Gofer::Transport::pipeone 0.010088
DBD::Gofer::Transport::stream 0.014599
DBD::Gofer::db 0.015327
DBD::Gofer::dr 0.015327
DBD::Gofer::st 0.015327
DBD::Mem 0.001
DBD::Mem::DataSource 0.001
DBD::Mem::Statement 0.001
DBD::Mem::Table 0.001
DBD::Mem::db 0.001
DBD::Mem::dr 0.001
DBD::Mem::st 0.001
DBD::NullP 12.014715
DBD::NullP::db 12.014715
DBD::NullP::dr 12.014715
DBD::NullP::st 12.014715
DBD::Proxy 0.2004
DBD::Proxy::RPC::PlClient 0.2004
DBD::Proxy::db 0.2004
DBD::Proxy::dr 0.2004
DBD::Proxy::st 0.2004
DBD::Sponge 12.010003
DBD::Sponge::db 12.010003
DBD::Sponge::dr 12.010003
DBD::Sponge::st 12.010003
DBDI 12.015129
DBI 1.643
DBI::Const::GetInfo::ANSI 2.008697
DBI::Const::GetInfo::ODBC 2.011374
DBI::Const::GetInfoReturn 2.008697
DBI::Const::GetInfoType 2.008697
DBI::DBD 12.015129
DBI::DBD::Metadata 2.014214
DBI::DBD::SqlEngine 0.06
DBI::DBD::SqlEngine::DataSource 0.06
DBI::DBD::SqlEngine::Statement 0.06
DBI::DBD::SqlEngine::Table 0.06
DBI::DBD::SqlEngine::TableSource 0.06
DBI::DBD::SqlEngine::TieMeta 0.06
DBI::DBD::SqlEngine::TieTables 0.06
DBI::DBD::SqlEngine::db 0.06
DBI::DBD::SqlEngine::dr 0.06
DBI::DBD::SqlEngine::st 0.06
DBI::Gofer::Execute 0.014283
DBI::Gofer::Request 0.012537
DBI::Gofer::Response 0.011566
DBI::Gofer::Serializer::Base 0.009950
DBI::Gofer::Serializer::DataDumper 0.009950
DBI::Gofer::Serializer::Storable 0.015586
DBI::Gofer::Transport::Base 0.012537
DBI::Gofer::Transport::pipeone 0.012537
DBI::Gofer::Transport::stream 0.012537
DBI::Profile 2.015065
DBI::ProfileData 2.010008
DBI::ProfileDumper 2.015325
DBI::ProfileDumper::Apache 2.014121
DBI::ProfileSubs 0.009396
DBI::ProxyServer 0.3005
DBI::ProxyServer::db 0.3005
DBI::ProxyServer::dr 0.3005
DBI::ProxyServer::st 0.3005
DBI::SQL::Nano 1.015544
DBI::SQL::Nano::Statement_ 1.015544
DBI::SQL::Nano::Table_ 1.015544
DBI::Util::CacheMemory 0.010315
DBI::Util::_accessor 0.009479
DBI::common 1.643
requirements:
ExtUtils::MakeMaker 6.48
Test::Simple 0.90
perl 5.008001
Data-Entropy-0.007
pathname: Z/ZE/ZEFRAM/Data-Entropy-0.007.tar.gz
provides:
Data::Entropy 0.007
Data::Entropy::Algorithms 0.007
Data::Entropy::RawSource::CryptCounter 0.007
Data::Entropy::RawSource::Local 0.007
Data::Entropy::RawSource::RandomOrg 0.007
Data::Entropy::RawSource::RandomnumbersInfo 0.007
Data::Entropy::Source 0.007
requirements:
Carp 0
Crypt::Rijndael 0
Data::Float 0.008
Errno 1.00
Exporter 0
HTTP::Lite 2.2
IO::File 1.03
Module::Build 0
Params::Classify 0
Test::More 0
constant 0
integer 0
parent 0
perl 5.006
strict 0
warnings 0
Data-Float-0.013
pathname: Z/ZE/ZEFRAM/Data-Float-0.013.tar.gz
provides:
Data::Float 0.013
requirements:
Carp 0
Exporter 0
Module::Build 0
Test::More 0
constant 0
integer 0
parent 0
perl 5.006
strict 0
warnings 0
Data-Validate-Domain-0.15
pathname: D/DR/DROLSKY/Data-Validate-Domain-0.15.tar.gz
provides:
Data::Validate::Domain 0.15
requirements:
Exporter 0
ExtUtils::MakeMaker 0
Net::Domain::TLD 1.74
strict 0
warnings 0
Data-Validate-IP-0.31
pathname: D/DR/DROLSKY/Data-Validate-IP-0.31.tar.gz
provides:
Data::Validate::IP 0.31
requirements:
Exporter 0
ExtUtils::MakeMaker 0
NetAddr::IP 4
Scalar::Util 0
base 0
perl 5.008
strict 0
warnings 0
Data-Validate-URI-0.07
pathname: S/SO/SONNEN/Data-Validate-URI-0.07.tar.gz
provides:
Data::Validate::URI 0.07
requirements:
Data::Validate::Domain 0
Data::Validate::IP 0
ExtUtils::MakeMaker 0
Data-Validator-1.07
pathname: G/GF/GFUJI/Data-Validator-1.07.tar.gz
provides:
Data::Validator 1.07
Data::Validator::Role::AllowExtra undef
Data::Validator::Role::Croak undef
Data::Validator::Role::Method undef
Data::Validator::Role::NoRestricted undef
Data::Validator::Role::NoThrow undef
Data::Validator::Role::Sequenced undef
Data::Validator::Role::SmartSequenced undef
Data::Validator::Role::StrictSequenced undef
requirements:
ExtUtils::MakeMaker 6.59
Module::Build 0.38
Mouse 0.93
perl 5.008001
Devel-CheckCompiler-0.07
pathname: S/SY/SYOHEX/Devel-CheckCompiler-0.07.tar.gz
provides:
Devel::AssertC99 undef
Devel::CheckCompiler 0.07
requirements:
Exporter 0
ExtUtils::CBuilder 0
File::Temp 0
Module::Build::Tiny 0.035
Test::More 0.98
parent 0
perl 5.008001
Devel-CheckLib-1.16
pathname: M/MA/MATTN/Devel-CheckLib-1.16.tar.gz
provides:
Devel::CheckLib 1.16
requirements:
Exporter 0
ExtUtils::MakeMaker 0
File::Spec 0
File::Temp 0.16
perl 5.004050
Devel-Cover-1.40
pathname: P/PJ/PJCJ/Devel-Cover-1.40.tar.gz
provides:
Devel::Cover 1.40
Devel::Cover::Annotation::Git 1.40
Devel::Cover::Annotation::Random 1.40
Devel::Cover::Annotation::Svk 1.40
Devel::Cover::Branch 1.40
Devel::Cover::Collection 1.40
Devel::Cover::Collection::Template::Provider 1.40
Devel::Cover::Condition 1.40
Devel::Cover::Condition_and_2 1.40
Devel::Cover::Condition_and_3 1.40
Devel::Cover::Condition_or_2 1.40
Devel::Cover::Condition_or_3 1.40
Devel::Cover::Condition_xor_4 1.40
Devel::Cover::Criterion 1.40
Devel::Cover::DB 1.40
Devel::Cover::DB::Criterion 1.40
Devel::Cover::DB::Digests 1.40
Devel::Cover::DB::File 1.40
Devel::Cover::DB::IO 1.40
Devel::Cover::DB::IO::Base 1.40
Devel::Cover::DB::IO::JSON 1.40
Devel::Cover::DB::IO::Sereal 1.40
Devel::Cover::DB::IO::Storable 1.40
Devel::Cover::DB::Run 1.40
Devel::Cover::DB::Structure 1.40
Devel::Cover::Html_Common 1.40
Devel::Cover::Op 1.40
Devel::Cover::Pod 1.40
Devel::Cover::Report::Compilation 1.40
Devel::Cover::Report::Html 1.40
Devel::Cover::Report::Html_basic 1.40
Devel::Cover::Report::Html_basic::Template::Provider 1.40
Devel::Cover::Report::Html_minimal 1.40
Devel::Cover::Report::Html_subtle 1.40
Devel::Cover::Report::Html_subtle::Template::Provider 1.40
Devel::Cover::Report::Json 1.40
Devel::Cover::Report::Sort 1.40
Devel::Cover::Report::Text 1.40
Devel::Cover::Report::Text2 1.40
Devel::Cover::Report::Vim 1.40
Devel::Cover::Report::Vim::Template::Provider 1.40
Devel::Cover::Statement 1.40
Devel::Cover::Subroutine 1.40
Devel::Cover::Test 1.40
Devel::Cover::Time 1.40
Devel::Cover::Truth_Table 1.40
Devel::Cover::Truth_Table::Row 1.40
Devel::Cover::Util 1.40
Devel::Cover::Web 1.40
requirements:
Digest::MD5 0
ExtUtils::MakeMaker 0
HTML::Entities 3.69
Storable 0
Test::More 0
Digest-HMAC-1.04
pathname: A/AR/ARODLAND/Digest-HMAC-1.04.tar.gz
provides:
Digest::HMAC 1.04
Digest::HMAC_MD5 1.04
Digest::HMAC_SHA1 1.04
requirements:
Digest::MD5 2
Digest::SHA 1
ExtUtils::MakeMaker 0
perl 5.004
Dist-CheckConflicts-0.11
pathname: D/DO/DOY/Dist-CheckConflicts-0.11.tar.gz
provides:
Dist::CheckConflicts 0.11
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 6.30
Module::Runtime 0.009
base 0
strict 0
warnings 0
EV-4.34
pathname: M/ML/MLEHMANN/EV-4.34.tar.gz
provides:
EV 4.34
EV::MakeMaker undef
requirements:
Canary::Stability 0
ExtUtils::MakeMaker 6.52
common::sense 0
Email-Date-Format-1.008
pathname: R/RJ/RJBS/Email-Date-Format-1.008.tar.gz
provides:
Email::Date::Format 1.008
requirements:
Exporter 5.57
ExtUtils::MakeMaker 6.78
Time::Local 1.27
perl 5.012
warnings 0
Email-Valid-1.203
pathname: R/RJ/RJBS/Email-Valid-1.203.tar.gz
provides:
Email::Valid 1.203
requirements:
ExtUtils::MakeMaker 0
Mail::Address 0
Scalar::Util 0
Test::More 0
perl 5.006
Encode-Locale-1.05
pathname: G/GA/GAAS/Encode-Locale-1.05.tar.gz
provides:
Encode::Locale 1.05
requirements:
Encode 2
Encode::Alias 0
ExtUtils::MakeMaker 0
perl 5.008
ExtUtils-Config-0.008
pathname: L/LE/LEONT/ExtUtils-Config-0.008.tar.gz
provides:
ExtUtils::Config 0.008
requirements:
Data::Dumper 0
ExtUtils::MakeMaker 6.30
strict 0
warnings 0
ExtUtils-Helpers-0.026
pathname: L/LE/LEONT/ExtUtils-Helpers-0.026.tar.gz
provides:
ExtUtils::Helpers 0.026
ExtUtils::Helpers::Unix 0.026
ExtUtils::Helpers::VMS 0.026
ExtUtils::Helpers::Windows 0.026
requirements:
Carp 0
Exporter 5.57
ExtUtils::MakeMaker 0
File::Basename 0
File::Copy 0
File::Spec::Functions 0
Text::ParseWords 3.24
perl 5.006
strict 0
warnings 0
ExtUtils-InstallPaths-0.012
pathname: L/LE/LEONT/ExtUtils-InstallPaths-0.012.tar.gz
provides:
ExtUtils::InstallPaths 0.012
requirements:
Carp 0
ExtUtils::Config 0.002
ExtUtils::MakeMaker 0
File::Spec 0
perl 5.006
strict 0
warnings 0
File-Listing-6.16
pathname: P/PL/PLICEASE/File-Listing-6.16.tar.gz
provides:
File::Listing 6.16
File::Listing::apache 6.16
File::Listing::dosftp 6.16
File::Listing::netware 6.16
File::Listing::unix 6.16
File::Listing::vms 6.16
requirements:
Exporter 5.57
ExtUtils::MakeMaker 0
HTTP::Date 0
perl 5.006
File-Remove-1.61
pathname: S/SH/SHLOMIF/File-Remove-1.61.tar.gz
provides:
File::Remove 1.61
requirements:
Cwd 3.29
ExtUtils::MakeMaker 0
File::Glob 0
File::Path 0
File::Spec 3.29
Module::Build 0.28
constant 0
perl 5.008
strict 0
vars 0
warnings 0
Filesys-DfPortable-0.85
pathname: I/IG/IGUTHRIE/Filesys-DfPortable-0.85.tar.gz
provides:
Filesys::DfPortable 0.85
requirements:
ExtUtils::MakeMaker 0
Filesys-DiskUsage-0.13
pathname: M/MA/MANWAR/Filesys-DiskUsage-0.13.tar.gz
provides:
Filesys::DiskUsage 0.13
requirements:
ExtUtils::MakeMaker 0
File::Basename 0
File::Find 0
File::Temp 0
Test::More 0
Test::Warn 0
perl 5.006
Furl-3.14
pathname: S/SY/SYOHEX/Furl-3.14.tar.gz
provides:
Furl 3.14
Furl::ConnectionCache undef
Furl::HTTP 3.14
Furl::Headers undef
Furl::Request undef
Furl::Response undef
Furl::ZlibStream undef
requirements:
Class::Accessor::Lite 0
Encode 0
HTTP::Parser::XS 0.11
MIME::Base64 0
Module::Build::Tiny 0.035
Mozilla::CA 0
Scalar::Util 0
Socket 0
Time::HiRes 0
perl 5.008001
HTML-Parser-3.81
pathname: O/OA/OALDERS/HTML-Parser-3.81.tar.gz
provides:
HTML::Entities 3.81
HTML::Filter 3.81
HTML::HeadParser 3.81
HTML::LinkExtor 3.81
HTML::Parser 3.81
HTML::PullParser 3.81
HTML::TokeParser 3.81
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 6.52
HTML::Tagset 0
HTTP::Headers 0
IO::File 0
URI 0
URI::URL 0
XSLoader 0
strict 0
HTML-Tagset-3.20
pathname: P/PE/PETDANCE/HTML-Tagset-3.20.tar.gz
provides:
HTML::Tagset 3.20
requirements:
ExtUtils::MakeMaker 0
HTTP-CookieJar-0.014
pathname: D/DA/DAGOLDEN/HTTP-CookieJar-0.014.tar.gz
provides:
HTTP::CookieJar 0.014
HTTP::CookieJar::LWP 0.014
requirements:
Carp 0
ExtUtils::MakeMaker 6.17
HTTP::Date 0
Time::Local 1.1901
parent 0
perl 5.008001
strict 0
warnings 0
HTTP-Cookies-6.11
pathname: O/OA/OALDERS/HTTP-Cookies-6.11.tar.gz
provides:
HTTP::Cookies 6.11
HTTP::Cookies::Microsoft 6.11
HTTP::Cookies::Netscape 6.11
requirements:
Carp 0
ExtUtils::MakeMaker 0
HTTP::Date 6
HTTP::Headers::Util 6
HTTP::Request 0
locale 0
perl 5.008001
strict 0
HTTP-Date-6.06
pathname: O/OA/OALDERS/HTTP-Date-6.06.tar.gz
provides:
HTTP::Date 6.06
requirements:
Exporter 0
ExtUtils::MakeMaker 0
Time::Local 1.28
Time::Zone 0
perl 5.006002
strict 0
HTTP-Lite-2.44
pathname: N/NE/NEILB/HTTP-Lite-2.44.tar.gz
provides:
HTTP::Lite 2.44
requirements:
ExtUtils::MakeMaker 0
Fcntl 0
Socket 1.3
perl 5.005
strict 0
warnings 0
HTTP-Message-6.45
pathname: O/OA/OALDERS/HTTP-Message-6.45.tar.gz
provides:
HTTP::Config 6.45
HTTP::Headers 6.45
HTTP::Headers::Auth 6.45
HTTP::Headers::ETag 6.45
HTTP::Headers::Util 6.45
HTTP::Message 6.45
HTTP::Request 6.45
HTTP::Request::Common 6.45
HTTP::Response 6.45
HTTP::Status 6.45
requirements:
Carp 0
Clone 0.46
Compress::Raw::Bzip2 0
Compress::Raw::Zlib 2.062
Encode 3.01
Encode::Locale 1
Exporter 5.57
ExtUtils::MakeMaker 0
File::Spec 0
HTTP::Date 6
IO::Compress::Bzip2 2.021
IO::Compress::Deflate 0
IO::Compress::Gzip 0
IO::HTML 0
IO::Uncompress::Inflate 0
IO::Uncompress::RawInflate 0
LWP::MediaTypes 6
MIME::Base64 2.1
MIME::QuotedPrint 0
URI 1.10
parent 0
perl 5.008001
strict 0
warnings 0
HTTP-Negotiate-6.01
pathname: G/GA/GAAS/HTTP-Negotiate-6.01.tar.gz
provides:
HTTP::Negotiate 6.01
requirements:
ExtUtils::MakeMaker 0
HTTP::Headers 6
perl 5.008001
HTTP-Parser-XS-0.17
pathname: K/KA/KAZUHO/HTTP-Parser-XS-0.17.tar.gz
provides:
HTTP::Parser::XS 0.17
HTTP::Parser::XS::PP undef
requirements:
ExtUtils::MakeMaker 6.36
Test::More 0.96
Hash-Merge-0.302
pathname: H/HE/HERMES/Hash-Merge-0.302.tar.gz
provides:
Hash::Merge 0.302
requirements:
Clone::Choose 0.008
ExtUtils::MakeMaker 6.64
Scalar::Util 0
perl 5.008001
IO-HTML-1.004
pathname: C/CJ/CJM/IO-HTML-1.004.tar.gz
provides:
IO::HTML 1.004
requirements:
Carp 0
Encode 2.10
Exporter 5.57
ExtUtils::MakeMaker 0
perl 5.008
IO-Socket-SSL-2.084
pathname: S/SU/SULLR/IO-Socket-SSL-2.084.tar.gz
provides:
IO::Socket::SSL 2.084
IO::Socket::SSL::Intercept 2.056
IO::Socket::SSL::OCSP_Cache 2.084
IO::Socket::SSL::OCSP_Resolver 2.084
IO::Socket::SSL::PublicSuffix undef
IO::Socket::SSL::SSL_Context 2.084
IO::Socket::SSL::SSL_HANDLE 2.084
IO::Socket::SSL::Session_Cache 2.084
IO::Socket::SSL::Trace 2.084
IO::Socket::SSL::Utils 2.015
requirements:
ExtUtils::MakeMaker 0
Mozilla::CA 0
Net::SSLeay 1.46
Scalar::Util 0
IO-Socket-Socks-0.74
pathname: O/OL/OLEG/IO-Socket-Socks-0.74.tar.gz
provides:
IO::Socket::Socks 0.74
IO::Socket::Socks::Debug 0.74
IO::Socket::Socks::Error 0.74
IO::Socket::Socks::ReadOnlyVar 0.74
IO::Socket::Socks::SocketClassVar 0.74
requirements:
ExtUtils::MakeMaker 6.52
IO::Select 0
Socket 1.94
Test::More 0.88
constant 1.03
ISO-639_1-0.04
pathname: L/LD/LDIDRY/ISO-639_1-0.04.tar.gz
provides:
ISO::639_1 0.04
requirements:
Module::Build::Tiny 0.035
perl 5.008001
JSON-4.10
pathname: I/IS/ISHIGAKI/JSON-4.10.tar.gz
provides:
JSON 4.10
JSON::Backend::PP 4.10
requirements:
ExtUtils::MakeMaker 0
Test::More 0
LWP-MediaTypes-6.04
pathname: O/OA/OALDERS/LWP-MediaTypes-6.04.tar.gz
provides:
LWP::MediaTypes 6.04
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 0
File::Basename 0
Scalar::Util 0
perl 5.006002
strict 0
Locale-Maketext-Lexicon-1.00
pathname: D/DR/DRTECH/Locale-Maketext-Lexicon-1.00.tar.gz
provides:
Locale::Maketext::Extract 1.00
Locale::Maketext::Extract::Plugin::Base 1.00
Locale::Maketext::Extract::Plugin::FormFu 1.00
Locale::Maketext::Extract::Plugin::FormFu::Extractor 1.00
Locale::Maketext::Extract::Plugin::Generic 1.00
Locale::Maketext::Extract::Plugin::Haml 1.00
Locale::Maketext::Extract::Plugin::Mason 1.00
Locale::Maketext::Extract::Plugin::PPI 1.00
Locale::Maketext::Extract::Plugin::Perl 1.00
Locale::Maketext::Extract::Plugin::TT2 1.00
Locale::Maketext::Extract::Plugin::TT2::Directive 1.00
Locale::Maketext::Extract::Plugin::TT2::Parser 1.00
Locale::Maketext::Extract::Plugin::TextTemplate 1.00
Locale::Maketext::Extract::Plugin::TextTemplate::Parser 1.00
Locale::Maketext::Extract::Plugin::YAML 1.00
Locale::Maketext::Extract::Plugin::YAML::Extractor 1.00
Locale::Maketext::Extract::Run 1.00
Locale::Maketext::Lexicon 1.00
Locale::Maketext::Lexicon::Auto 1.00
Locale::Maketext::Lexicon::Gettext 1.00
Locale::Maketext::Lexicon::Msgcat 1.00
Locale::Maketext::Lexicon::Tie 1.00
requirements:
ExtUtils::MakeMaker 6.30
Locale::Maketext 1.17
Log-Minimal-0.19
pathname: K/KA/KAZEBURO/Log-Minimal-0.19.tar.gz
provides:
Log::Minimal 0.19
requirements:
CPAN::Meta 0
CPAN::Meta::Prereqs 0
Data::Dumper 0
Module::Build 0.38
Scalar::Util 0
Term::ANSIColor 0
MIME-Charset-1.013.1
pathname: N/NE/NEZUMI/MIME-Charset-1.013.1.tar.gz
provides:
MIME::Charset v1.13.1
requirements:
CPAN 0
Encode 1.98
ExtUtils::MakeMaker 6.42
Test::More 0
perl 5.005
MIME-EncWords-1.014.3
pathname: N/NE/NEZUMI/MIME-EncWords-1.014.3.tar.gz
provides:
Encode::MIME::EncWords 0.03
MIME::EncWords 1.014003
requirements:
Encode 1.98
ExtUtils::MakeMaker 0
MIME::Base64 2.13
MIME::Charset v1.10.1
Test::More 0
MIME-Lite-3.033
pathname: R/RJ/RJBS/MIME-Lite-3.033.tar.gz
provides:
MIME::Lite 3.033
MIME::Lite::IO_Handle 3.033
MIME::Lite::IO_Scalar 3.033
MIME::Lite::IO_ScalarArray 3.033
MIME::Lite::SMTP 3.033
MailTool undef
requirements:
Email::Date::Format 1.000
ExtUtils::MakeMaker 0
File::Basename 0
File::Spec 0
MIME::Base64 0
MIME::QuotedPrint 0
MIME::Types 1.28
Mail::Address 1.62
MIME-Types-2.24
pathname: M/MA/MARKOV/MIME-Types-2.24.tar.gz
provides:
MIME::Type 2.24
MIME::Types 2.24
MojoX::MIME::Types 2.24
requirements:
ExtUtils::MakeMaker 0
File::Basename 0
File::Spec 0
List::Util 0
Test::More 0.47
MRO-Compat-0.15
pathname: H/HA/HAARG/MRO-Compat-0.15.tar.gz
provides:
MRO::Compat 0.15
requirements:
ExtUtils::MakeMaker 0
perl 5.006
MailTools-2.21
pathname: M/MA/MARKOV/MailTools-2.21.tar.gz
provides:
Mail::Address 2.21
Mail::Cap 2.21
Mail::Field 2.21
Mail::Field::AddrList 2.21
Mail::Field::Date 2.21
Mail::Field::Generic 2.21
Mail::Filter 2.21
Mail::Header 2.21
Mail::Internet 2.21
Mail::Mailer 2.21
Mail::Mailer::qmail 2.21
Mail::Mailer::rfc822 2.21
Mail::Mailer::sendmail 2.21
Mail::Mailer::smtp 2.21
Mail::Mailer::smtp::pipe 2.21
Mail::Mailer::smtps 2.21
Mail::Mailer::smtps::pipe 2.21
Mail::Mailer::testfile 2.21
Mail::Mailer::testfile::pipe 2.21
Mail::Send 2.21
Mail::Util 2.21
MailTools 2.21
requirements:
Date::Format 0
Date::Parse 0
ExtUtils::MakeMaker 0
IO::Handle 0
Net::Domain 1.05
Net::SMTP 1.03
Test::More 0
Module-Build-0.4234
pathname: L/LE/LEONT/Module-Build-0.4234.tar.gz
provides:
Module::Build 0.4234
Module::Build::Base 0.4234
Module::Build::Compat 0.4234
Module::Build::Config 0.4234
Module::Build::Cookbook 0.4234
Module::Build::Dumper 0.4234
Module::Build::Notes 0.4234
Module::Build::PPMMaker 0.4234
Module::Build::Platform::Default 0.4234
Module::Build::Platform::MacOS 0.4234
Module::Build::Platform::Unix 0.4234
Module::Build::Platform::VMS 0.4234
Module::Build::Platform::VOS 0.4234
Module::Build::Platform::Windows 0.4234
Module::Build::Platform::aix 0.4234
Module::Build::Platform::cygwin 0.4234
Module::Build::Platform::darwin 0.4234
Module::Build::Platform::os2 0.4234
Module::Build::PodParser 0.4234
requirements:
CPAN::Meta 2.142060
Cwd 0
Data::Dumper 0
ExtUtils::CBuilder 0.27
ExtUtils::Install 0
ExtUtils::Manifest 0
ExtUtils::Mkbootstrap 0
ExtUtils::ParseXS 2.21
File::Basename 0
File::Compare 0
File::Copy 0
File::Find 0
File::Path 0
File::Spec 0.82
Getopt::Long 0
Module::Metadata 1.000002
Perl::OSType 1
TAP::Harness 3.29
Text::Abbrev 0
Text::ParseWords 0
perl 5.006001
version 0.87
Module-Build-Tiny-0.047
pathname: L/LE/LEONT/Module-Build-Tiny-0.047.tar.gz
provides:
Module::Build::Tiny 0.047
requirements:
CPAN::Meta 0
DynaLoader 0
Exporter 5.57
ExtUtils::CBuilder 0
ExtUtils::Config 0.003
ExtUtils::Helpers 0.020
ExtUtils::Install 0
ExtUtils::InstallPaths 0.002
ExtUtils::ParseXS 0
File::Basename 0
File::Find 0
File::Path 0
File::Spec::Functions 0
Getopt::Long 2.36
JSON::PP 2
Pod::Man 0
TAP::Harness::Env 0
perl 5.006
strict 0
warnings 0
Module-Build-XSUtil-0.19
pathname: H/HI/HIDEAKIO/Module-Build-XSUtil-0.19.tar.gz
provides:
Module::Build::XSUtil 0.19
requirements:
Devel::CheckCompiler 0
Devel::PPPort 0
Exporter 0
ExtUtils::CBuilder 0
File::Basename 0
File::Path 0
Module::Build 0.4005
XSLoader 0
parent 0
perl 5.008001
Module-Implementation-0.09
pathname: D/DR/DROLSKY/Module-Implementation-0.09.tar.gz
provides:
Module::Implementation 0.09
requirements:
Carp 0
ExtUtils::MakeMaker 0
Module::Runtime 0.012
Try::Tiny 0
strict 0
warnings 0
Module-Install-1.21
pathname: E/ET/ETHER/Module-Install-1.21.tar.gz
provides:
Module::AutoInstall 1.21
Module::Install 1.21
Module::Install::Admin 1.21
Module::Install::Admin::Bundle 1.21
Module::Install::Admin::Compiler 1.21
Module::Install::Admin::Find 1.21
Module::Install::Admin::Include 1.21
Module::Install::Admin::Makefile 1.21
Module::Install::Admin::Manifest 1.21
Module::Install::Admin::Metadata 1.21
Module::Install::Admin::ScanDeps 1.21
Module::Install::Admin::WriteAll 1.21
Module::Install::AutoInstall 1.21
Module::Install::Base 1.21
Module::Install::Base::FakeAdmin 1.21
Module::Install::Bundle 1.21
Module::Install::Can 1.21
Module::Install::Compiler 1.21
Module::Install::Deprecated 1.21
Module::Install::External 1.21
Module::Install::Fetch 1.21
Module::Install::Include 1.21
Module::Install::Inline 1.21
Module::Install::MakeMaker 1.21
Module::Install::Makefile 1.21
Module::Install::Metadata 1.21
Module::Install::PAR 1.21
Module::Install::Run 1.21
Module::Install::Scripts 1.21
Module::Install::Share 1.21
Module::Install::Win32 1.21
Module::Install::With 1.21
Module::Install::WriteAll 1.21
inc::Module::Install 1.21
requirements:
Devel::PPPort 3.16
ExtUtils::Install 1.52
ExtUtils::MakeMaker 6.59
ExtUtils::ParseXS 2.19
File::Path 0
File::Remove 1.42
File::Spec 3.28
Module::Build 0.29
Module::CoreList 2.17
Module::ScanDeps 1.09
Parse::CPAN::Meta 1.4413
Test::Harness 3.13
Test::More 0.86
YAML::Tiny 1.38
autodie 0
perl 5.006
Module-Runtime-0.016
pathname: Z/ZE/ZEFRAM/Module-Runtime-0.016.tar.gz
provides:
Module::Runtime 0.016
requirements:
Module::Build 0
Test::More 0.41
perl 5.006
strict 0
warnings 0
Module-ScanDeps-1.35
pathname: R/RS/RSCHUPP/Module-ScanDeps-1.35.tar.gz
provides:
Module::ScanDeps 1.35
requirements:
ExtUtils::MakeMaker 0
File::Spec 0
File::Temp 0
Getopt::Long 0
List::Util 1.33
Module::Metadata 0
Text::ParseWords 0
perl 5.008009
version 0
Mojo-Pg-4.27
pathname: S/SR/SRI/Mojo-Pg-4.27.tar.gz
provides:
Mojo::Pg 4.27
Mojo::Pg::Database undef
Mojo::Pg::Migrations undef
Mojo::Pg::PubSub undef
Mojo::Pg::Results undef
Mojo::Pg::Transaction undef
requirements:
DBD::Pg 3.007004
ExtUtils::MakeMaker 0
Mojolicious 8.50
SQL::Abstract::Pg 1.0
perl 5.016
Mojo-SQLite-3.009
pathname: D/DB/DBOOK/Mojo-SQLite-3.009.tar.gz
provides:
Mojo::SQLite 3.009
Mojo::SQLite::Database 3.009
Mojo::SQLite::Migrations 3.009
Mojo::SQLite::PubSub 3.009
Mojo::SQLite::Results 3.009
Mojo::SQLite::Transaction 3.009
requirements:
Carp 0
DBD::SQLite 1.68
DBI 1.627
File::Spec::Functions 0
File::Temp 0
Module::Build::Tiny 0.034
Mojolicious 8.03
SQL::Abstract::Pg 1.0
Scalar::Util 0
URI 1.69
URI::db 0.15
URI::file 4.21
perl 5.010001
Mojo-mysql-1.27
pathname: J/JH/JHTHORSEN/Mojo-mysql-1.27.tar.gz
provides:
Mojo::mysql 1.27
Mojo::mysql::Database undef
Mojo::mysql::Migrations undef
Mojo::mysql::PubSub undef
Mojo::mysql::Results undef
Mojo::mysql::Transaction undef
SQL::Abstract::mysql undef
requirements:
DBD::MariaDB 1.21
DBI 1.643
ExtUtils::MakeMaker 0
Mojolicious 8.03
SQL::Abstract 1.86
perl 5.016
Mojolicious-9.35
pathname: S/SR/SRI/Mojolicious-9.35.tar.gz
provides:
Mojo undef
Mojo::Asset undef
Mojo::Asset::File undef
Mojo::Asset::Memory undef
Mojo::Base undef
Mojo::ByteStream undef
Mojo::Cache undef
Mojo::Collection undef
Mojo::Content undef
Mojo::Content::MultiPart undef
Mojo::Content::Single undef
Mojo::Cookie undef
Mojo::Cookie::Request undef
Mojo::Cookie::Response undef
Mojo::DOM undef
Mojo::DOM::CSS undef
Mojo::DOM::HTML undef
Mojo::Date undef
Mojo::DynamicMethods undef
Mojo::EventEmitter undef
Mojo::Exception undef
Mojo::File undef
Mojo::Headers undef
Mojo::HelloWorld undef
Mojo::Home undef
Mojo::IOLoop undef
Mojo::IOLoop::Client undef
Mojo::IOLoop::Server undef
Mojo::IOLoop::Stream undef
Mojo::IOLoop::Subprocess undef
Mojo::IOLoop::TLS undef
Mojo::JSON undef
Mojo::JSON::Pointer undef
Mojo::Loader undef
Mojo::Log undef
Mojo::Message undef
Mojo::Message::Request undef
Mojo::Message::Response undef
Mojo::Parameters undef
Mojo::Path undef
Mojo::Promise undef
Mojo::Reactor undef
Mojo::Reactor::EV undef
Mojo::Reactor::Poll undef
Mojo::Server undef
Mojo::Server::CGI undef
Mojo::Server::Daemon undef
Mojo::Server::Hypnotoad undef
Mojo::Server::Morbo undef
Mojo::Server::Morbo::Backend undef
Mojo::Server::Morbo::Backend::Poll undef
Mojo::Server::PSGI undef
Mojo::Server::Prefork undef
Mojo::Template undef
Mojo::Transaction undef
Mojo::Transaction::HTTP undef
Mojo::Transaction::WebSocket undef
Mojo::URL undef
Mojo::Upload undef
Mojo::UserAgent undef
Mojo::UserAgent::CookieJar undef
Mojo::UserAgent::Proxy undef
Mojo::UserAgent::Server undef
Mojo::UserAgent::Transactor undef
Mojo::Util undef
Mojo::WebSocket undef
Mojolicious 9.35
Mojolicious::Command undef
Mojolicious::Command::Author::cpanify undef
Mojolicious::Command::Author::generate undef
Mojolicious::Command::Author::generate::app undef
Mojolicious::Command::Author::generate::dockerfile undef
Mojolicious::Command::Author::generate::lite_app undef
Mojolicious::Command::Author::generate::makefile undef
Mojolicious::Command::Author::generate::plugin undef
Mojolicious::Command::Author::inflate undef
Mojolicious::Command::cgi undef
Mojolicious::Command::daemon undef
Mojolicious::Command::eval undef
Mojolicious::Command::get undef
Mojolicious::Command::prefork undef
Mojolicious::Command::psgi undef
Mojolicious::Command::routes undef
Mojolicious::Command::version undef
Mojolicious::Commands undef
Mojolicious::Controller undef
Mojolicious::Lite undef
Mojolicious::Plugin undef
Mojolicious::Plugin::Config undef
Mojolicious::Plugin::DefaultHelpers undef
Mojolicious::Plugin::EPLRenderer undef
Mojolicious::Plugin::EPRenderer undef
Mojolicious::Plugin::HeaderCondition undef
Mojolicious::Plugin::JSONConfig undef
Mojolicious::Plugin::Mount undef
Mojolicious::Plugin::NotYAMLConfig undef
Mojolicious::Plugin::TagHelpers undef
Mojolicious::Plugins undef
Mojolicious::Renderer undef
Mojolicious::Routes undef
Mojolicious::Routes::Match undef
Mojolicious::Routes::Pattern undef
Mojolicious::Routes::Route undef
Mojolicious::Sessions undef
Mojolicious::Static undef
Mojolicious::Types undef
Mojolicious::Validator undef
Mojolicious::Validator::Validation undef
Test::Mojo undef
ojo undef
requirements:
ExtUtils::MakeMaker 0
IO::Socket::IP 0.37
Sub::Util 1.41
perl 5.016
Mojolicious-Plugin-Authentication-1.39
pathname: J/JJ/JJATRIA/Mojolicious-Plugin-Authentication-1.39.tar.gz
provides:
Mojolicious::Plugin::Authentication 1.39
requirements:
Exporter 0
ExtUtils::MakeMaker 0
Mojolicious 8.0
perl 5.016
Mojolicious-Plugin-CSPHeader-0.06
pathname: L/LD/LDIDRY/Mojolicious-Plugin-CSPHeader-0.06.tar.gz
provides:
Mojolicious::Plugin::CSPHeader 0.06
requirements:
ExtUtils::MakeMaker 0
Mojolicious 7.75
Mojolicious-Plugin-DebugDumperHelper-0.03
pathname: L/LD/LDIDRY/Mojolicious-Plugin-DebugDumperHelper-0.03.tar.gz
provides:
Mojolicious::Plugin::DebugDumperHelper 0.03
requirements:
ExtUtils::MakeMaker 0
Mojolicious 6.11
Mojolicious-Plugin-GzipStatic-0.04
pathname: L/LD/LDIDRY/Mojolicious-Plugin-GzipStatic-0.04.tar.gz
provides:
Mojolicious::Plugin::GzipStatic 0.04
requirements:
ExtUtils::MakeMaker 0
IO::Compress::Gzip 0
Mojolicious 7.75
Mojolicious-Plugin-I18N-1.6
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.6.tar.gz
provides:
Mojolicious::Plugin::I18N 1.6
requirements:
I18N::LangTags 0.35
Module::Build 0.42
Mojolicious 5
Test::More 0
perl 5.010001
Mojolicious-Plugin-Mail-1.5
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-Mail-1.5.tar.gz
provides:
Mojolicious::Plugin::Mail 1.5
requirements:
Encode 0
MIME::EncWords 0
MIME::Lite 3.027
Mojolicious 5
Test::More 0
perl 5.001001
Mojolicious-Plugin-PgURLHelper-0.03
pathname: L/LD/LDIDRY/Mojolicious-Plugin-PgURLHelper-0.03.tar.gz
provides:
Mojolicious::Plugin::PgURLHelper 0.03
requirements:
ExtUtils::MakeMaker 0
Mojolicious 7.23
Mojolicious-Plugin-StaticCache-0.02
pathname: L/LD/LDIDRY/Mojolicious-Plugin-StaticCache-0.02.tar.gz
provides:
Mojolicious::Plugin::StaticCache 0.02
requirements:
ExtUtils::MakeMaker 0
Mojolicious 7.33
Moo-2.005005
pathname: H/HA/HAARG/Moo-2.005005.tar.gz
provides:
Method::Generate::Accessor undef
Method::Generate::BuildAll undef
Method::Generate::Constructor undef
Method::Generate::DemolishAll undef
Moo 2.005005
Moo::HandleMoose undef
Moo::HandleMoose::FakeConstructor undef
Moo::HandleMoose::FakeMetaClass undef
Moo::HandleMoose::_TypeMap undef
Moo::Object undef
Moo::Role 2.005005
Moo::_Utils undef
Moo::sification undef
oo undef
requirements:
Carp 0
Class::Method::Modifiers 1.10
Exporter 0
ExtUtils::MakeMaker 0
Role::Tiny 2.002003
Scalar::Util 1.00
Sub::Defer 2.006006
Sub::Quote 2.006006
perl 5.006
Mouse-v2.5.10
pathname: S/SK/SKAJI/Mouse-v2.5.10.tar.gz
provides:
Mouse v2.5.10
Mouse::Exporter undef
Mouse::Meta::Attribute undef
Mouse::Meta::Class undef
Mouse::Meta::Method undef
Mouse::Meta::Method::Accessor undef
Mouse::Meta::Method::Constructor undef
Mouse::Meta::Method::Delegation undef
Mouse::Meta::Method::Destructor undef
Mouse::Meta::Module undef
Mouse::Meta::Role undef
Mouse::Meta::Role::Application undef
Mouse::Meta::Role::Application::RoleSummation undef
Mouse::Meta::Role::Composite undef
Mouse::Meta::Role::Method undef
Mouse::Meta::TypeConstraint undef
Mouse::Object undef
Mouse::PurePerl undef
Mouse::Role v2.5.10
Mouse::Spec v2.5.10
Mouse::TypeRegistry undef
Mouse::Util v2.5.10
Mouse::Util::MetaRole undef
Mouse::Util::TypeConstraints undef
Squirrel undef
Squirrel::Role undef
Test::Mouse undef
ouse undef
requirements:
ExtUtils::CBuilder 0
Module::Build 0.4005
Module::Build::XSUtil 0.19
Scalar::Util 1.14
XSLoader 0.02
perl 5.008005
Mozilla-CA-20231213
pathname: L/LW/LWP/Mozilla-CA-20231213.tar.gz
provides:
Mozilla::CA 20231213
requirements:
ExtUtils::MakeMaker 0
Net-DNS-1.41
pathname: N/NL/NLNETLABS/Net-DNS-1.41.tar.gz
provides:
Net::DNS 1.41
Net::DNS::Domain 1913
Net::DNS::DomainName 1898
Net::DNS::DomainName1035 1898
Net::DNS::DomainName2535 1898
Net::DNS::Header 1910
Net::DNS::Mailbox 1910
Net::DNS::Mailbox1035 1910
Net::DNS::Mailbox2535 1910
Net::DNS::Nameserver 1949
Net::DNS::Packet 1947
Net::DNS::Parameters 1945
Net::DNS::Question 1895
Net::DNS::RR 1910
Net::DNS::RR::A 1896
Net::DNS::RR::AAAA 1896
Net::DNS::RR::AFSDB 1945
Net::DNS::RR::AMTRELAY 1896
Net::DNS::RR::APL 1896
Net::DNS::RR::APL::Item 1896
Net::DNS::RR::CAA 1910
Net::DNS::RR::CDNSKEY 1909
Net::DNS::RR::CDS 1909
Net::DNS::RR::CERT 1896
Net::DNS::RR::CNAME 1896
Net::DNS::RR::CSYNC 1910
Net::DNS::RR::DHCID 1896
Net::DNS::RR::DNAME 1896
Net::DNS::RR::DNSKEY 1910
Net::DNS::RR::DS 1909
Net::DNS::RR::EUI48 1896
Net::DNS::RR::EUI64 1896
Net::DNS::RR::GPOS 1910
Net::DNS::RR::HINFO 1896
Net::DNS::RR::HIP 1896
Net::DNS::RR::HTTPS 1945
Net::DNS::RR::IPSECKEY 1909
Net::DNS::RR::ISDN 1896
Net::DNS::RR::KEY 1896
Net::DNS::RR::KX 1945
Net::DNS::RR::L32 1896
Net::DNS::RR::L64 1896
Net::DNS::RR::LOC 1896
Net::DNS::RR::LP 1896
Net::DNS::RR::MB 1910
Net::DNS::RR::MG 1910
Net::DNS::RR::MINFO 1896
Net::DNS::RR::MR 1910
Net::DNS::RR::MX 1945
Net::DNS::RR::NAPTR 1898
Net::DNS::RR::NID 1896
Net::DNS::RR::NS 1896
Net::DNS::RR::NSEC 1945
Net::DNS::RR::NSEC3 1910
Net::DNS::RR::NSEC3PARAM 1896
Net::DNS::RR::NULL 1896
Net::DNS::RR::OPENPGPKEY 1896
Net::DNS::RR::OPT 1934
Net::DNS::RR::OPT::CHAIN 1934
Net::DNS::RR::OPT::CLIENT_SUBNET 1934
Net::DNS::RR::OPT::COOKIE 1934
Net::DNS::RR::OPT::DAU 1934
Net::DNS::RR::OPT::DHU 1934
Net::DNS::RR::OPT::EXPIRE 1934
Net::DNS::RR::OPT::EXTENDED_ERROR 1934
Net::DNS::RR::OPT::KEY_TAG 1934
Net::DNS::RR::OPT::N3U 1934
Net::DNS::RR::OPT::NSID 1934
Net::DNS::RR::OPT::PADDING 1934
Net::DNS::RR::OPT::REPORT_CHANNEL 1934
Net::DNS::RR::OPT::TCP_KEEPALIVE 1934
Net::DNS::RR::PTR 1896
Net::DNS::RR::PX 1945
Net::DNS::RR::RP 1945
Net::DNS::RR::RRSIG 1896
Net::DNS::RR::RT 1945
Net::DNS::RR::SIG 1908
Net::DNS::RR::SMIMEA 1896
Net::DNS::RR::SOA 1945
Net::DNS::RR::SPF 1896
Net::DNS::RR::SRV 1945
Net::DNS::RR::SSHFP 1896
Net::DNS::RR::SVCB 1945
Net::DNS::RR::TKEY 1908
Net::DNS::RR::TLSA 1896
Net::DNS::RR::TSIG 1909
Net::DNS::RR::TXT 1911
Net::DNS::RR::URI 1896
Net::DNS::RR::X25 1896
Net::DNS::RR::ZONEMD 1896
Net::DNS::Resolver 1895
Net::DNS::Resolver::Base 1947
Net::DNS::Resolver::MSWin32 1856
Net::DNS::Resolver::Recurse 1930
Net::DNS::Resolver::UNIX 1856
Net::DNS::Resolver::android 1856
Net::DNS::Resolver::cygwin 1856
Net::DNS::Resolver::os2 1856
Net::DNS::Resolver::os390 1856
Net::DNS::Text 1894
Net::DNS::Update 1895
Net::DNS::ZoneFile 1910
Net::DNS::ZoneFile::Generator 1910
Net::DNS::ZoneFile::Text 1910
requirements:
Carp 1.1
Digest::HMAC 1.03
Digest::MD5 2.13
Digest::SHA 5.23
Encode 2.26
Exporter 5.63
ExtUtils::MakeMaker 6.48
File::Spec 3.29
Getopt::Long 2.43
IO::File 1.14
IO::Select 1.17
IO::Socket 1.3
IO::Socket::IP 0.38
MIME::Base64 2.13
PerlIO 1.05
Scalar::Util 1.19
Time::Local 1.19
perl 5.008009
Net-Domain-TLD-1.75
pathname: A/AL/ALEXP/Net-Domain-TLD-1.75.tar.gz
provides:
Net::Domain::TLD 1.75
requirements:
Carp 0
ExtUtils::MakeMaker 0
Storable 0
Net-HTTP-6.23
pathname: O/OA/OALDERS/Net-HTTP-6.23.tar.gz
provides:
Net::HTTP 6.23
Net::HTTP::Methods 6.23
Net::HTTP::NB 6.23
Net::HTTPS 6.23
requirements:
Carp 0
Compress::Raw::Zlib 0
ExtUtils::MakeMaker 0
IO::Socket::INET 0
IO::Uncompress::Gunzip 0
URI 0
base 0
perl 5.006002
strict 0
warnings 0
Net-OpenStack-Swift-0.15
pathname: M/MA/MASAKYST/Net-OpenStack-Swift-0.15.tar.gz
provides:
Net::OpenStack::Swift 0.15
Net::OpenStack::Swift::InnerKeystone::Base undef
Net::OpenStack::Swift::InnerKeystone::V1_0 undef
Net::OpenStack::Swift::InnerKeystone::V2_0 undef
Net::OpenStack::Swift::InnerKeystone::V3_0 undef
Net::OpenStack::Swift::Util undef
requirements:
App::Rad 0
Data::Validator 0
Furl 0
IO::Socket::SSL 0
JSON 0
Log::Minimal 0
Module::Build::Tiny 0.035
Mouse 0
Parallel::Fork::BossWorkerAsync 0
Path::Tiny 0
Sys::CPU 0
Text::ASCIITable 0
URI::Escape 0
namespace::clean 0
perl 5.010_001
Net-SSLeay-1.92
pathname: C/CH/CHRISN/Net-SSLeay-1.92.tar.gz
provides:
Net::SSLeay 1.92
Net::SSLeay::Handle 1.92
requirements:
English 0
ExtUtils::MakeMaker 0
File::Spec::Functions 0
MIME::Base64 0
Text::Wrap 0
constant 0
perl 5.008001
NetAddr-IP-4.079
pathname: M/MI/MIKER/NetAddr-IP-4.079.tar.gz
provides:
NetAddr::IP 4.079
NetAddr::IP::InetBase 0.08
NetAddr::IP::Lite 1.57
NetAddr::IP::Util 1.53
NetAddr::IP::UtilPP 1.09
NetAddr::IP::UtilPolluted 1.53
NetAddr::IP::Util_IS 1
requirements:
ExtUtils::MakeMaker 0
Test::More 0
Number-Bytes-Human-0.11
pathname: F/FE/FERREIRA/Number-Bytes-Human-0.11.tar.gz
provides:
Number::Bytes::Human 0.11
requirements:
Carp 0
ExtUtils::MakeMaker 0
POSIX 0
Test::More 0
Package-Stash-0.40
pathname: E/ET/ETHER/Package-Stash-0.40.tar.gz
provides:
Package::Stash 0.40
Package::Stash::PP 0.40
requirements:
B 0
Carp 0
Dist::CheckConflicts 0.02
ExtUtils::MakeMaker 0
Getopt::Long 0
Module::Implementation 0.06
Package::Stash::XS 0.26
Scalar::Util 0
Symbol 0
Text::ParseWords 0
constant 0
perl 5.008001
strict 0
warnings 0
Package-Stash-XS-0.30
pathname: E/ET/ETHER/Package-Stash-XS-0.30.tar.gz
provides:
Package::Stash::XS 0.30
requirements:
ExtUtils::MakeMaker 0
XSLoader 0
perl 5.008001
strict 0
warnings 0
Parallel-Fork-BossWorkerAsync-0.09
pathname: J/JV/JVANNUCCI/Parallel-Fork-BossWorkerAsync-0.09.tar.gz
provides:
Parallel::Fork::BossWorkerAsync 0.09
requirements:
ExtUtils::MakeMaker 0
Params-Classify-0.015
pathname: Z/ZE/ZEFRAM/Params-Classify-0.015.tar.gz
provides:
Params::Classify 0.015
requirements:
Exporter 0
Module::Build 0
Scalar::Util 1.01
Test::More 0
parent 0
perl 5.006001
strict 0
warnings 0
Path-Tiny-0.144
pathname: D/DA/DAGOLDEN/Path-Tiny-0.144.tar.gz
provides:
Path::Tiny 0.144
Path::Tiny::Error 0.144
requirements:
Carp 0
Cwd 0
Digest 1.03
Digest::SHA 5.45
Encode 0
Exporter 5.57
ExtUtils::MakeMaker 6.17
Fcntl 0
File::Compare 0
File::Copy 0
File::Glob 0
File::Path 2.07
File::Spec 0.86
File::Temp 0.19
File::stat 0
constant 0
overload 0
perl 5.008001
strict 0
warnings 0
warnings::register 0
Role-Tiny-2.002004
pathname: H/HA/HAARG/Role-Tiny-2.002004.tar.gz
provides:
Role::Tiny 2.002004
Role::Tiny::With 2.002004
requirements:
Exporter 5.57
perl 5.006
SQL-Abstract-2.000001
pathname: M/MS/MSTROUT/SQL-Abstract-2.000001.tar.gz
provides:
Chunkstrumenter undef
DBIx::Class::SQLMaker::Role::SQLA2Passthrough undef
SQL::Abstract 2.000001
SQL::Abstract::Formatter undef
SQL::Abstract::Parts undef
SQL::Abstract::Plugin::BangOverrides undef
SQL::Abstract::Plugin::ExtraClauses undef
SQL::Abstract::Reference undef
SQL::Abstract::Role::Plugin undef
SQL::Abstract::Test undef
SQL::Abstract::Tree undef
requirements:
Exporter 5.57
ExtUtils::MakeMaker 0
Hash::Merge 0.12
List::Util 0
MRO::Compat 0.12
Moo 2.000001
Scalar::Util 0
Sub::Quote 2.000001
Test::Builder::Module 0.84
Test::Deep 0.101
Text::Balanced 2.00
perl 5.006
SQL-Abstract-Pg-1.0
pathname: S/SR/SRI/SQL-Abstract-Pg-1.0.tar.gz
provides:
SQL::Abstract::Pg 1.0
requirements:
ExtUtils::MakeMaker 0
SQL::Abstract 2.0
perl 5.016
Sub-Exporter-Progressive-0.001013
pathname: F/FR/FREW/Sub-Exporter-Progressive-0.001013.tar.gz
provides:
Sub::Exporter::Progressive 0.001013
requirements:
ExtUtils::MakeMaker 0
Sub-Quote-2.006008
pathname: H/HA/HAARG/Sub-Quote-2.006008.tar.gz
provides:
Sub::Defer 2.006008
Sub::Quote 2.006008
requirements:
ExtUtils::MakeMaker 0
Scalar::Util 0
perl 5.006
Sub-Uplevel-0.2800
pathname: D/DA/DAGOLDEN/Sub-Uplevel-0.2800.tar.gz
provides:
Sub::Uplevel 0.2800
requirements:
Carp 0
ExtUtils::MakeMaker 6.17
constant 0
perl 5.006
strict 0
warnings 0
Switch-2.17
pathname: C/CH/CHORNY/Switch-2.17.tar.gz
provides:
Switch 2.17
requirements:
ExtUtils::MakeMaker 0
Filter::Util::Call 0
Text::Balanced 2
if 0
perl 5.005
Sys-CPU-0.52
pathname: M/MK/MKODERER/Sys-CPU-0.52.tar.gz
provides:
Sys::CPU 0.52
requirements:
ExtUtils::MakeMaker 0
Term-ProgressBar-2.23
pathname: M/MA/MANWAR/Term-ProgressBar-2.23.tar.gz
provides:
Term::ProgressBar 2.23
Term::ProgressBar::IO 2.23
requirements:
Capture::Tiny 0.13
Carp 0
Class::MethodMaker 1.02
ExtUtils::MakeMaker 0
Fatal 0
File::Temp 0
POSIX 0
Term::ReadKey 2.14
Test::Exception 0.31
Test::More 0.80
Test::Warnings 0
perl 5.006
TermReadKey-2.38
pathname: J/JS/JSTOWE/TermReadKey-2.38.tar.gz
provides:
Term::ReadKey 2.38
requirements:
ExtUtils::MakeMaker 6.58
Test-Deep-1.204
pathname: R/RJ/RJBS/Test-Deep-1.204.tar.gz
provides:
Test::Deep 1.204
Test::Deep::All 1.204
Test::Deep::Any 1.204
Test::Deep::Array 1.204
Test::Deep::ArrayEach 1.204
Test::Deep::ArrayElementsOnly 1.204
Test::Deep::ArrayLength 1.204
Test::Deep::ArrayLengthOnly 1.204
Test::Deep::Blessed 1.204
Test::Deep::Boolean 1.204
Test::Deep::Cache 1.204
Test::Deep::Cache::Simple 1.204
Test::Deep::Class 1.204
Test::Deep::Cmp 1.204
Test::Deep::Code 1.204
Test::Deep::Hash 1.204
Test::Deep::HashEach 1.204
Test::Deep::HashElements 1.204
Test::Deep::HashKeys 1.204
Test::Deep::HashKeysOnly 1.204
Test::Deep::Ignore 1.204
Test::Deep::Isa 1.204
Test::Deep::ListMethods 1.204
Test::Deep::MM 1.204
Test::Deep::Methods 1.204
Test::Deep::NoTest 1.204
Test::Deep::None 1.204
Test::Deep::Number 1.204
Test::Deep::Obj 1.204
Test::Deep::Ref 1.204
Test::Deep::RefType 1.204
Test::Deep::Regexp 1.204
Test::Deep::RegexpMatches 1.204
Test::Deep::RegexpOnly 1.204
Test::Deep::RegexpRef 1.204
Test::Deep::RegexpRefOnly 1.204
Test::Deep::RegexpVersion 1.204
Test::Deep::ScalarRef 1.204
Test::Deep::ScalarRefOnly 1.204
Test::Deep::Set 1.204
Test::Deep::Shallow 1.204
Test::Deep::Stack 1.204
Test::Deep::String 1.204
Test::Deep::SubHash 1.204
Test::Deep::SubHashElements 1.204
Test::Deep::SubHashKeys 1.204
Test::Deep::SubHashKeysOnly 1.204
Test::Deep::SuperHash 1.204
Test::Deep::SuperHashElements 1.204
Test::Deep::SuperHashKeys 1.204
Test::Deep::SuperHashKeysOnly 1.204
requirements:
ExtUtils::MakeMaker 6.78
List::Util 1.09
Scalar::Util 1.09
Test::Builder 0
Test::More 0.96
perl 5.012
Test-Exception-0.43
pathname: E/EX/EXODIST/Test-Exception-0.43.tar.gz
provides:
Test::Exception 0.43
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 0
Sub::Uplevel 0.18
Test::Builder 0.7
Test::Builder::Tester 1.07
Test::Harness 2.03
base 0
perl 5.006001
strict 0
warnings 0
Test-Fatal-0.017
pathname: R/RJ/RJBS/Test-Fatal-0.017.tar.gz
provides:
Test::Fatal 0.017
requirements:
Carp 0
Exporter 5.57
ExtUtils::MakeMaker 6.78
Test::Builder 0
Try::Tiny 0.07
strict 0
warnings 0
Test-Warn-0.37
pathname: B/BI/BIGJ/Test-Warn-0.37.tar.gz
provides:
Test::Warn 0.37
requirements:
Carp 1.22
ExtUtils::MakeMaker 0
Sub::Uplevel 0.12
Test::Builder 0.13
Test::Builder::Tester 1.02
perl 5.006
Test-Warnings-0.032
pathname: E/ET/ETHER/Test-Warnings-0.032.tar.gz
provides:
Test::Warnings 0.032
requirements:
Carp 0
Exporter 0
ExtUtils::MakeMaker 0
Test::Builder 0
parent 0
perl 5.006
strict 0
warnings 0
Text-ASCIITable-0.22
pathname: L/LU/LUNATIC/Text-ASCIITable-0.22.tar.gz
provides:
Text::ASCIITable 0.22
Text::ASCIITable::Wrap 0.2
requirements:
Carp 0
Encode 0
List::Util 0
perl v5.6.0
Text-Soundex-3.05
pathname: R/RJ/RJBS/Text-Soundex-3.05.tar.gz
provides:
Text::Soundex 3.05
requirements:
ExtUtils::MakeMaker 0
if 0
TimeDate-2.33
pathname: A/AT/ATOOMIC/TimeDate-2.33.tar.gz
provides:
Date::Format 2.24
Date::Format::Generic 2.24
Date::Language 1.10
Date::Language::Afar 0.99
Date::Language::Amharic 1.00
Date::Language::Austrian 1.01
Date::Language::Brazilian 1.01
Date::Language::Bulgarian 1.01
Date::Language::Chinese 1.00
Date::Language::Chinese_GB 1.01
Date::Language::Czech 1.01
Date::Language::Danish 1.01
Date::Language::Dutch 1.02
Date::Language::English 1.01
Date::Language::Finnish 1.01
Date::Language::French 1.04
Date::Language::Gedeo 0.99
Date::Language::German 1.02
Date::Language::Greek 1.00
Date::Language::Hungarian 1.01
Date::Language::Icelandic 1.01
Date::Language::Italian 1.01
Date::Language::Norwegian 1.01
Date::Language::Occitan 1.04
Date::Language::Oromo 0.99
Date::Language::Romanian 1.01
Date::Language::Russian 1.01
Date::Language::Russian_cp1251 1.01
Date::Language::Russian_koi8r 1.01
Date::Language::Sidama 0.99
Date::Language::Somali 0.99
Date::Language::Spanish 1.00
Date::Language::Swedish 1.01
Date::Language::Tigrinya 1.00
Date::Language::TigrinyaEritrean 1.00
Date::Language::TigrinyaEthiopian 1.00
Date::Language::Turkish 1.0
Date::Parse 2.33
Time::Zone 2.24
TimeDate 1.21
requirements:
ExtUtils::MakeMaker 0
Try-Tiny-0.31
pathname: E/ET/ETHER/Try-Tiny-0.31.tar.gz
provides:
Try::Tiny 0.31
requirements:
Carp 0
Exporter 5.57
ExtUtils::MakeMaker 0
constant 0
perl 5.006
strict 0
warnings 0
URI-5.21
pathname: O/OA/OALDERS/URI-5.21.tar.gz
provides:
URI 5.21
URI::Escape 5.21
URI::Heuristic 5.21
URI::IRI 5.21
URI::QueryParam 5.21
URI::Split 5.21
URI::URL 5.21
URI::WithBase 5.21
URI::data 5.21
URI::file 5.21
URI::file::Base 5.21
URI::file::FAT 5.21
URI::file::Mac 5.21
URI::file::OS2 5.21
URI::file::QNX 5.21
URI::file::Unix 5.21
URI::file::Win32 5.21
URI::ftp 5.21
URI::gopher 5.21
URI::http 5.21
URI::https 5.21
URI::icap 5.21
URI::icaps 5.21
URI::ldap 5.21
URI::ldapi 5.21
URI::ldaps 5.21
URI::mailto 5.21
URI::mms 5.21
URI::news 5.21
URI::nntp 5.21
URI::nntps 5.21
URI::pop 5.21
URI::rlogin 5.21
URI::rsync 5.21
URI::rtsp 5.21
URI::rtspu 5.21
URI::sftp 5.21
URI::sip 5.21
URI::sips 5.21
URI::snews 5.21
URI::ssh 5.21
URI::telnet 5.21
URI::tn3270 5.21
URI::urn 5.21
URI::urn::isbn 5.21
URI::urn::oid 5.21
requirements:
Carp 0
Cwd 0
Data::Dumper 0
Encode 0
Exporter 5.57
ExtUtils::MakeMaker 0
MIME::Base64 2
Net::Domain 0
Scalar::Util 0
constant 0
integer 0
overload 0
parent 0
perl 5.008001
strict 0
utf8 0
warnings 0
URI-Find-20160806
pathname: M/MS/MSCHWERN/URI-Find-20160806.tar.gz
provides:
URI::Find 20160806
URI::Find::Schemeless 20160806
requirements:
Module::Build 0.30
Test::More 0.88
URI 1.60
perl v5.8.8
URI-Nested-0.10
pathname: D/DW/DWHEELER/URI-Nested-0.10.tar.gz
provides:
URI::Nested 0.10
requirements:
Module::Build 0.30
Test::More 0.88
URI 1.40
perl 5.008001
URI-db-0.21
pathname: D/DW/DWHEELER/URI-db-0.21.tar.gz
provides:
URI::cassandra 0.21
URI::cockroach 0.21
URI::cockroachdb 0.21
URI::couch 0.21
URI::couchdb 0.21
URI::cubrid 0.21
URI::db 0.21
URI::db2 0.21
URI::derby 0.21
URI::exasol 0.21
URI::firebird 0.21
URI::hive 0.21
URI::impala 0.21
URI::informix 0.21
URI::ingres 0.21
URI::interbase 0.21
URI::ldapdb 0.21
URI::maria 0.21
URI::mariadb 0.21
URI::max 0.21
URI::maxdb 0.21
URI::monet 0.21
URI::monetdb 0.21
URI::mongo 0.21
URI::mongodb 0.21
URI::mssql 0.21
URI::mysql 0.21
URI::oracle 0.21
URI::pg 0.21
URI::pgsql 0.21
URI::pgxc 0.21
URI::postgres 0.21
URI::postgresql 0.21
URI::postgresxc 0.21
URI::redshift 0.21
URI::snowflake 0.21
URI::sqlite 0.21
URI::sqlite3 0.21
URI::sqlserver 0.21
URI::sybase 0.21
URI::teradata 0.21
URI::unify 0.21
URI::vertica 0.21
URI::yugabyte 0.21
URI::yugabytedb 0.21
requirements:
Module::Build 0.30
Test::More 0.88
URI 1.40
URI::Nested 0.10
perl 5.008001
Variable-Magic-0.63
pathname: V/VP/VPIT/Variable-Magic-0.63.tar.gz
provides:
Variable::Magic 0.63
requirements:
Carp 0
Config 0
Exporter 0
ExtUtils::MakeMaker 0
IO::Handle 0
IO::Select 0
IPC::Open3 0
POSIX 0
Socket 0
Test::More 0
XSLoader 0
base 0
lib 0
perl 5.008
WWW-RobotRules-6.02
pathname: G/GA/GAAS/WWW-RobotRules-6.02.tar.gz
provides:
WWW::RobotRules 6.02
WWW::RobotRules::AnyDBM_File 6.00
WWW::RobotRules::InCore 6.02
requirements:
AnyDBM_File 0
ExtUtils::MakeMaker 0
Fcntl 0
URI 1.10
perl 5.008001
YAML-Tiny-1.74
pathname: E/ET/ETHER/YAML-Tiny-1.74.tar.gz
provides:
YAML::Tiny 1.74
requirements:
B 0
Carp 0
Exporter 0
ExtUtils::MakeMaker 0
Fcntl 0
Scalar::Util 0
perl 5.008001
strict 0
warnings 0
common-sense-3.75
pathname: M/ML/MLEHMANN/common-sense-3.75.tar.gz
provides:
common::sense 3.75
requirements:
ExtUtils::MakeMaker 0
libwww-perl-6.72
pathname: O/OA/OALDERS/libwww-perl-6.72.tar.gz
provides:
LWP 6.72
LWP::Authen::Basic 6.72
LWP::Authen::Digest 6.72
LWP::Authen::Ntlm 6.72
LWP::ConnCache 6.72
LWP::Debug 6.72
LWP::Debug::TraceHTTP 6.72
LWP::DebugFile 6.72
LWP::MemberMixin 6.72
LWP::Protocol 6.72
LWP::Protocol::cpan 6.72
LWP::Protocol::data 6.72
LWP::Protocol::file 6.72
LWP::Protocol::ftp 6.72
LWP::Protocol::gopher 6.72
LWP::Protocol::http 6.72
LWP::Protocol::loopback 6.72
LWP::Protocol::mailto 6.72
LWP::Protocol::nntp 6.72
LWP::Protocol::nogo 6.72
LWP::RobotUA 6.72
LWP::Simple 6.72
LWP::UserAgent 6.72
requirements:
Digest::MD5 0
Encode 2.12
Encode::Locale 0
ExtUtils::MakeMaker 0
File::Copy 0
File::Listing 6
File::Temp 0
Getopt::Long 0
HTML::Entities 0
HTML::HeadParser 3.71
HTTP::CookieJar::LWP 0
HTTP::Cookies 6
HTTP::Date 6
HTTP::Negotiate 6
HTTP::Request 6.18
HTTP::Request::Common 6.18
HTTP::Response 6.18
HTTP::Status 6.18
IO::Select 0
IO::Socket 0
LWP::MediaTypes 6
MIME::Base64 2.1
Module::Load 0
Net::FTP 2.58
Net::HTTP 6.18
Scalar::Util 0
Try::Tiny 0
URI 1.10
URI::Escape 0
WWW::RobotRules 6
parent 0.217
perl 5.008001
strict 0
warnings 0
namespace-clean-0.27
pathname: R/RI/RIBASUSHI/namespace-clean-0.27.tar.gz
provides:
namespace::clean 0.27
requirements:
B::Hooks::EndOfScope 0.12
ExtUtils::MakeMaker 0
Package::Stash 0.23
perl 5.008001
perl-ldap-0.68
pathname: M/MA/MARSCHAP/perl-ldap-0.68.tar.gz
provides:
Bundle::Net::LDAP 0.03
LWP::Protocol::ldap 1.25
LWP::Protocol::ldapi undef
LWP::Protocol::ldaps undef
Net::LDAP 0.68
Net::LDAP::ASN 0.13
Net::LDAP::Bind 1.05
Net::LDAP::Constant 0.24
Net::LDAP::Control 0.20
Net::LDAP::Control::Assertion 0.02
Net::LDAP::Control::DontUseCopy 0.02
Net::LDAP::Control::EntryChange 0.02
Net::LDAP::Control::ManageDsaIT 0.04
Net::LDAP::Control::MatchedValues 0.02
Net::LDAP::Control::NoOp 0.01
Net::LDAP::Control::Paged 0.05
Net::LDAP::Control::PasswordPolicy 0.04
Net::LDAP::Control::PersistentSearch 0.04
Net::LDAP::Control::PostRead 0.03
Net::LDAP::Control::PreRead 0.04
Net::LDAP::Control::ProxyAuth 1.09
Net::LDAP::Control::Relax 0.03
Net::LDAP::Control::Sort 0.04
Net::LDAP::Control::SortResult 0.03
Net::LDAP::Control::Subentries 0.01
Net::LDAP::Control::SyncDone 0.03
Net::LDAP::Control::SyncRequest 0.03
Net::LDAP::Control::SyncState 0.04
Net::LDAP::Control::TreeDelete 0.01
Net::LDAP::Control::VLV 0.07
Net::LDAP::Control::VLVResponse 0.04
Net::LDAP::DSML 0.17
Net::LDAP::DSML::output 0.17
Net::LDAP::DSML::pp 0.17
Net::LDAP::Entry 0.29
Net::LDAP::Extension 1.04
Net::LDAP::Extension::Cancel 0.02
Net::LDAP::Extension::Refresh 0.04
Net::LDAP::Extension::SetPassword 0.06
Net::LDAP::Extension::WhoAmI 0.02
Net::LDAP::Extra 0.02
Net::LDAP::Extra::AD 0.05
Net::LDAP::Extra::eDirectory 0.03
Net::LDAP::Filter 0.20
Net::LDAP::FilterList 0.02
Net::LDAP::FilterMatch 0.27
Net::LDAP::Intermediate 0.04
Net::LDAP::Intermediate::SyncInfo 0.03
Net::LDAP::LDIF 0.27
Net::LDAP::Message 1.12
Net::LDAP::Message::Dummy 1.12
Net::LDAP::Reference 0.14
Net::LDAP::RootDSE 0.02
Net::LDAP::Schema 0.9908
Net::LDAP::Search 0.14
Net::LDAP::Util 0.20
Net::LDAPI 0.04
Net::LDAPS 0.06
requirements:
Authen::SASL 2.00
Convert::ASN1 0.2
Digest::MD5 0
ExtUtils::MakeMaker 6.59
File::Basename 0
File::Compare 0
File::Path 0
HTTP::Negotiate 0
HTTP::Response 0
HTTP::Status 0
IO::File 0
IO::Socket::SSL 1.26
JSON 0
LWP 0
LWP::MediaTypes 0
LWP::Protocol 0
MIME::Base64 0
Test::More 0
Text::Soundex 0
Time::Local 0
URI::ldap 1.1
perl 5.008001
================================================
FILE: lib/Date/Language/Occitan.pm
================================================
##
## Occitan tables, contributed by Quentn PAGÈS
##
package Date::Language::Occitan;
use Date::Language ();
use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION);
@ISA = qw(Date::Language);
$VERSION = "1.04";
@DoW = qw(dimenge diluns dimars dimècres dijòus divendres dissabte);
@MoY = qw(genièr febrièr març abrial mai junh
julhet agost octòbre novembre decembre);
@DoWs = map { substr($_,0,3) } @DoW;
@MoYs = map { substr($_,0,3) } @MoY;
$MoYs[6] = 'jul';
@AMPM = qw(AM PM);
@Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er');
@MoY{@MoY} = (0 .. scalar(@MoY));
@MoY{@MoYs} = (0 .. scalar(@MoYs));
@DoW{@DoW} = (0 .. scalar(@DoW));
@DoW{@DoWs} = (0 .. scalar(@DoWs));
# Formatting routines
sub format_a { $DoWs[$_[0]->[6]] }
sub format_A { $DoW[$_[0]->[6]] }
sub format_b { $MoYs[$_[0]->[4]] }
sub format_B { $MoY[$_[0]->[4]] }
sub format_h { $MoYs[$_[0]->[4]] }
sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] }
1;
================================================
FILE: lib/Lufi/Command/copyFilesToSwift.pm
================================================
package Lufi::Command::copyFilesToSwift;
use Mojo::Base 'Mojolicious::Command';
use File::Spec;
use Term::ProgressBar;
has description => 'Copy files from filesystem to Swift object storage';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
if ($c->app->config('swift')) {
$c->app->check_swift_container();
my @dirs = glob(File::Spec->catdir($c->app->config('upload_dir'), '*'));
unless (scalar(@dirs)) {
say sprintf('The configured upload_dir (%s) seems to be empty. Is `upload_dir` configured in lufi.conf?', $c->app->config('upload_dir'));
exit 1;
}
say sprintf('%d folders to upload to Swift (can\'t say how many files, or the total size, sorry). This can take some time.', scalar(@dirs));
print 'Do you want to continue? [Y/n] ';
my $confirm = ;
if ($confirm =~ m/yes|y/i) {
my $progress = Term::ProgressBar->new({ count => scalar(@dirs), ETA => 'linear', name => 'Copying to Swift'});
for my $dir (@dirs) {
my @files = glob(File::Spec->catfile($dir, '*'));
for my $file (@files) {
my ($volume, $directories, $filename) = File::Spec->splitpath($file);
my @file_dirs = File::Spec->splitdir($directories);
my $short = ($file_dirs[-1] ne '') ? $file_dirs[-1] : $file_dirs[-2];
my $path = File::Spec->catfile($short, $filename);
open my $fh, '<', $file or die sprintf('Unable to open file %s: %s', $file, $!);
$c->app->swift->put_object(
container_name => $c->app->config('swift')->{container},
object_name => $path,
content_length => -s $file,
content => $fh
);
close $fh;
}
$progress->update();
}
say sprintf('The copy to Swift object storage has ended. You can test Lufi, then delete `%s` directory', $c->app->config('upload_dir'));
} else {
say 'You want to stop. No problem.';
}
} else {
say 'You didn\'t configure `swift` in your config file. Exiting.';
exit 1;
}
}
=encoding utf8
=head1 NAME
Lufi::Command::copyFilesToSwift Copy files from filesystem to Swift object storage
=head1 SYNOPSIS
Usage: script/lufi copyFilesToSwift
This command needs you to:
- set `upload_dir` in your config file (otherwise, it will use the default path, `files` to copy files from)
- configure `swift` with correct informations in your config file
=cut
1;
================================================
FILE: lib/Lufi/Command/cron/cleanbdd.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Command::cron::cleanbdd;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Delete IP addresses from database after configured delay.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
my $separation = time() - $config->{keep_ip_during} * 86400;
Lufi::DB::File->new(app => $c->app)->delete_creator_before($separation);
}
=encoding utf8
=head1 NAME
Lufi::Command::cron::cleanbdd - Delete IP addresses from database after configured delay
=head1 SYNOPSIS
Usage: script/lufi cron cleanbdd
=cut
1;
================================================
FILE: lib/Lufi/Command/cron/cleanfiles.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Command::cron::cleanfiles;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Delete expired files.';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
my $time = time();
my $ldfile = Lufi::DB::File->new(app => $c->app);
$ldfile->get_expired($time)->each(
sub {
my ($f, $num) = @_;
$f->delete;
}
);
if (defined($config->{delete_no_longer_viewed_files}) && $config->{delete_no_longer_viewed_files} > 0) {
$time = time() - $config->{delete_no_longer_viewed_files} * 86400;
my $ldfile = Lufi::DB::File->new(app => $c->app);
$ldfile->get_no_longer_viewed($time)->each(
sub {
my ($f, $num) = @_;
$f->delete;
}
);
}
}
=encoding utf8
=head1 NAME
Lufi::Command::cron::cleanfiles - Delete expired files
=head1 SYNOPSIS
Usage: script/lufi cron cleanfiles
=cut
1;
================================================
FILE: lib/Lufi/Command/cron/watch.pm
================================================
package Lufi::Command::cron::watch;
use Mojo::Base 'Mojolicious::Command';
use Filesys::DiskUsage qw/du/;
use Lufi::DB::File;
use Switch;
use FindBin qw($Bin);
use Lufi::DefaultConfig qw($default_config);
has description => 'Watch the files directory and take action when over quota';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
if (defined($config->{max_total_size})) {
my $total = du(($c->app->config('upload_dir')));
if ($total > $config->{max_total_size}) {
say "[Lufi cron job watch] Files directory is over quota ($total > ".$config->{max_total_size}.")";
switch ($config->{policy_when_full}) {
case 'warn' {
say "[Lufi cron job watch] Please, delete some files or increase quota (".$config->{max_total_size}.")";
}
case 'stop-upload' {
open (my $fh, '>', 'stop-upload') or die ("Couldn't open stop-upload: $!");
close($fh);
say '[Lufi cron job watch] Uploads are stopped. Delete some images and the stop-upload file to reallow uploads.';
}
case 'delete' {
say '[Lufi cron job watch] Older files are being deleted';
my $ldfile = Lufi::DB::File->new(app => $c->app);
do {
$ldfile->get_oldest_undeleted_files(50)->each(
sub {
my ($f, $num) = @_;
$f->delete;
}
);
} while (du(qw/files/) > $config->{max_total_size});
}
else {
say '[Lufi cron job watch] Unrecognized policy_when_full option: '.$config->{policy_when_full}.'. Aborting.';
}
}
} else {
unlink 'stop-upload' if (-f 'stop-upload');
}
} else {
say "[Lufi cron job watch] No max_total_size found in the configuration file. Aborting.";
}
}
=encoding utf8
=head1 NAME
Lufi::Command::cron::watch - Watch the files directory and take action when over quota
=head1 SYNOPSIS
Usage: script/lufi cron watch
=cut
1;
================================================
FILE: lib/Lufi/Command/cron.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Command::cron;
use Mojo::Base 'Mojolicious::Commands';
has description => 'Execute tasks.';
has hint => < sub { shift->extract_usage . "\nCron tasks:\n" };
has namespaces => sub { ['Lufi::Command::cron'] };
sub help { shift->run(@_) }
1;
=encoding utf8
=head1 NAME
Lufi::Command::cron - Cron commands
=head1 SYNOPSIS
Usage: script/lufi cron TASK [OPTIONS]
=cut
================================================
FILE: lib/Lufi/Command/sqliteToOtherDB.pm
================================================
package Lufi::Command::sqliteToOtherDB;
use Mojo::Base 'Mojolicious::Command';
use Lufi::DB::File;
use Lufi::DB::Slice;
use Lufi::DB::Invitation;
use Mojo::SQLite;
use FindBin qw($Bin);
use Term::ProgressBar;
use Lufi::DefaultConfig qw($default_config);
has description => 'Migrate the records from a SQLite db to the currently configured database';
has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
if ($config->{dbtype} eq 'sqlite') {
say 'Please configure `dbtype` to something else than `sqlite` to use this command.';
print $c->usage;
exit 1;
}
my $sqlite = Mojo::SQLite->new('sqlite:'.$config->{db_path});
my $files = $sqlite->db->select('files', undef)->hashes;
my $slices = $sqlite->db->select('slices', undef)->hashes;
my $invitations = $sqlite->db->select('invitations', undef)->hashes;
my $progress = Term::ProgressBar->new({count => $files->size + $slices->size + $invitations->size});
$files->each(sub {
my ($file, $num) = @_;
$progress->update();
Lufi::DB::File->new(app => $c->app)
->short($file->{short})
->deleted($file->{deleted})
->mediatype($file->{mediatype})
->filename($file->{filename})
->filesize($file->{filesize})
->counter($file->{counter})
->delete_at_first_view($file->{delete_at_first_view})
->delete_at_day($file->{delete_at_day})
->created_at($file->{created_at})
->created_by($file->{created_by})
->last_access_at($file->{last_access_at})
->mod_token($file->{mod_token})
->nbslices($file->{nbslices})
->complete($file->{complete})
->passwd($file->{passwd})
->abuse($file->{abuse})
->write();
});
$slices->each(sub {
my ($slice, $num) = @_;
Lufi::DB::Slice->new(app => $c->app)
->short($slice->{short})
->j($slice->{j})
->write();
$progress->update();
});
$invitations->each(sub {
my ($invitation, $num) = @_;
Lufi::DB::Invitation->new(app => $c->app)
->token($invitation->{token})
->ldap_user($invitation->{ldap_user})
->ldap_user_mail($invitation->{ldap_user_mail})
->guest_mail($invitation->{guest_mail})
->created_at($invitation->{created_at})
->expire_at($invitation->{expire_at})
->files_sent_at($invitation->{files_sent_at})
->expend_expire_at($invitation->{expend_expire_at})
->files($invitation->{files})
->show_in_list($invitation->{show_in_list})
->deleted($invitation->{deleted})
->write();
$progress->update();
});
}
=encoding utf8
=head1 NAME
Lufi::Command::sqliteToOtherDB Migrate the records from a SQLite db to the currently configured database
=head1 SYNOPSIS
Usage: script/lufi sqliteToOtherDB
This command needs you to:
- set `db_path` in your config file (otherwise, it will use the default path, `lufi.db` to migrate data from)
- set `dbtype` to an other database type in your config file
- configure the other database access in your config file
=cut
1;
================================================
FILE: lib/Lufi/Command/theme.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Command::theme;
use Mojo::Base 'Mojolicious::Commands';
use FindBin qw($Bin);
use File::Spec qw(catfile catdir);
use File::Path qw(make_path);
has description => 'Create new theme skeleton.';
has usage => sub { shift->extract_usage };
has message => sub { shift->extract_usage . "\nCreate new theme skeleton:\n" };
has namespaces => sub { ['Lufi::Command::theme'] };
sub run {
my $c = shift;
my $name = shift;
unless (defined $name) {
say $c->extract_usage;
exit 1;
}
my $home = File::Spec->catdir($Bin, '..', 'themes', $name);
unless (-d $home) {
# Create skeleton
mkdir $home;
mkdir File::Spec->catdir($home, 'public');
make_path(File::Spec->catdir($home, 'templates', 'layouts'));
make_path(File::Spec->catdir($home, 'lib', 'Lufi', 'I18N'));
my $i18n = < 1,
_decode => 1,
_style => 'gettext',
'*' => [
Gettext => dirname(__FILE__) . '/I18N/*.po',
Gettext => dirname(__FILE__) . '/../../../default/lib/Lufi/I18N/*.po',
]
};
1;
EOF
open my $f, '>', File::Spec->catfile($home, 'lib', 'Lufi', 'I18N.pm') or die "Unable to open $home/lib/Lufi/I18N.pm: $!";
print $f $i18n;
close $f;
my $makefile = </dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(FR) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(IT) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(OC) 2>/dev/null
sed \$(SEDOPTS) -i \$(EN)
sed \$(SEDOPTS2) -i \$(EN)
sed \$(SEDOPTS) -i \$(FR)
sed \$(SEDOPTS2) -i \$(FR)
sed \$(SEDOPTS) -i \$(IT)
sed \$(SEDOPTS2) -i \$(IT)
sed \$(SEDOPTS) -i \$(OC)
sed \$(SEDOPTS2) -i \$(OC)
EOF
open $f, '>', File::Spec->catfile($home, 'Makefile') or die "Unable to open $home/Makefile: $!";
print $f $makefile;
close $f;
} else {
say "$name theme already exists. Aborting.";
exit 1;
}
}
=encoding utf8
=head1 NAME
Lufi::Command::theme - Create new theme skeleton.
=head1 SYNOPSIS
Usage: script/lufi theme THEME_NAME
Your new theme will be available in the themes directory.
=cut
1;
================================================
FILE: lib/Lufi/Controller/Auth.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Auth;
use Mojo::Base 'Mojolicious::Controller';
sub login_page {
my $c = shift;
my $redirect = $c->param('redirect') // '/';
if ($c->is_user_authenticated) {
$c->redirect_to('/');
} else {
if ($c->config('auth_headers')) {
if($c->authenticate('dummy', 'dummy')) {
if ($redirect eq 'invite') {
return $c->redirect_to('invite');
} elsif ($redirect eq 'my_invitations') {
return $c->redirect_to('invite_list');
}
return $c->redirect_to('/');
}
} else {
$c->render(
template => 'login',
redirect => $redirect
);
}
}
}
sub login {
my $c = shift;
my $login = $c->param('login');
my $pwd = $c->param('password');
my $redirect = $c->param('redirect') // '/';
if ($c->validation->csrf_protect->has_error('csrf_token')) {
$c->stash(msg => $c->l('Bad CSRF token.'));
$c->render(template => 'login');
} else {
if($c->authenticate($login, $pwd)) {
if ($redirect eq 'invite') {
return $c->redirect_to('invite');
} elsif ($redirect eq 'my_invitations') {
return $c->redirect_to('invite_list');
}
return $c->redirect_to('/');
} else {
$c->stash(msg => $c->l('Please, check your credentials or your right to access this service: unable to authenticate.'));
$c->render(template => 'login');
}
}
}
sub log_out {
my $c = shift;
if ($c->is_user_authenticated) {
if ($c->validation->csrf_protect->has_error('csrf_token')) {
$c->stash(msg => $c->l('Bad CSRF token.'));
} else {
$c->logout;
}
}
if ($c->config('logout_custom')) {
return $c->redirect_to($c->config('logout_custom'));
} else {
$c->render(template => 'logout');
}
}
1;
================================================
FILE: lib/Lufi/Controller/Files.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Files;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON qw(encode_json decode_json to_json true false);
use Mojo::Util qw(encode decode);
use Mojo::File;
use Lufi::DB::File;
use Lufi::DB::Slice;
use File::Spec::Functions;
use Number::Bytes::Human qw(format_bytes);
use Filesys::DfPortable;
use Crypt::SaltedHash;
sub files {
my $c = shift;
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd'))) || $c->is_user_authenticated) {
$c->render(template => 'files');
} else {
$c->redirect_to('login');
}
}
sub upload {
my $c = shift;
my $invitation;
my $token = $c->session->{guest_token};
$invitation = Lufi::DB::Invitation->new(app => $c->app)->from_token($token) if $token;
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd')) && !defined($c->config('auth_headers'))) || $c->is_user_authenticated || $invitation) {
$c->inactivity_timeout(30000000);
$c->app->log->debug('Client connected');
$c->on(
message => sub {
my ($ws, $text) = @_;
my $invit = Lufi::DB::Invitation->new(app => $c->app)->from_token($token) if $token;
my $begin = time;
my ($json) = split('XXMOJOXX', $text, 2);
$json = encode 'UTF-8', $json;
$text =~ s/^.*?XXMOJOXX/${json}XXMOJOXX/;
$json = decode_json $json;
$c->app->log->debug('Got message');
if (defined($json->{cancel}) && $json->{cancel}) {
my $f = Lufi::DB::File->new(app => $c->app)->from_short($json->{id});
if ($f && $f->mod_token && $f->mod_token eq $json->{mod_token}) {
$f = $f->delete();
return $ws->send(to_json(
{
action => 'cancel',
success => $f->deleted ? true : false,
msg => $f->deleted ? 'Lufi::DB::File->delete() was successfull' : 'Lufi::DB::File->delete() failed',
i => $json->{i}
}
));
} else {
return $ws->send(to_json(
{
action => 'cancel',
success => false,
msg => 'Lufi::DB::File not found or invalid mod_token',
i => $json->{i}
}
));
}
}
my $stop = 0;
# Check if stop_upload file is present
if ($c->stop_upload) {
$stop = 1;
return $ws->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Sorry, uploading is disabled.'),
sent_delay => $json->{delay},
i => $json->{i}
}
)));
}
# Check against max_size
if (defined $c->config('max_file_size')) {
if ($json->{size} > $c->config('max_file_size')) {
$stop = 1;
return $ws->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Your file is too big: %1 (maximum size allowed: %2)', format_bytes($json->{size}), format_bytes($c->config('max_file_size'))),
sent_delay => $json->{delay},
i => $json->{i}
}
)));
}
}
# Check that we have enough space (multiplying by 2 since it's encrypted, it takes more place that the original file)
# Only check if using filesystem, not Swift storage
if (!defined($c->config('swift')) && $json->{part} == 0 && ($json->{size} * 2) >= dfportable($c->config('upload_dir'))->{bavail}) {
$stop = 1;
return $ws->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('No enough space available on the server for this file (size: %1).', format_bytes($json->{size})),
sent_delay => $json->{delay},
i => $json->{i}
}
)));
}
# Check that the invitation is still valid, but only if it's the first chunk
# (i.e. a new file, we don't want to stop a current uploading)
if ($json->{part} == 0 && $invit && !$invit->is_valid()) {
$stop = 1;
$c->app->log->info(sprintf('Someone (%s) tried to use an expired or deleted invitation.', $invit->guest_mail));
$ws->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Sorry, your invitation has expired or has been deleted. Please contact %1 to have another invitation.', $invit->ldap_user_mail),
}
)));
}
unless ($stop) {
my $f;
if (defined($json->{id})) {
$f = Lufi::DB::File->new(app => $c->app)->from_short($json->{id});
} else {
my $delay;
unless (defined $json->{delay}) {
$json->{delay} = $c->max_delay;
}
if (defined $c->config('delay_for_size')) {
# Choose delay according to config
my $delays = $c->config('delay_for_size');
my @keys = sort {$b <=> $a} keys %{$delays};
for my $key (@keys) {
if ($json->{size} >= $key) {
$delay = ($json->{delay} < $delays->{$key}) ? $json->{delay} : $delays->{$key};
last;
}
}
}
# If the file size is lower than the lowest configured size or if there is no delay_for_size setting, we choose the configured max delay
unless (defined $delay) {
$delay = (($json->{delay} > 0 && $json->{delay} <= $c->max_delay) || $c->max_delay == 0) ? $json->{delay} : $c->max_delay;
}
# If we have a password
my $salted_pwd;
if ($c->config('allow_pwd_on_files') && defined($json->{file_pwd}) && $json->{file_pwd} ne '') {
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-256', salt_len => 8);
$csh->add($json->{file_pwd});
$salted_pwd = $csh->generate();
}
my $creator = $c->ip;
# Authenticated user logging
if ((defined($c->config('ldap')) || defined($c->config('htpasswd')) || defined($c->config('auth_headers'))) && !$invitation) {
$creator = sprintf('User: %s, IP: %s', $c->current_user->{username}, $creator);
}
# Guest user logging
if ($invitation) {
$creator = sprintf('User: %s, IP: %s', $invitation->guest_mail, $creator);
}
my $delete_at_first_view = ($json->{del_at_first_view}) ? 1 : 0;
$delete_at_first_view = 1 if $c->app->config('force_burn_after_reading');
$f = Lufi::DB::File->new(app => $c->app)->get_empty()
->created_by($creator)
->delete_at_first_view($delete_at_first_view)
->delete_at_day($delay)
->mediatype($json->{type})
->filename($json->{name})
->filesize($json->{size})
->nbslices($json->{total})
->mod_token($c->shortener($c->config('token_length')))
->passwd($salted_pwd)
->zipped($json->{zipped})
->write;
}
# This check is just in case we didn't succeed to find a corresponding record
# It normally can't happen
if (defined $f) {
# If we already have a part, it's a resend because the websocket has been broken
# In this case, we don't need to rewrite the file
unless ($f->slices->grep(sub { $_->j == $json->{part} })->size) {
# Create slice file
my $s = Lufi::DB::Slice->new(
app => $c->app,
short => $f->short,
j => $json->{part}
)->store($text);
push @{$f->slices}, $s;
$s->write;
if (($json->{part} + 1) == $json->{total}) {
$f->complete(1);
$f->created_at(time);
$f->write;
}
}
my $result = {
success => true,
i => $json->{i},
j => $json->{part},
parts => $json->{total},
short => $f->short,
name => $f->filename,
size => $f->filesize,
del_at_first_view => (($f->delete_at_first_view) ? true : false),
created_at => $f->created_at,
delay => $f->delete_at_day,
token => $f->mod_token,
sent_delay => $json->{delay},
duration => time - $begin
};
$ws->send(to_json($result));
} else {
$ws->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('The server was unable to find the file record to add your file part to. Please, contact the administrator.'),
sent_delay => $json->{delay},
i => $json->{i}
}
)));
}
}
}
);
$c->on(
finish => sub {
$c->app->log->debug('Client disconnected');
}
);
} else {
$c->on(
message => sub {
$c->app->log->info(sprintf('Someone unauthenticated tried to upload a file. IP: %s', $c->ip));
$c->finish;
}
);
}
}
sub download {
my $c = shift;
my $short = $c->param('short');
$c->inactivity_timeout(300000);
$c->app->log->debug('Client connected');
my $ldfile = Lufi::DB::File->new(app => $c->app)->from_short($short);
# Do we have a file?
if (defined $ldfile) {
# Is the file fully uploaded?
if ($ldfile->deleted
|| (
$ldfile->delete_at_day != 0
&& (
($ldfile->created_at + $ldfile->delete_at_day * 86400) < time()
)
)
) {
unless ($ldfile->deleted) {
$ldfile->delete;
}
$c->on(
message => sub {
my ($ws, $json) = @_;
$c->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Error: the file existed but was deleted.')
}
)));
}
);
} elsif (defined($ldfile->abuse)) {
my $abuse_msg = $c->l('This file has been deactivated by the admins. Contact them to know why.');
$abuse_msg = $c->app->config('abuse')->{$ldfile->abuse} if ($c->app->config('abuse') && $c->app->config('abuse')->{$ldfile->abuse});
$c->on(
message => sub {
my ($ws, $json) = @_;
$c->send(decode('UTF-8', encode_json(
{
success => false,
msg => $abuse_msg
}
)));
}
);
} elsif ($ldfile->complete) {
my $f = $ldfile;
$c->on(
message => sub {
my ($ws, $json) = @_;
$json = decode_json $json;
# Do we need a password?
my $valid = 1;
if ($c->config('allow_pwd_on_files') && defined($f->{passwd})) {
my $pwd = $json->{file_pwd};
$valid = Crypt::SaltedHash->validate($f->{passwd}, $json->{file_pwd}, 8);
}
if ($valid) {
if (defined($json->{part})) {
# Make $num an integer instead of a string
my $num = $json->{part} + 0;
# Get the slice
my $e = $f->slices->[$num];
my $text = $e->retrieve();
my ($json2) = split('XXMOJOXX', $text, 2);
$json2 = decode 'UTF-8', $json2;
$text =~ s/^.*?XXMOJOXX/${json2}XXMOJOXX/;
# Send the slice
$c->send($text);
} elsif (defined($json->{ended}) && $json->{ended}) {
$f->counter($f->counter + 1);
$f->last_access_at(time);
if ($f->delete_at_first_view) {
$f->delete;
} else {
$f->write;
}
}
} else {
$c->send(decode('UTF-8', encode_json(
{
msg => $c->l('Your password is not valid. Please refresh the page to retry.')
}
)));
}
}
);
$c->on(
finish => sub {
$c->app->log->debug('Client disconnected');
}
);
} else {
$c->on(
message => sub {
my ($ws, $json) = @_;
$c->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Error: the file has not been sent entirely.')
}
)));
}
);
}
} else {
$c->send(decode('UTF-8', encode_json(
{
success => false,
msg => $c->l('Error: unable to find the file. Are you sure of your URL?')
}
)));
}
}
sub r {
my $c = shift;
my $short = $c->param('short');
my $ldfile = Lufi::DB::File->new(app => $c->app)->from_short($short);
if (defined $ldfile) {
return $c->render(
template => 'render',
f => $ldfile,
file_pwd => ($c->config('allow_pwd_on_files') && defined($ldfile->passwd))
);
} else {
return $c->render(
template => 'render',
msg => $c->l('Could not find the file. Are you sure of the URL?')
);
}
}
sub get_counter {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd')) && !defined($c->config('auth_headers'))) || $c->is_user_authenticated) {
my $ldfile = Lufi::DB::File->new(app => $c->app)->from_short($short);
if (defined $ldfile) {
if ($ldfile->mod_token eq $token) {
return $c->render(
json => {
success => true,
short => $short,
counter => $ldfile->counter,
deleted => ($ldfile->deleted) ? true : false
}
);
} else {
return $c->render(
json => {
success => false,
missing => false,
short => $short,
msg => $c->l('Unable to get counter for %1. The token is invalid.', $short)
}
);
}
} else {
return $c->render(
json => {
success => false,
missing => true,
short => $short,
msg => $c->l('Unable to get counter for %1. The file does not exists. It will be removed from your localStorage.', $short)
}
);
}
} else {
return $c->render(
json => {
success => false,
missing => false,
short => $short,
msg => $c->l('Unable to get counter for %1. You are not authenticated.', $short)
}
);
}
}
sub delete_file_page {
my $c = shift;
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd'))) || $c->is_user_authenticated) {
my $short = $c->param('short');
my $token = $c->param('token');
my $ldfile = Lufi::DB::File->new(app => $c->app)->from_short($short);
$c->render(
template => 'delete_file',
short => $short,
token => $token,
filename => $ldfile->{filename},
);
} else {
$c->redirect_to('login');
}
}
sub delete {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd')) && !defined($c->config('auth_headers'))) || $c->is_user_authenticated) {
if (!(defined($c->param('_format')) && $c->param('_format') eq 'json') && $c->validation->csrf_protect->has_error('csrf_token')) {
$c->flash(msg => $c->l('Bad CSRF token.'));
$c->redirect_to('delete', $short, $token);
} else {
my $ldfile = Lufi::DB::File->new(app => $c->app)->from_short($short);
$ldfile = undef unless (defined($ldfile) && $ldfile->mod_token eq $token);
if (defined $ldfile) {
my $msg;
if ($ldfile->deleted) {
$msg = $c->l('The file has already been deleted');
} else {
$ldfile->delete;
$msg = $c->l('File deleted');
}
return $c->respond_to(
json => {
json => {
success => true,
msg => $msg
}
},
any => sub {
$c->render(
template => 'msg',
f => $ldfile,
msg => $msg,
del_short_from_localstorage => $short
);
}
);
} else {
my $msg = $c->l('Could not find the file. Are you sure of the URL and the token?');
return $c->respond_to(
json => {
json => {
success => false,
msg => $msg
}
},
any => sub {
$c->render(
template => 'msg',
f => undef,
msg => $msg
);
}
);
}
}
} else {
my $msg = $c->l('Could not delete the file. You are not authenticated.');
return $c->respond_to(
json => {
json => {
success => false,
msg => $msg
}
},
any => sub {
$c->render(
template => 'msg',
f => undef,
msg => $msg
);
}
);
}
}
1;
================================================
FILE: lib/Lufi/Controller/Invitation.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Invitation;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Collection 'c';
use Mojo::File;
use Mojo::JSON qw(true false decode_json encode_json);
use Mojo::URL;
use Mojo::Util qw(decode encode);
use Email::Valid;
use Lufi::DB::File;
use Lufi::DB::Invitation;
use Date::Format;
sub new_invite {
my $c = shift;
# The `if (defined($c->config('ldap')))` is at the router level in lib/Lufi.pm
if ($c->is_user_authenticated) {
my $mail_attr = $c->config('invitations')->{'mail_attr'} // 'mail';
my $max_expire_at = $c->config('invitations')->{'max_invitation_expiration_delay'} // 30;
my $send_with_user_email = defined $c->config('invitations')->{'send_invitation_with_ldap_user_mail'};
$c->render(
template => 'invitations/invite',
max_expire_at => $max_expire_at,
send_with_user_email => $send_with_user_email,
user_mail => ($send_with_user_email) ? $c->current_user->{$mail_attr} : '',
fails => [],
success => []
);
} else {
$c->redirect_to($c->url_for('login')->query(redirect => 'invite'));
}
}
sub send_invite {
my $c = shift;
my $guest_mail = $c->param('guest_mail');
my $expire_at = $c->param('expire_at');
my $mail_attr = $c->config('invitations')->{'mail_attr'} // 'mail';
my $max_expire_at = $c->config('invitations')->{'max_invitation_expiration_delay'} // 30;
my $send_with_user_email = defined $c->config('invitations')->{'send_invitation_with_ldap_user_mail'};
# The `if (defined($c->config('ldap')))` is at the router level in lib/Lufi.pm
if ($c->is_user_authenticated) {
my @fails = ();
my @success = ();
unless (Email::Valid->address($guest_mail)) {
push @fails, $c->l('The guest email address (%1) is unvalid.', $guest_mail);
}
unless ($expire_at >= 1 && $expire_at <= $max_expire_at) {
push @fails, $c->l('The expiration delay (%1) is not between 1 and %2 days.', $expire_at, $max_expire_at);
}
unless (scalar(@fails)) {
my $invitation = Lufi::DB::Invitation->new(app => $c->app);
my $mail_attr = $c->config('invitations')->{'mail_attr'} // 'mail';
my $expend_expire_at = $c->config('invitations')->{'max_additional_period'} // 10;
my $token;
do {
$token = $c->create_invitation_token;
} while ($invitation->is_token_used($token));
$invitation = $invitation->from_token($token);
$invitation->ldap_user($c->current_user->{username});
$invitation->ldap_user_mail($c->current_user->{$mail_attr});
$invitation->created_at(time);
$invitation->guest_mail($guest_mail);
$invitation->expire_at($invitation->created_at + 86400 * $expire_at);
$invitation->expend_expire_at($expend_expire_at);
$invitation->show_in_list(1);
$invitation = $invitation->write;
my $from = ($c->config('invitations')->{'send_invitation_with_ldap_user_mail'}) ? $invitation->ldap_user_mail : $c->config('mail_sender');
my $url = $c->url_for('guest', token => $invitation->token)->to_abs;
$c->mail(
from => $from,
to => $invitation->guest_mail,
template => 'invitations/invite',
format => 'mail',
ldap_user => ucfirst($invitation->ldap_user),
url => $url,
invitation => $invitation,
expires => $c->get_date_lang()->time2str($c->l('%A %d %B %Y at %T'), $invitation->expire_at)
);
push @success, $c->l('Invitation sent to %1.
URL: %2', $invitation->guest_mail, $url);
}
$c->render(
template => 'invitations/invite',
max_expire_at => $max_expire_at,
send_with_user_email => $send_with_user_email,
user_mail => ($send_with_user_email) ? $c->current_user->{$mail_attr} : '',
fails => \@fails,
success => \@success
);
} else {
$c->redirect_to('login');
}
}
sub my_invitations {
my $c = shift;
# The `if (defined($c->config('ldap')))` is at the router level in lib/Lufi.pm
if ($c->is_user_authenticated) {
my $invitations = Lufi::DB::Invitation->new(app => $c->app)
->from_user($c->current_user->{username});
$invitations = c() unless $invitations;
$c->render(
template => 'invitations/my_invitations',
invitations => $invitations
);
} else {
$c->redirect_to($c->url_for('login')->query(redirect => 'my_invitations'));
}
}
sub delete_invitations {
my $c = shift;
my @tokens = @{$c->every_param('tokens[]')};
if ($c->is_user_authenticated) {
my @result = ();
my @failures = ();
for my $token (@tokens) {
my $i = Lufi::DB::Invitation->new(app => $c->app)
->from_token($token);
if ($i->ldap_user eq $c->current_user->{username}) {
$i->deleted(1)
->write;
push @result, { msg => $c->l('The invitation %1 has been deleted.', $i->token), token => $i->token, deleted => $i->deleted };
} else {
push @failures, $c->l('The invitation %1 can’t be deleted: it wasn’t created by you (%2).', $i->token, $c->current_user->{username});
}
}
$c->render(json => {
success => (scalar(@result) > 0) ? true : false,
tokens => \@result,
failures => \@failures
});
} else {
$c->redirect_to($c->url_for('login')->query(redirect => 'my_invitations'));
}
}
sub resend_invitations {
my $c = shift;
my @tokens = @{$c->every_param('tokens[]')};
if ($c->is_user_authenticated) {
my @success = ();
my @failures = ();
for my $token (@tokens) {
my $i = Lufi::DB::Invitation->new(app => $c->app)
->from_token($token);
if ($i->ldap_user eq $c->current_user->{username}) {
if ($i->files_sent_at) {
push @failures, $c->l('The invitation %1 can’t be resent: %2 has already sent files.
Please create a new invitation.', $i->token, $i->guest_mail);
} else {
if ($c->config('invitations')->{'extend_invitation_expiration_on_resend'}) {
$i->expire_at(time + $i->expire_at - $i->created_at)
->write;
}
my $from = ($c->config('invitations')->{'send_invitation_with_ldap_user_mail'}) ? $i->ldap_user_mail : $c->config('mail_sender');
my $url = $c->url_for('guest', token => $i->token)->to_abs;
my $expire = $c->get_date_lang()->time2str($c->l('%A %d %B %Y at %T'), $i->expire_at);
$c->mail(
from => $from,
to => $i->guest_mail,
template => 'invitations/invite',
format => 'mail',
ldap_user => ucfirst($i->ldap_user),
url => $url,
invitation => $i,
expires => $expire
);
push @success, { msg => $c->l('Invitation resent to %1.
URL: %2', $i->guest_mail, $url), expires => $expire, token => $i->token };
}
} else {
push @failures, $c->l('The invitation %1 can’t be resent: it wasn’t created by you (%2).', $i->token, $c->current_user->{username});
}
}
$c->render(json => {
success => \@success,
failures => \@failures
});
} else {
$c->redirect_to($c->url_for('login')->query(redirect => 'my_invitations'));
}
}
sub toggle_invitations_visibility {
my $c = shift;
my @tokens = @{$c->every_param('tokens[]')};
my @result = ();
for my $token (@tokens) {
my $i = Lufi::DB::Invitation->new(app => $c->app)
->from_token($token)
->toggle_visibility;
push @result, { token => $i->token, show => ($i->show_in_list) ? true : false }
}
$c->render(json => {
success => true,
tokens => \@result
});
}
sub guest {
my $c = shift;
my $token = $c->param('token');
my $invitation = Lufi::DB::Invitation->new(app => $c->app)->from_token($token);
if ($invitation) {
if ($invitation->is_valid) {
$c->session->{guest_token} = $token;
$c->session(expires => $invitation->expire_at);
return $c->render(
template => 'index',
invitation => $invitation
);
} else {
$c->stash('expired_or_deleted_invitation' => 1);
}
} else {
$c->stash('invitation_not_found' => 1);
}
return $c->render(template => 'invitations/exception');
}
sub send_mail_to_ldap_user {
my $c = shift;
my $token = $c->param('token');
my $urls = c(@{$c->every_param('urls[]')});
my $invitation = Lufi::DB::Invitation->new(app => $c->app)->from_token($token);
if ($invitation) {
my @files = ();
if ($c->config('invitations')->{'save_files_url_in_db'} && $urls->size) {
my $guest_files = $invitation->files;
my %list_token;
if ($guest_files) {
$guest_files = decode_json(encode 'UTF-8', $guest_files);
for my $file (@{$guest_files}) {
$list_token{$file->{token}} = 1;
}
} else {
$guest_files = [];
}
push @files, @{$guest_files};
$urls->each(sub {
my ($e, $num) = @_;
$e = decode_json(encode 'UTF-8', $e);
if (!defined($list_token{$e->{token}})) {
push @{$guest_files}, $e;
push @files, $e;
}
});
$invitation->files(decode 'UTF-8', encode_json($guest_files));
$invitation->write;
} else {
$urls->each(sub {
push @files, decode_json(encode 'UTF-8', shift);
});
}
my $already_notified = 1;
unless ($invitation->files_sent_at) {
$invitation->files_sent_at(time);
$invitation->write;
$already_notified = 0;
}
$c->session(expires => $invitation->files_sent_at + 60 * $invitation->expend_expire_at);
$c->mail(
from => $c->config('mail_sender'),
to => $invitation->ldap_user_mail,
template => 'invitations/notification_files_sent',
format => 'mail',
files => c(@files),
invitation => $invitation,
already_notified => $already_notified
);
return $c->render(
json => {
success => true,
msg => $c->l('The URLs of your files have been sent by email to %1.', $invitation->ldap_user_mail)
}
);
} else {
return $c->render(
json => {
success => false,
msg => $c->l('Sorry, the invitation doesn’t exist. Are you sure you are on the right URL?')
}
);
}
}
1;
================================================
FILE: lib/Lufi/Controller/Mail.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Mail;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON qw(decode_json);
use Mojo::URL;
use Email::Valid;
use URI::Find;
use URI::Find::Schemeless;
sub render_mail {
my $c = shift;
my $links = (defined($c->param('links'))) ? decode_json($c->param('links')) : [];
$c->redirect_to('/') unless (scalar(@{$links}));
$c->render(
template => 'mail',
links => $links
);
}
sub send_mail {
my $c = shift;
my $validation = $c->validation;
return $c->render(text => $c->l('Bad CSRF token!'), status => 403) if $validation->csrf_protect->has_error('csrf_token');
my $emails = $c->param('emails');
my $body = $c->param('body');
my $subject = $c->param('subject');
my $msg = '';
my $base_url = $c->req->url->to_abs->path($c->config('prefix').'r/');
my $fixed_url = $base_url;
if ($c->config('fixed_domain')) {
$fixed_url->host($c->config('fixed_domain'));
}
my $at_least_one_instance_url = 0;
my $finder = URI::Find->new(sub {
my ($uri, $orig_uri) = @_;
$uri = Mojo::URL->new($uri);
if ($uri->host ne $base_url->to_abs->host && $uri->host ne $fixed_url->to_abs->host) {
$msg .= $c->l('You can\'t add URLs that are not related to this instance (%1).', $orig_uri).'
';
} elsif (index($orig_uri, $fixed_url->to_abs->to_string) > -1) {
$at_least_one_instance_url = 1;
}
return $orig_uri;
});
$finder->find(\$body);
$finder->find(\$subject);
# Schemeless URI beginning with www, which are interpreted by mailers 🤦
$finder = URI::Find::Schemeless->new(sub {
my ($uri, $orig_uri) = @_;
return $orig_uri if ($uri !~ m/www/);
$uri = Mojo::URL->new($uri);
if ($uri->host ne $base_url->to_abs->host && $uri->host ne $fixed_url->to_abs->host) {
$msg .= $c->l('You can\'t add URLs that are not related to this instance (%1).', $orig_uri).'
';
}
return $orig_uri;
});
$finder->find(\$body);
$finder->find(\$subject);
unless ($at_least_one_instance_url) {
$msg .= $c->l('The body of the mail must contain at least one URL pointing to a file hosted on this instance.').'
';
}
$emails =~ s/ //g;
my @a = split(',', $emails);
my @bad;
my @good;
for my $email (@a) {
if (!Email::Valid->address($email)) {
push @bad, $email;
}
}
if (scalar(@bad)) {
$msg .= $c->l('The following email addresses are not valid: %1', join(', ', @bad)).'
';
}
$msg .= $c->l('You must give email addresses.').'
' unless (scalar(@a));
$msg .= $c->l('The email subject can\'t be empty.').'
' unless ($subject);
$msg .= $c->l('The email body can\'t be empty.').'
' unless ($body);
if ($msg) {
return $c->render(
template => 'mail',
msg => $msg,
links => [],
values => {
emails => $emails,
subject => $subject,
body => $body
}
)
}
$c->mail(
from => $c->config('mail_sender'),
bcc => $emails,
subject => $subject,
data => $body
);
return $c->render(
template => 'msg',
msg_success => $c->l('The mail has been sent.')
);
}
1;
================================================
FILE: lib/Lufi/Controller/Misc.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Controller::Misc;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::File;
use Mojo::JSON qw(true false);
use Mojo::URL;
use Lufi::DB::File;
sub index {
my $c = shift;
if ((!defined($c->config('ldap')) && !defined($c->config('htpasswd')) && !defined($c->config('auth_headers'))) || $c->is_user_authenticated) {
$c->render(template => 'index');
} else {
$c->redirect_to('login');
}
}
sub change_lang {
my $c = shift;
my $l = $c->param('l');
if ($c->iso639_native_name($l)) {
$c->cookie($c->app->moniker.'_lang' => $l, { path => $c->config('prefix') });
}
if ($c->req->headers->referrer
&& Mojo::URL->new($c->req->headers->referrer)->host eq $c->req->url->to_abs->host) {
return $c->redirect_to($c->req->headers->referrer);
} else {
return $c->redirect_to('/');
}
}
sub about {
my $c = shift;
$c->render(
template => 'about',
version => $c->git_version
);
}
sub config_infos {
my $c = shift;
$c->render(
json => {
report => $c->config('report'),
instance_name => $c->config('instance_name'),
max_file_size => $c->config('max_file_size'),
broadcast_message => $c->config('broadcast_message'),
default_delay => $c->config('default_delay'),
max_delay => $c->config('max_delay'),
delay_for_size => $c->config('delay_for_size'),
allow_pwd_on_files => $c->config('allow_pwd_on_files'),
force_burn_after_reading => $c->config('force_burn_after_reading'),
keep_ip_during => $c->config('keep_ip_during'),
stop_upload => (-f 'stop-upload' || -f 'stop-upload.manual') ? true : false,
need_authentication => (defined($c->config('ldap')) || defined($c->config('htpasswd')) || defined($c->config('auth_headers'))) ? true : false,
version => $c->git_version
}
);
}
sub js_files {
my $c = shift;
$c->stash($c->req->params->to_hash);
$c->render(
template => 'partial/'.$c->param('file'),
format => 'js',
layout => undef,
);
}
sub fullstats {
my $c = shift;
my $stats = Lufi::DB::File->new(app => $c->app)->get_stats;
$stats->{timestamp} = time;
return $c->render(
json => $stats
);
}
sub delays {
shift->render(template => 'delays');
}
1;
================================================
FILE: lib/Lufi/DB/File/Mysql.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::File::Mysql;
use Mojo::Base 'Lufi::DB::File';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/File/Pg.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::File::Pg;
use Mojo::Base 'Lufi::DB::File';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/File/SQLite.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::File::SQLite;
use Mojo::Base 'Lufi::DB::File';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/File.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::File;
use Mojo::Base -base;
use Mojo::File;
use Mojo::Collection 'c';
use Lufi::DB::Slice;
has 'short';
has 'deleted' => 0;
has 'mediatype';
has 'filename';
has 'filesize';
has 'counter' => 0;
has 'delete_at_first_view' => 0;
has 'delete_at_day';
has 'created_at' => sub {
return time;
};
has 'created_by';
has 'last_access_at';
has 'mod_token';
has 'nbslices';
has 'complete' => 0;
has 'slices' => sub {
return Mojo::Collection->new();
};
has 'passwd';
has 'abuse';
has 'zipped' => 0;
has 'record' => 0;
has 'app';
=head1 NAME
Lufi::DB::File - DB abstraction layer for Lufi file
=head1 Contributing
When creating a new database accessor, make sure that it provides the following subroutines.
After that, modify this file and modify the C subroutine to allow to use your accessor.
Have a look at Lufi::DB::File::SQLite's code: it's simple and may be more understandable that this doc.
=head1 Attributes
=over 1
=item B : string
=item B : boolean
=item B : string
=item B : string
=item B : integer
=item B : integer
=item B : boolean
=item B : integer
=item B : unix timestamp
=item B : string
=item B : unix timestamp
=item B : string
=item B : integer
=item B : boolean
=item B : Mojo::Collection of Lufi::DB::Slice
=item B : string
=item B : integer
=item B : boolean
=item B : a Mojolicious object
=back
=head1 Sub routines
=head2 new
=over 1
=item B : C<$c = Lufi::DB::File-Enew(app =E $self);>
=item B : any of the attribute above
=item B : construct a new db accessor object. If the C attribute is provided, it have to load the informations from the database.
=item B : the db accessor object
=item B : the app argument is used by Lufi::DB::File to choose which db accessor will be used, you don't need to use it in new(), but you can use it to access helpers or configuration settings in the other subroutines
=back
=cut
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
if (ref($c) eq 'Lufi::DB::File') {
my $dbtype = $c->app->config('dbtype');
if ($dbtype eq 'sqlite') {
use Lufi::DB::File::SQLite;
$c = Lufi::DB::File::SQLite->new(@_);
} elsif ($dbtype eq 'postgresql') {
use Lufi::DB::File::Pg;
$c = Lufi::DB::File::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lufi::DB::File::Mysql;
$c = Lufi::DB::File::Mysql->new(@_);
}
}
return $c;
}
=head2 delete
=over 1
=item B : C<$c-Edelete>
=item B : none
=item B : delete the files of the slices and the directory containing those files, then update the object by setting the deleted attribute to 1 (true)
=item B : the db accessor object
=back
=cut
sub delete {
my $c = shift;
$c->slices->each(sub {
my ($e, $num) = @_;
$e->delete_file();
});
$c->delete_path
->deleted(1)
->write;
return $c;
}
=head2 delete_path
=over 1
=item B : C<$c-Edelete_path()>
=item B : none
=item B : delete the directory of the slices on filesystem or Swift object storage
=item B : the db accessor object
=back
=cut
sub delete_path {
my $c = shift;
if (!defined($c->app->config('swift'))) {
rmdir Mojo::File->new($c->app->config('upload_dir'), $c->short);
}
return $c;
}
=head2 write
=over 1
=item B : C<$c-Ewrite>
=item B : none
=item B : create or update a record in the database, with the values of the object's attributes
=item B : the db accessor object
=back
=cut
sub write {
my $c = shift;
if ($c->record) {
$c->app->dbi->db->query('UPDATE files SET short = ?, deleted = ?, mediatype = ?, filename = ?, filesize = ?, counter = ?, delete_at_first_view = ?, delete_at_day = ?, created_at = ?, created_by = ?, last_access_at = ?, mod_token = ?, nbslices = ?, complete = ?, passwd = ?, abuse = ?, zipped = ? WHERE short = ?', $c->short, $c->deleted, $c->mediatype, $c->filename, $c->filesize, $c->counter, $c->delete_at_first_view, $c->delete_at_day, $c->created_at, $c->created_by, $c->last_access_at, $c->mod_token, $c->nbslices, $c->complete, $c->passwd, $c->abuse, $c->zipped, $c->short);
} else {
$c->app->dbi->db->query('INSERT INTO files (short, deleted, mediatype, filename, filesize, counter, delete_at_first_view, delete_at_day, created_at, created_by, last_access_at, mod_token, nbslices, complete, passwd, abuse, zipped) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', $c->short, $c->deleted, $c->mediatype, $c->filename, $c->filesize, $c->counter, $c->delete_at_first_view, $c->delete_at_day, $c->created_at, $c->created_by, $c->last_access_at, $c->mod_token, $c->nbslices, $c->complete, $c->passwd, $c->abuse, $c->zipped);
$c->record(1);
}
return $c;
}
=head2 count_empty
=over 1
=item B : C<$c-Ecount_empty>
=item B : none
=item B : count how many records have a null created_at column
=item B : integer
=back
=cut
sub count_empty {
my $c = shift;
return $c->app->dbi->db->query('SELECT count(short) AS count FROM files WHERE created_at IS NULL')->hashes->first->{count};
}
=head2 already_exists
=over 1
=item B : C<$c-Ealready_exists($short)>
=item B : a string
=item B : check if the given string is already used as short attribute for a file
=item B : 1 or 0
=back
=cut
sub already_exists {
my $c = shift;
my $short = shift;
return $c->app->dbi->db->query('SELECT count(short) AS count FROM files WHERE short = ?', $short)->hashes->first->{count};
}
=head2 get_empty
=over 1
=item B : C<$c-Eget_empty>
=item B : none
=item B : select an empty ready-to-use record from the database
=item B : a db accessor object
=back
=cut
sub get_empty {
my $c = shift;
my $r = $c->app->dbi->db->query('SELECT * FROM files WHERE created_at IS NULL')->hashes->shuffle->first;
return $c->_slurp($r)->created_at(time)->write;
}
=head2 get_stats
=over 1
=item B : C<$c-Eget_stats>
=item B : none
=item B : get stats about how many empty files, deleted files and non-deleted files there is in the database
=item B : a hash table reference containing three keys: files, deleted and empty
=back
=cut
sub get_stats {
my $c = shift;
my $files = $c->app->dbi->db->query('SELECT count(short) AS count FROM files WHERE created_at IS NOT null AND deleted = ?', 0)->hashes->first->{count};
my $deleted = $c->app->dbi->db->query('SELECT count(short) AS count FROM files WHERE created_at IS NOT null AND deleted = ?', 1)->hashes->first->{count};
my $empty = $c->app->dbi->db->query('SELECT count(short) AS count FROM files WHERE created_at IS null')->hashes->first->{count};
my $downloads = $c->app->dbi->db->query('SELECT SUM(counter) AS sum FROM files')->hashes->first->{sum};
return { files => $files, deleted => $deleted, empty => $empty, downloads => $downloads };
}
=head2 from_short
=over 1
=item B : C<$c-Efrom_short($short)>
=item B : string
=item B : find a file in the database from its short attribute
=item B : a db accessor object
=back
=cut
sub from_short {
my $c = shift;
my $short = shift;
my $r = $c->app->dbi->db->query('SELECT * FROM files WHERE short = ?', $short)->hashes;
if ($r->size) {
return $c->_slurp($r->first)->record(1);
} else {
return undef;
}
}
=head2 get_oldest_undeleted_files
=over 1
=item B : C<$c-Eget_oldest_undeleted_files($num)>
=item B : integer
=item B : get the X oldest non-deleted files
=item B : a Mojo::Collection of Lufi::DB::File objects
=back
=cut
sub get_oldest_undeleted_files {
my $c = shift;
my $num = shift;
my @files;
my $records = $c->app->dbi->db->query('SELECT * FROM files WHERE deleted = ? ORDER BY created_at ASC LIMIT ?', 0, $num)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lufi::DB::File->new(app => $c->app);
push @files, $i->_slurp($e);
}
);
return c(@files);
}
=head2 get_expired
=over 1
=item B : C<$c-Eget_expired($time)>
=item B : unix timestamp
=item B : get the non-deleted files that are expired at the given timestamp minus 2 days
=item B : a Mojo::Collection of Lufi::DB::File objects
=back
=cut
sub get_expired {
my $c = shift;
my $time = shift;
my @files;
## Select only files expired since two days, to be sure that nobody is still downloading it
my $records = $c->app->dbi->db->query('SELECT * FROM files WHERE deleted = ? AND ((delete_at_day + 2) * 86400) < (? - created_at) AND delete_at_day != 0', 0, $time)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lufi::DB::File->new(app => $c->app);
push @files, $i->_slurp($e);
}
);
return c(@files);
}
=head2 get_no_longer_viewed
=over 1
=item B : C<$c-Eget_no_longer_viewed($time)>
=item B : unix timestamp
=item B : get the files that have not been viewed after the given timestamp
=item B : a Mojo::Collection of Lufi::DB::File objects
=back
=cut
sub get_no_longer_viewed {
my $c = shift;
my $time = shift;
my @files;
my $records = $c->app->dbi->db->query('SELECT * FROM files WHERE deleted = ? AND last_access_at < ?', 0, $time)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lufi::DB::File->new(app => $c->app);
push @files, $i->_slurp($e);
}
);
return c(@files);
}
=head2 delete_creator_before
=over 1
=item B : C<$c-Edelete_creator_before($time)>
=item B : unix timestamp
=item B : empty the created_by column for files created before the given timestamp
=item B : nothing
=back
=cut
sub delete_creator_before {
my $c = shift;
my $separation = shift;
$c->app->dbi->db->query('UPDATE files SET created_by = NULL WHERE created_by IS NOT NULL AND created_at < ?', $separation);
}
=head2 delete_all
=over 1
=item B : C<$c-Edelete_all()>
=item B : none
=item B : delete all file records from database unconditionnally
=item B : nothing
=back
=cut
sub delete_all {
my $c = shift;
$c->app->dbi->db->delete('files');
}
=head2 _slurp
=over 1
=item B : C<$c-E_slurp>
=item B : none
=item B : put a database record's columns into the Lufi::DB::File object's attributes
=item B : the Lufi::DB::File object
=back
=cut
sub _slurp {
my $c = shift;
my $r = shift;
my $file;
if (defined $r) {
$file = $r;
} else {
my $files = $c->app->dbi->db->query('SELECT * FROM files WHERE short = ?', $c->short)->hashes;
if ($files->size) {
$file = $files->first;
}
}
if ($file) {
$c->short($file->{short});
$c->deleted($file->{deleted});
$c->mediatype($file->{mediatype});
$c->filename($file->{filename});
$c->filesize($file->{filesize});
$c->counter($file->{counter});
$c->delete_at_first_view($file->{delete_at_first_view});
$c->delete_at_day($file->{delete_at_day});
$c->created_at($file->{created_at});
$c->created_by($file->{created_by});
$c->last_access_at($file->{last_access_at});
$c->mod_token($file->{mod_token});
$c->nbslices($file->{nbslices});
$c->complete($file->{complete});
$c->passwd($file->{passwd});
$c->abuse($file->{abuse});
$c->zipped($file->{zipped});
$c->record(1) unless $c->record;
}
$c->slices(Lufi::DB::Slice->new(app => $c->app)->get_slices_of_file($c->short));
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Invitation/Mysql.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Invitation::Mysql;
use Mojo::Base 'Lufi::DB::Invitation';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Invitation/Pg.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Invitation::Pg;
use Mojo::Base 'Lufi::DB::Invitation';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Invitation/SQLite.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Invitation::SQLite;
use Mojo::Base 'Lufi::DB::Invitation';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Invitation.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Invitation;
use Mojo::Base -base;
use Mojo::File;
use Mojo::Collection 'c';
has 'token';
has 'ldap_user';
has 'ldap_user_mail';
has 'guest_mail';
has 'created_at';
has 'expire_at';
has 'files_sent_at';
has 'expend_expire_at';
has 'files';
has 'show_in_list' => 1;
has 'deleted' => 0;
has 'record' => 0;
has 'app';
=head1 NAME
Lufi::DB::Invitation - DB abstraction layer for Lufi invitations
=head1 Contributing
When creating a new database accessor, make sure that it provides the following subroutines.
After that, modify this file and modify the C subroutine to allow to use your accessor.
Have a look at Lufi::DB::Invitation::SQLite's code: it's simple and may be more understandable that this doc.
=head1 Attributes
=over 1
=item B : string, invitation token
=item B : string, the user who created the invitation
=item B : string, the email of the user who created the invitation
=item B : string, the email of the guest
=item B : unix timestamp
=item B : unix timestamp
=item B : unix timestamp
=item B : integer, "error" delay, in minutes
=item B : string, optional, list of files sent by the guest
=item B : boolean, if the ldap user want to see the invitation in his/her invitations list
=item B : boolean
=item B : a Mojolicious object
=back
=head1 Sub routines
=head2 new
=over 1
=item B : C<$c = Lufi::DB::Invitation-Enew(app =E $self);>
=item B : any of the attribute above
=item B : construct a new db accessor object. If the C attribute is provided, it have to load the informations from the database.
=item B : the db accessor object
=item B : the app argument is used by Lufi::DB::Invitation to choose which db accessor will be used, you don't need to use it in new(), but you can use it to access helpers or configuration settings in the other subroutines
=back
=cut
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
if (ref($c) eq 'Lufi::DB::Invitation') {
my $dbtype = $c->app->config('dbtype');
if ($dbtype eq 'sqlite') {
use Lufi::DB::Invitation::SQLite;
$c = Lufi::DB::Invitation::SQLite->new(@_);
} elsif ($dbtype eq 'postgresql') {
use Lufi::DB::Invitation::Pg;
$c = Lufi::DB::Invitation::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lufi::DB::Invitation::Mysql;
$c = Lufi::DB::Invitation::Mysql->new(@_);
}
}
return $c;
}
sub to_hash {
my $c = shift;
return {
token => $c->token,
ldap_user => $c->ldap_user,
ldap_user_mail => $c->ldap_user_mail,
guest_mail => $c->guest_mail,
created_at => $c->created_at,
expire_at => $c->expire_at,
files_sent_at => $c->files_sent_at,
expend_expire_at => $c->expend_expire_at,
files => $c->files,
show_in_list => $c->show_in_list,
deleted => $c->deleted
};
}
=head2 delete
=over 1
=item B : C<$c-Edelete>
=item B : none
=item B : set the C flag to true
=item B : the db accessor object
=back
=cut
sub delete {
my $c = shift;
$c->deleted(1);
$c->write;
return $c;
}
=head2 hide
=over 1
=item B : C<$c-Ehide>
=item B : none
=item B : set the C flag to false
=item B : the db accessor object
=back
=cut
sub hide {
my $c = shift;
$c->show_in_list(0);
$c->write;
return $c;
}
=head2 show
=over 1
=item B : C<$c-Eshow>
=item B : none
=item B : set the C flag to true
=item B : the db accessor object
=back
=cut
sub show {
my $c = shift;
$c->show_in_list(1);
$c->write;
return $c;
}
=head2 toggle_visibility
=over 1
=item B : C<$c-Etoggle_visibility>
=item B : none
=item B : toggle the C flag
=item B : the db accessor object
=back
=cut
sub toggle_visibility {
my $c = shift;
if ($c->show_in_list) {
return $c->hide;
} else {
return $c->show;
}
}
=head2 write
=over 1
=item B : C<$c-Ewrite>
=item B : none
=item B : create or update a record in the database, with the values of the object's attributes
=item B : the db accessor object
=back
=cut
sub write {
my $c = shift;
if ($c->record) {
$c->app->dbi->db->update('invitations', $c->to_hash, { token => $c->token });
} else {
$c->app->dbi->db->insert('invitations', $c->to_hash);
$c->record(1);
}
return $c;
}
=head2 from_token
=over 1
=item B : C<$c-Efrom_token($token)>
=item B : string
=item B : find an invitation in the database from its C attribute
=item B : a db accessor object
=back
=cut
sub from_token {
my $c = shift;
my $token = shift;
my $r = $c->app->dbi->db->select('invitations', undef, { token => $token })->hashes;
if ($r->size) {
return $c->_slurp($r->first)->record(1);
} else {
return undef;
}
}
=head2 from_user
=over 1
=item B : C<$c-Efrom_user($mail)>
=item B : string
=item B : find invitations in the database from their C attribute
=item B : a Mojo::Collection of Lufi::DB::Invitation objects, sorted by creation date
=back
=cut
sub from_user {
my $c = shift;
my $user = shift;
my $r = $c->app->dbi->db
->select('invitations', undef, { ldap_user => $user }, { -desc => 'created_at' })
->hashes;
if ($r->size) {
my @invitations;
$r->each(sub {
my ($e, $num) = @_;
$e->{app} = $c->app;
$e->{record} = 1;
push @invitations, Lufi::DB::Invitation->new($e);
});
return c(@invitations);
} else {
return undef;
}
}
=head2 is_token_used
=over 1
=item B : C<$c-Eis_token_used($token)>
=item B : string
=item B : tells if a token is already used. If not, insert it in database to reserve it
=item B : a boolean
=back
=cut
sub is_token_used {
my $c = shift;
my $token = shift;
my $r = $c->app->dbi->db->select('invitations', ['token'], { token => $token })->hashes;
if ($r->size) {
return 1;
} else {
$c->app->dbi->db->insert('invitations', { token => $token });
return 0;
}
}
=head2 is_valid
=over 1
=item B : C<$c-Eis_valid()>
=item B : none
=item B : tells if an invitation is still valid
=item B : a boolean
=back
=cut
sub is_valid {
my $c = shift;
my $time = time;
# Active After creation date Before expiration date Before files send date plus extension delay
return (!$c->deleted && $time >= $c->created_at && $time < $c->expire_at && (!defined($c->files_sent_at) || $time < ($c->files_sent_at + $c->expend_expire_at * 60)));
}
=head2 _slurp
=over 1
=item B : C<$c-E_slurp>
=item B : none
=item B : put a database record's columns into the Lufi::DB::Invitation object's attributes
=item B : the Lufi::DB::Invitation object
=back
=cut
sub _slurp {
my $c = shift;
my $r = shift;
my $invitation;
if (defined $r) {
$invitation = $r;
} else {
my $invitations = $c->app->dbi->db->select('invitations', undef, { token => $c->token })->hashes;
if ($invitations->size) {
$invitation = $invitations->first;
}
}
if ($invitation) {
$c->token( $invitation->{token} );
$c->ldap_user( $invitation->{ldap_user} );
$c->ldap_user_mail( $invitation->{ldap_user_mail} );
$c->guest_mail( $invitation->{guest_mail} );
$c->created_at( $invitation->{created_at} );
$c->expire_at( $invitation->{expire_at} );
$c->files_sent_at( $invitation->{files_sent_at} );
$c->expend_expire_at($invitation->{expend_expire_at});
$c->files( $invitation->{files} );
$c->show_in_list( $invitation->{show_in_list} );
$c->deleted( $invitation->{deleted} );
$c->record(1) unless $c->record;
}
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Slice/Mysql.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Slice::Mysql;
use Mojo::Base 'Lufi::DB::Slice';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Slice/Pg.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Slice::Pg;
use Mojo::Base 'Lufi::DB::Slice';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Slice/SQLite.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Slice::SQLite;
use Mojo::Base 'Lufi::DB::Slice';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
1;
================================================
FILE: lib/Lufi/DB/Slice.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DB::Slice;
use Mojo::Base -base;
use Encode 'encode';
use File::Spec::Functions;
use Mojo::Collection 'c';
has 'short';
has 'j';
has 'record' => 0;
has 'app';
=encoding utf8
=head1 NAME
Lufi::DB::Slice - DB abstraction layer for Lufi file
=head1 Contributing
When creating a new database accessor, make sure that it provides the following subroutines.
After that, modify this file and modify the C subroutine to allow to use your accessor.
Have a look at Lufi::DB::Slice::SQLite's code: it's simple and may be more understandable that this doc.
=head1 Attributes
=over 1
=item B : string
=item B : integer
=item B : A mojolicious object
=back
=head1 Sub routines
=head2 new
=over 1
=item B : C<$c = Lufi::DB::Slice-Enew(app =E $self);>
=item B : any of the attribute above
=item B : construct a new db accessor object. If the C attribute is provided, it have to load the informations from the database.
=item B : the db accessor object
=item B : the app argument is used by Lufi::DB::Slice to choose which db accessor will be used, you don't need to use it in new(), but you can use it to access helpers or configuration settings in the other subroutines
=back
=cut
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
if (ref($c) eq 'Lufi::DB::Slice') {
my $dbtype = $c->app->config('dbtype');
if ($dbtype eq 'sqlite') {
use Lufi::DB::Slice::SQLite;
$c = Lufi::DB::Slice::SQLite->new(@_);
} elsif ($dbtype eq 'postgresql') {
use Lufi::DB::Slice::Pg;
$c = Lufi::DB::Slice::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lufi::DB::Slice::Mysql;
$c = Lufi::DB::Slice::Mysql->new(@_);
}
}
return $c;
}
=head2 write
=over 1
=item B : C<$c-Ewrite>
=item B : none
=item B : create or update a record in the database, with the values of the object's attributes
=item B : the db accessor object
=back
=cut
sub write {
my $c = shift;
if ($c->record) {
$c->app->dbi->db->query('UPDATE slices SET short = ?, j = ? WHERE short = ? AND j = ?', $c->short, $c->j, $c->short, $c->j);
} else {
$c->app->dbi->db->query('INSERT INTO slices (short, j) VALUES (?, ?)', $c->short, $c->j);
$c->record(1);
}
return $c;
}
=head2 store
=over 1
=item B : C<$c-Estore($text)>
=item B : a scalar value
=item B : will store the content to the object's path, either on filesystem or on Swift object storage
=item B : the db accessor object
=back
=cut
sub store {
my $c = shift;
my $text = shift;
if ($c->app->config('swift')) {
$c->app->swift->put_object(
container_name => $c->app->config('swift')->{container},
object_name => $c->get_path(),
content_length => length(Encode::encode_utf8($text)),
content => Encode::encode_utf8($text)
);
} else {
# Create directory
my $dir = catfile($c->app->config('upload_dir'), $c->short);
mkdir($dir, 0700) unless (-d $dir);
# Write file
my $file = catfile($c->app->config('upload_dir'), $c->get_path());
Mojo::File->new($file)->spew($text);
}
return $c;
}
=head2 retrieve
=over 1
=item B : C<$c-Eretrieve>
=item B : none
=item B : get file from storage, either filesystem or Swift object storage
=item B : the data from the file
=back
=cut
sub retrieve {
my $c = shift;
my $upload = shift;
if ($c->app->config('swift')) {
my $file;
$c->app->swift->get_object(
container_name => $c->app->config('swift')->{container},
object_name => $c->get_path(),
write_code => sub {
my ($status, $message, $headers, $chunk) = @_;
$file .= $chunk;
}
);
return Encode::decode_utf8($file);
} else {
my $file = catfile($c->app->config('upload_dir'), $c->get_path());
return Mojo::File->new($file)->slurp;
}
}
=head2 delete_file
=over 1
=item B : C<$c-Edelete_file()>
=item B : none
=item B : delete the file on filesystem or Swift object storage
=item B : the db accessor object
=back
=cut
sub delete_file {
my $c = shift;
if ($c->app->config('swift')) {
$c->app->swift->delete_object({
container_name => $c->app->config('swift')->{container},
object_name => $c->get_path()
});
} else {
my $file = catfile($c->app->config('upload_dir'), $c->get_path());
unlink $file or warn sprintf('Could not unlink %s: %s', $file, $!);
}
return $c;
}
=head2 get_slices_of_file
=over 1
=item B : C<$c-Eget_slices_of_file($short)>
=item B : string
=item B : get all Lufi::DB::Slice objects related to a file
=item B : a Mojo::Collection of Lufi::DB::Slice objects
=back
=cut
sub get_slices_of_file {
my $c = shift;
my $short = shift;
my @slices;
my $records = $c->app->dbi->db->query('SELECT * FROM slices WHERE short = ? ORDER BY j ASC', $short)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lufi::DB::Slice->new(app => $c->app);
push @slices, $i->_slurp($e);
}
);
return c(@slices);
}
=head2 delete_all
=over 1
=item B : C<$c-Edelete_all()>
=item B : none
=item B : delete all slices records from database unconditionnally
=item B : nothing
=back
=cut
sub delete_all {
my $c = shift;
$c->app->dbi->db->delete('slices');
}
=head2 path
=over 1
=item B : C<$c-Epath()>
=item B : non
=item B : format the path of the file, relative to the directory of the Swift object storage
=item B : the path of the file
=back
=cut
sub get_path {
my $c = shift;
return catfile($c->short, sprintf('%d.part', $c->j));
}
=head2 count
=over 1
=item B : C<$c-Ecount()>
=item B : none
=item B : get count of slices records from database
=item B : integer
=back
=cut
sub count {
my $c = shift;
return $c->app->dbi->db->query('SELECT count(*) AS count FROM slices')->hashes->first->{count};
}
=head2 _slurp
=over 1
=item B : C<$c-E_slurp>
=item B : none
=item B : put a database record's columns into the Lufi::DB::Slice object's attributes
=item B : the Lufi::DB::Slice object
=back
=cut
sub _slurp {
my $c = shift;
my $r = shift;
my $slice;
if (defined $r) {
$slice = $r;
} else {
my $slices = $c->app->dbi->db->query('SELECT * FROM slices WHERE short = ? AND j = ?', $c->short, $c->j)->hashes;
if ($slices->size) {
$slice = $slices->first;
}
}
if ($slice) {
$c->short($slice->{short});
$c->j($slice->{j});
$c->record(1);
}
return $c;
}
1;
================================================
FILE: lib/Lufi/DefaultConfig.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::DefaultConfig;
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw($default_config);
our $default_config = {
instance_name => 'Lufi',
prefix => '/',
provisioning => 100,
provis_step => 5,
length => 10,
token_length => 32,
secrets => ['hfudsifdsih'],
default_delay => 0,
max_delay => 0,
mail => {
how => 'sendmail'
},
mail_sender => 'no-reply@lufi.io',
disable_mail_sending => 0,
theme => 'default',
upload_dir => 'files',
session_duration => 3600,
allow_pwd_on_files => 0,
dbtype => 'sqlite',
db_path => 'lufi.db',
force_burn_after_reading => 0,
x_frame_options => 'DENY',
x_content_type_options => 'nosniff',
x_xss_protection => '1; mode=block',
keep_ip_during => 365,
policy_when_full => 'warn',
};
1;
================================================
FILE: lib/Lufi/Plugin/Headers.pm
================================================
package Lufi::Plugin::Headers;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app) = @_;
# Assets Cache headers
$app->plugin('StaticCache');
# Add CSP Header
if (!defined($app->config('csp')) || (defined($app->config('csp')) && $app->config('csp') ne '')) {
my $directives = {
'default-src' => "'none'",
'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline'",
'img-src' => "'self' blob:",
'media-src' => "blob:",
'font-src' => "'self'",
'form-action' => "'self'",
'base-uri' => "'self'",
'connect-src' => {
base => "'self'",
ws => 1
}
};
my $frame_ancestors = '';
$frame_ancestors = "'none'" if $app->config('x_frame_options') eq 'DENY';
$frame_ancestors = "'self'" if $app->config('x_frame_options') eq 'SAMEORIGIN';
if ($app->config('x_frame_options') =~ m#^ALLOW-FROM#) {
$frame_ancestors = $app->config('x_frame_options');
$frame_ancestors =~ s#ALLOW-FROM +##;
}
$directives->{'frame-ancestors'} = $frame_ancestors if $frame_ancestors;
$app->plugin('CSPHeader',
csp => $app->config('csp'),
directives => $directives
);
}
# Add other headers
$app->hook(
before_dispatch => sub {
my $c = shift;
$c->res->headers->header('X-Frame-Options' => $app->config('x_frame_options')) if $app->config('x_frame_options');
$c->res->headers->header('X-Content-Type-Options' => $app->config('x_content_type_options')) if $app->config('x_content_type_options');
$c->res->headers->header('X-XSS-Protection' => $app->config('x_xss_protection')) if $app->config('x_xss_protection');
}
);
}
1;
================================================
FILE: lib/Lufi/Plugin/Helpers.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Plugin::Helpers;
use Mojo::Base 'Mojolicious::Plugin';
use Lufi::DB::File;
use Lufi::DB::Invitation;
use Date::Language;
sub register {
my ($self, $app) = @_;
# PgURL helper
if ($app->config('dbtype') eq 'postgresql' || $app->config('dbtype') eq 'mysql') {
$app->plugin('PgURLHelper');
}
if ($app->config('dbtype') eq 'postgresql') {
require Mojo::Pg;
$app->helper(dbi => \&_pg);
# Database migration
my $migrations = Mojo::Pg::Migrations->new(pg => $app->dbi);
if ($app->mode eq 'development' && $ENV{LUFI_DEV}) {
$migrations->from_file('utilities/migrations/pg.sql')->migrate(0)->migrate($migrations->latest);
} else {
$migrations->from_file('utilities/migrations/pg.sql')->migrate($migrations->latest);
}
} elsif ($app->config('dbtype') eq 'mysql') {
require Mojo::mysql;
$app->helper(dbi => \&_mysql);
# Database migration
my $migrations = Mojo::mysql::Migrations->new(mysql => $app->dbi);
if ($app->mode eq 'development' && $ENV{LUFI_DEV}) {
$migrations->from_file('utilities/migrations/mysql.sql')->migrate(0)->migrate($migrations->latest);
} else {
$migrations->from_file('utilities/migrations/mysql.sql')->migrate($migrations->latest);
}
} elsif ($app->config('dbtype') eq 'sqlite') {
require Mojo::SQLite;
$app->helper(dbi => \&_sqlite);
# Database migration
# Have to create $sql before using its migrations attribute, otherwise, it won't work
my $sql = $app->dbi;
my $migrations = $sql->migrations;
if ($app->mode eq 'development' && $ENV{LUFI_DEV}) {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(0)->migrate($migrations->latest);
} else {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate($migrations->latest);
}
# Check if passwd column is missing
my $columns = $app->dbi->db->query('PRAGMA table_info(files)')->hashes;
my $pwd_col = 0;
$columns->each(sub {
my ($e, $num) = @_;
$pwd_col = 1 if $e->{name} eq 'passwd';
});
$app->dbi->db->query('ALTER TABLE files ADD COLUMN passwd TEXT') unless $pwd_col;
}
$app->helper(provisioning => \&_provisioning);
$app->helper(get_empty => \&_get_empty);
$app->helper(ip => \&_ip);
$app->helper(default_delay => \&_default_delay);
$app->helper(max_delay => \&_max_delay);
$app->helper(is_selected => \&_is_selected);
$app->helper(stop_upload => \&_stop_upload);
$app->helper(create_invitation_token => \&_create_invitation_token);
$app->helper(is_guest => \&_is_guest);
$app->helper(get_date_lang => \&_get_date_lang);
$app->helper(git_version => \&_git_version);
}
sub _pg {
my $c = shift;
my $pgdb = $c->config('pgdb');
my $port = (defined $pgdb->{port}) ? $pgdb->{port}: 5432;
my $addr = $c->pg_url({
host => $pgdb->{host}, port => $port, database => $pgdb->{database}, user => $pgdb->{user}, pwd => $pgdb->{pwd}
});
state $pg = Mojo::Pg->new($addr);
$pg->max_connections($pgdb->{max_connections}) if defined $pgdb->{max_connections};
return $pg;
}
sub _mysql {
my $c = shift;
my $mysqldb = $c->config('mysqldb');
my $port = (defined $mysqldb->{port}) ? $mysqldb->{port}: 3306;
my $addr = $c->pg_url({
host => $mysqldb->{host}, port => $port, database => $mysqldb->{database}, user => $mysqldb->{user}, pwd => $mysqldb->{pwd}
});
$addr =~ s/postgresql/mysql/;
state $mysql = Mojo::mysql->new($addr);
$mysql->max_connections($mysqldb->{max_connections}) if defined $mysqldb->{max_connections};
return $mysql;
}
sub _sqlite {
my $c = shift;
state $sqlite = Mojo::SQLite->new('sqlite:'.$c->app->config('db_path'));
return $sqlite;
}
sub _provisioning {
my $c = shift;
# Create some short patterns for provisioning
my $ldfile = Lufi::DB::File->new(app => $c->app);
if ($ldfile->count_empty < $c->app->config('provisioning')) {
for (my $i = 0; $i < $c->app->config('provis_step'); $i++) {
my $short;
do {
$short = $c->shortener($c->app->config('length'));
} while ($ldfile->already_exists($short));
$ldfile->created_at(undef)->short($short)->write;
}
}
}
sub _get_empty {
my $c = shift;
my $ldfile = Lufi::DB::File->new(app => $c->app)->get_empty;
return $ldfile;
}
sub _ip {
my $c = shift;
my $proxy = $c->req->headers->header('X-Forwarded-For');
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
return "$ip remote port:$remote_port";
}
sub _default_delay {
my $c = shift;
return $c->app->config('default_delay') if ($c->app->config('default_delay') >= 0);
warn "default_delay set to a negative value. Default to 0.";
return 0;
}
sub _max_delay {
my $c = shift;
return $c->app->config('max_delay') if ($c->app->config('max_delay') >= 0);
warn "max_delay set to a negative value. Default to 0.";
return 0;
}
sub _is_selected {
my $c = shift;
my $num = shift;
return ($num == $c->max_delay) ? 'selected="selected"' : '' if ($c->max_delay && !$c->default_delay);
return ($num == $c->default_delay) ? 'selected="selected"' : '';
}
sub _stop_upload {
my $c = shift;
if (-f 'stop-upload' || -f 'stop-upload.manual') {
return 1;
}
return 0;
}
sub _create_invitation_token {
my $c = shift;
return $c->shortener(32);
}
sub _is_guest {
my $c = shift;
my $token = shift;
my $invitation = Lufi::DB::Invitation->new(app => $c->app)->from_token($token);
return $invitation if ($invitation && $invitation->is_valid);
return 0;
}
my %date_langs = (
aa => 'Afar',
am => 'Amharic',
pt => 'Brazilian',
bg => 'Bulgarian',
zh => 'Chinese',
cs => 'Czech',
da => 'Danish',
nl => 'Dutch',
fi => 'Finnish',
en => 'English',
fr => 'French',
de => 'German',
el => 'Greek',
hu => 'Hungarian',
is => 'Icelandic',
it => 'Italian',
nn => 'Norwegian',
om => 'Oromo',
oc => 'Occitan',
ro => 'Romanian',
ru => 'Russian',
so => 'Somali',
es => 'Spanish',
sv => 'Swedish',
ti => 'Tigrinya',
tk => 'Turkish',
);
sub _get_date_lang {
my $c = shift;
my $l = $c->languages();
return Date::Language->new($date_langs{$l}) if $date_langs{$l};
$l =~ s/^(..).*/$1/;
return Date::Language->new($date_langs{$l}) if $date_langs{$l};
return Date::Language->new('English');
}
sub _git_version {
my $c = shift;
my $last_tag = `git describe --tags --abbrev=0`;
my $last_commit = `git rev-parse HEAD`;
chomp $last_tag;
chomp $last_commit;
return {
tag => $last_tag,
commit => $last_commit
}
}
1;
================================================
FILE: lib/Lufi.pm
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi;
use Mojo::Base 'Mojolicious';
use Mojolicious::Sessions;
use Mojo::File;
use Email::Valid;
use Data::Validate::URI qw(is_web_uri);
use Lufi::DefaultConfig qw($default_config);
$ENV{MOJO_MAX_WEBSOCKET_SIZE} = 100485760; # 10 * 1024 * 1024 = 10MiB
# This method will run once at server start
sub startup {
my $self = shift;
my $config = $self->plugin('Config' => {
default => $default_config
});
die 'You need to provide a contact information in lufi.conf!' unless (defined($self->config('contact')));
die 'You need to provide a **report** information in lufi.conf!' unless (defined($self->config('report')));
if (Email::Valid->address($self->config('report'))) {
$self->config('report' => 'mailto:'.$self->config('report'));
} elsif (!is_web_uri($self->config('report'))) {
die 'You need to provide an email address or an URL as report information in lufi.conf!';
}
$self->config('prefix', $self->config('prefix').'/') unless substr($self->config('prefix'), -1) eq '/';
# Themes handling
$self->plugin('FiatTux::Themes');
# Mail config
my $mail_config = {
type => 'text/plain',
encoding => 'quoted-printable',
how => $self->config('mail')->{'how'}
};
$mail_config->{howargs} = $self->config('mail')->{'howargs'} if (defined $self->config('mail')->{'howargs'});
$self->plugin('Mail' => $mail_config);
# Internationalization
my $lib = $self->home->rel_file('themes/'.$config->{theme}.'/lib');
eval qq(use lib "$lib");
$self->plugin('I18N');
# Debug
$self->plugin('DebugDumperHelper');
# Compress static assets
$self->plugin('GzipStatic');
# Headers
$self->plugin('Lufi::Plugin::Headers');
# Fiat Tux helpers
$self->plugin('FiatTux::Helpers');
# Authentication
$self->plugin('FiatTux::GrantAccess');
# Secrets
$self->secrets($self->config('secrets'));
# Helpers
$self->plugin('Lufi::Plugin::Helpers');
# Now helpers has been loaded, time to check Swift container
if ($config->{swift}) {
$self->check_swift_container();
$self->log->info('EXPERIMENTAL Using Swift object storage');
}
# Recurrent task
my $config_file = $ENV{MOJO_CONFIG} || $self->moniker.'.conf';
Mojo::IOLoop->recurring(2 => sub {
my $loop = shift;
my $lockfile = Mojo::File->new($config_file)->basename('.conf').'-provisioning.lock';
if (defined($config->{lockfile_dir})) {
$lockfile = Mojo::File->new($config->{lockfile_dir}, $lockfile)->to_string;
}
if (-e $lockfile) {
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($lockfile);
# Remove the lockfile if more than 20 seconds old
if ($mtime && time - $mtime > 20) {
unlink $lockfile if -e $lockfile; # if -e just to be sure the file hasn’t been removed while checking it
} else {
return;
}
}
Mojo::File->new($lockfile)->open('>'); # Create the file, like ->touch() but does not croak on fail
$self->provisioning();
unlink $lockfile if -e $lockfile;
});
# Create directory if needed
if (!defined($config->{swift})) {
mkdir($self->config('upload_dir'), 0700) unless (-d $self->config('upload_dir'));
die ('The upload directory ('.$self->config('upload_dir').') is not writable') unless (-w $self->config('upload_dir'));
}
# Configure sessions
my $sessions = Mojolicious::Sessions->new;
$sessions->cookie_name('lufi');
$sessions->cookie_path($self->config('prefix'));
$self->sessions($sessions);
# Default layout
$self->defaults(layout => 'default');
# Router
my $r = $self->routes;
# Page for files uploading
$r->get('/')
->to('Misc#index')
->name('index');
$r->get('/lang/:l')
->to('Misc#change_lang')
->name('lang');
if (defined $self->config('ldap') || defined $self->config('htpasswd') || defined $self->config('auth_headers')) {
# Login page
$r->get('/login')
->to('Auth#login_page');
# Authentication
$r->post('/login')
->to('Auth#login');
# Logout page
$r->post('/logout')
->to('Auth#log_out')
->name('logout');
if ((defined $self->config('ldap') || defined $self->config('auth_headers')) && defined $self->config('invitations')) {
# Invitation creation page
$r->get('/invite')
->name('invite')
->to('Invitation#new_invite');
# Send invitation
$r->post('/invite')
->to('Invitation#send_invite');
# Get my invitations
$r->get('/invite/list')
->name('invite_list')
->to('Invitation#my_invitations');
# Delete invitations
$r->post('/invite/list/delete')
->name('invite_list_delete')
->to('Invitation#delete_invitations');
# Resend invitation mail
$r->post('/invite/list/resend')
->name('invite_list_resend')
->to('Invitation#resend_invitations');
# Toggle invitations visibility
$r->post('/invite/list/visibility')
->name('invite_list_visibility')
->to('Invitation#toggle_invitations_visibility');
# I’m a guest
$r->get('/guest/:token')
->name('guest')
->to('Invitation#guest');
# I’m a guest and I sent all my files
$r->post('/guest/:token/send_mail')
->name('guest_send_mail')
->to('Invitation#send_mail_to_ldap_user');
}
}
# About page
$r->get('/about')
->to('Misc#about')
->name('about');
# About config API endpoint
$r->get('/about/config')
->to('Misc#config_infos')
->name('config');
# Generated js files
$r->get('/partial/<:file>.<:ext>')
->to('Misc#js_files')
->name('partial');
# Get instance stats
$r->get('/fullstats')
->to('Misc#fullstats')
->name('fullstats');
# Get a file
$r->get('/r/:short')
->to('Files#r')
->name('render');
# List of files (use localstorage, so the server know nothing about files)
$r->get('/files')
->to('Files#files')
->name('files');
# Get counter informations about a file
$r->post('/c')
->to('Files#get_counter')
->name('counter');
# Delete a file
$r->get('/d/:short/:token')
->to('Files#delete_file_page')
->name('delete');
$r->post('/d/:short/:token')
->to('Files#delete')
->name('really_delete');
# Get some informations about delays
$r->get('/delays')
->to('Misc#delays')
->name('delays');
# Get mail page
$r->get('/m')
->to('Mail#render_mail')
->name('mail');
# Submit mail
$r->post('/m')
->to('Mail#send_mail') unless $self->config('disable_mail_sending');
# Upload files websocket
$r->websocket('/upload')
->to('Files#upload')
->name('upload');
# Get files websocket
$r->websocket('/download/:short')
->to('Files#download')
->name('download');
}
1;
================================================
FILE: lib/Mounter.pm
================================================
package Mounter;
use Mojo::Base 'Mojolicious';
use FindBin qw($Bin);
use File::Spec qw(catfile);
use Lufi::DefaultConfig qw($default_config);
# This method will run once at server start
sub startup {
my $self = shift;
push @{$self->commands->namespaces}, 'Lufi::Command';
my $cfile = Mojo::File->new($Bin, '..' , 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $self->plugin('Config' =>
{
file => $cfile,
default => $default_config
}
);
# Compress static assets
$self->plugin('GzipStatic');
# Fiat Tux helpers
$self->plugin('FiatTux::Helpers');
# Headers
$self->plugin('Lufi::Plugin::Headers');
# Helpers
$self->plugin('Lufi::Plugin::Helpers');
# Themes handling
$self->plugin('FiatTux::Themes');
$self->plugin('Mount' => {$config->{prefix} => File::Spec->catfile($Bin, '..', 'script', 'application')});
}
1;
================================================
FILE: lufi.conf.template
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
# you can specify a unix socket too, like 'http+unix://%2Ftmp%2Flufi.sock'
listen => ['http://127.0.0.1:8081'],
# if you use Lufi behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lufi directly, let it commented
#proxy => 1,
# Please read http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers
# to adjust this to your server
workers => 30,
clients => 1,
},
# Put a way to contact you here and uncomment it
# You can put some HTML in it
# MANDATORY
#contact => 'Contact page',
# Put an URL or an email address to receive file reports and uncomment it
# It's for make reporting illegal files easy for users
# MANDATORY
#report => 'report@example.com',
# Array of random strings used to encrypt cookies
# optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT
#secrets => ['fdjsofjoihrei'],
# Name of the instance, displayed next to the logo
# optional, default is Lufi
#instance_name => 'Lufi',
# Choose a theme. See the available themes in `themes` directory
# Optional, default is 'default'
#theme => 'default',
# Length of the random URL
# optional, default is 8
#length => 8,
# How many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# Max number of URLs to be provisioned
# optional, default is 100
#provisioning => 100,
# Length of the modify/delete token
# optional, default is 32
#token_length => 32,
# Max file size, in octets
# You can write it 100*1024*1024
# optional, no default
#max_file_size => 104857600,
# If you want to have piwik statistics, provide a piwik image tracker
# Only the image tracker is allowed, no javascript
# optional, no default
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&rec=1',
# Broadcast_message which will displayed on the index page
# optional, no default
#broadcast_message => 'Maintenance',
# Default time limit for files
# Valid values are 0, 1, 7, 30 and 365
# optional, default is 0 (no limit)
#default_delay => 0,
# Number of days after which the files will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# A warning message will be displayed on homepage
# optional, default is 0 (no limit)
#max_delay => 0,
# Size thresholds: if you want to define max delays for different sizes of file
# The keys are size in Bytes, you can't have 10*1000*10000 as key
# If a file is smaller than the smallest configured size, it will have a expiration delay of max_delay (see above)
# optional, default is using max_delay (see above) for all sizes
#delay_for_size => {
# 10000000 => 90, # between 10MB and 50MB => max is 90 days, less than 10MB => max is max_delay (see above)
# 50000000 => 60, # between 50MB ans 1GB => max is 60 days
# 1000000000 => 2, # more than 1GB => max is 2 days
#},
# URL sub-directory in which you want Lufi to be accessible
# example: you want to have Lufi under https://example.org/lufi/
# => set prefix to '/lufi' or to '/lufi/', it doesn't matter
# optional, defaut is /
#prefix => '/',
# Array of authorized domains for API calls.
# If you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# String of the URL to be redirected to when accessing /logout
# optional, default is no redirection after logging out
#logout_custom => 'https://sso.example.com/logout?redirect_uri=https%3A%2F%2Fexample.com',
# Define a path to the upload directory, where the uploaded files will be stored
# You can define it relative to lufi directory or set an absolute path
# Remember that it has to be in a directory writable by Lufi user
# optional, default is 'files'
#upload_dir => 'files',
#!!!!!!!!!!!!!!!
# EXPERIMENTAL !
#!!!!!!!!!!!!!!!
# You can store files on Swift object storage (https://en.wikipedia.org/wiki/OpenStack#Swift) instead of filesystem
# Please read https://metacpan.org/pod/Net::OpenStack::Swift#SYNOPSIS to know how to configure this setting
# IMPORTANT: add a `container` key in it, to let Lufi know which container to use. This is not a regular Net::OpenStack::Swift setting, but Lufi need it.
# EXPERIMENTAL: if the upload or download of files are stucked, reload Lufi and create a cron task to reload Lufi once a day
# You can copy Lufi files to Swift object storage by launching the command `carton exec script/lufi copyFilesToSwift` (can take a long time)
# optional, no default
#swift => {
# auth_url => 'https://auth-endpoint-url/v2.0',
# user => 'userid',
# password => 'password',
# tenant_name => 'project_id',
# container => 'lufi'
#},
# Allow to add a password on files, asked before allowing to download files
# optional, default is 0
#allow_pwd_on_files => 0,
# Force all files to be in "Burn after reading mode"
# optional, default is 0
#force_burn_after_reading => 0,
# If set, the files' URLs will always use this domain
# optional, no default
#fixed_domain => 'example.org',
# Abuse reasons
# Set an integer in the abuse field of a file in the database and it will not be downloadable anymore
# The reason will be displayed to the downloader, according to the reasons you will configure here.
# optional, no default
#abuse => {
# 0 => 'Copyright infringment',
# 1 => 'Illegal content',
#},
# Lockfile directory
# In which directory do you want to store the lockfile?
# If using load balancing, you will want to set a directory shared by the servers
# You can define it relative to lufi directory or set an absolute path
# Remember that it has to be in a directory writable by Lufi user
# optional, default is lufi directory
#lockfile_dir => '.',
###############
# Mail settings
###############
# Mail configuration
# See https://metacpan.org/pod/Mojolicious::Plugin::Mail#EXAMPLES
# optional, default to sendmail method with no arguments
#mail => {
# # Valid values are 'sendmail' and 'smtp'
# how => 'smtp',
# howargs => ['smtp.example.org']
#},
# Email sender address
# optional, default to no-reply@lufi.io
#mail_sender => 'no-reply@lufi.io',
# Disable sending mail through the server
# optional, default is false
#disable_mail_sending => 0,
#############
# DB settings
#############
# Choose what database you want to use
# Valid choices are sqlite, postgresql and mysql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# Define a path to the SQLite database
# You can define it relative to lufi directory or set an absolute path
# Remember that it has to be in a directory writable by Lufi user
# optional, default is lufi.db
#db_path => 'lufi.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# These are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
#pgdb => {
# database => 'lufi',
# host => 'localhost',
# # optional, default is 5432
# #port => 5432,
# user => 'DBUSER',
# pwd => 'DBPASSWORD',
# # https://mojolicious.org/perldoc/Mojo/Pg#max_connections
# # optional, default is 1
# #max_connections => 1,
#},
# MySQL ONLY - only used if dbtype is set to mysql
# These are the credentials to access the MySQL database
# mandatory if you choosed mysql as dbtype
#mysqldb => {
# database => 'lufi',
# host => 'localhost',
# # optional, default is 3306
# #port => 3306,
# user => 'DBUSER',
# pwd => 'DBPASSWORD',
# # https://metacpan.org/pod/Mojo::mysql#max_connections
# # optional, default is 5 (set to 0 to disable persistent connections)
# #max_connections => 5,
#},
#############################################
# LDAP settings (authentication and features)
#############################################
# Set `ldap` if you want that only authenticated users can upload files
# Please note that everybody can still download files
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org', # server URI
# user_tree => 'ou=users,dc=example,dc=org', # search base DN
# bind_dn => 'uid=ldap_user,ou=users,dc=example,dc=org', # search bind DN
# bind_pwd => 'secr3t', # search bind password
# user_attr => 'uid', # user attribute (uid, mail, sAMAccountName, etc.)
# user_filter => '(!(uid=ldap_user))', # user filter (to exclude some users, etc.)
# # optional start_tls configuration. See https://metacpan.org/pod/distribution/perl-ldap/lib/Net/LDAP.pod#start_tls
# # don't set or uncomment if you don't want to configure it
# start_tls => {
# verify => 'optional',
# clientcert => '/etc/ssl/certs/ca-bundle.pem'
# }
#},
# If you've set ldap above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
# If you use `ldap` for authentication, you can map some attributes from LDAP to be able to access them in Lufi
# Those attributes will be accessible with:
# $c->current_user->{lufi_attribute_name} in Lufi backend files (all that is in `lib` directory)
# <%= $self->current_user->{lufi_attribute_name} %> in templates files (in `themes` directory)
#
# Define the attributes like this: `lufi_attribute_name => 'LDAP_attribute_name'`
# Note that you can’t use `username` as a Lufi attribute name: this name is reserved and will contain the login of the user
# optional, no default
#ldap_map_attr => {
# displayname => 'cn',
# mail => 'mail'
#},
# When using LDAP authentication, LDAP users can invite people (by mail) to use Lufi to send them files without
# being authenticated.
# This is where you configure the behavior of the invitations.
# You may need to fetch some attributes from LDAP to use some invitations settings. See `ldap_map_attr` above.
# optional, no default
#invitations => {
# # The name of the key set in `ldap_map_attr` (above) that corresponds to the mail of the LDAP user
# # optional, default is `mail`
# mail_attr => 'mail',
# # The `From` header of invitation mail can be the mail of the LDAP user
# # Be sure to have a mail system that will correctly send the mail from your users! (DKIM, SPF…)
# # To enable this feature, set it to 1
# # optional, disabled by default
# send_invitation_with_ldap_user_mail => 1,
# # The user is able to set an expiration delay for the invitation.
# # This expiration delay can’t be more than this setting (in days).
# # optional, default is 30 days
# max_invitation_expiration_delay => 30,
# # Once the guest has submitted his files, he has an additional period of time to submit forgotten files.
# # You can set that additional period of time in minutes here.
# # To disable that feature, set it to 0 or less
# # optional, default is 10 minutes
# max_additional_period => 10,
# # Lufi follows privacy-by-design, so, by default, no files URLs (with the decode secret) are stored in database.
# # However, the concern is different for this case. Storing files URLs makes users able to retrieve the guests’ sent files
# # from their `invitations` page.
# # Set to 1 to store guests’ files URLs in database
# # optional, default is 0 (disabled)
# save_files_url_in_db => 0,
# # Users can resend the invitation to their guest. This does not extend the invitation’s expiration delay unless you
# # set this option to 1.
# # optional, default is 0 (disabled)
# extend_invitation_expiration_on_resend => 0,
#},
#########################
# Htpasswd authentication
#########################
# Set `htpasswd` if you want to use an htpasswd file instead of ldap
# See 'man htpasswd' to know how to create such file
#htpasswd => 'lufi.passwd',
############################
# HTTP header authentication
############################
# Set `auth_headers` if you want to use HTTP header auth.
# Typically, these headers are set by a reverse-proxy
# acting as an authentication server. Useful for SSO.
# `auth_headers` should contains the user's username.
#
# /!\ LUFI BLINDLY TRUSTS THESE HEADERS
# /!\ IT'S UP TO YOU TO SANITIZE INCOMING HEADERS TO SECURE YOUR INSTANCE
#
#auth_headers => 'X-AUTH-PREFERRED-USERNAME',
#auth_headers_map_value => {
# # Like ldap_map_attr but for headers
# displayname => 'X-AUTH-DISPLAYNAME',
# firstname => 'X-AUTH-GIVENNAME',
# lastname => 'X-AUTH-LASTNAME',
# mail => 'X-AUTH-EMAIL'
#},
#######################
# HTTP Headers settings
#######################
# Content-Security-Policy header that will be sent by Lufi
# Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self' ws://YOUR_HOST; default-src 'none'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' blob:; media-src blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
#csp => "",
# X-Frame-Options header that will be sent by Lufi
# Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/'
# Set to '' to disable X-Frame-Options header
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
# Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly
# to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
# optional, default is 'DENY'
#x_frame_options => 'DENY',
# X-Content-Type-Options that will be sent by Lufi
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
# Set to '' to disable X-Content-Type-Options header
# optional, default is 'nosniff'
#x_content_type_options => 'nosniff',
# X-XSS-Protection that will be sent by Lufi
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
# Set to '' to disable X-XSS-Protection header
# optional, default is '1; mode=block'
#x_xss_protection => '1; mode=block',
#########################
# Lufi cron jobs settings
#########################
# Expired files will be kept for 2 additional days after the expiration time has passed!
# The reasoning behind this is to allow downloads to complete and avoid deleting them while
# they are still being tranfered.
# Number of days senders' IP addresses are kept in database
# After that delay, they will be deleted from database (used with script/lufi cron cleanbdd)
# optional, default is 365
#keep_ip_during => 365,
# Max size of the files directory, in octets
# Used by script/lufi cron watch to trigger an action
# optional, no default
#max_total_size => 10*1024*1024*1024,
# Default action when files directory is over max_total_size (used with script/lufi cron watch)
# Valid values are 'warn', 'stop-upload' and 'delete'
# Please, see README.md
# optional, default is 'warn'
#policy_when_full => 'warn',
# Files which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task
# If delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted
# optional, no default
#delete_no_longer_viewed_files => 90,
};
================================================
FILE: script/application
================================================
#!/usr/bin/env perl
use strict;
use warnings;
use lib 'lib';
# Start command line interface for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Lufi');
================================================
FILE: script/lufi
================================================
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../lib" }
# Start command line interface for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Mounter');
================================================
FILE: t/lufi.passwd
================================================
luc:$apr1$zG4UAKGa$FqSi4widrkVH/pT3qPawd.
================================================
FILE: t/test.t
================================================
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
use Mojo::Base -strict;
use Mojo::File;
use Mojo::JSON qw(to_json from_json true false);
use Mojolicious;
use Test::More;
use Test::Mojo;
use Lufi::DB::File;
use Lufi::DB::Slice;
use FindBin qw($Bin);
my ($m, $cfile, $config_orig, $config_file, $config_content);
my $msg = Encode::encode_utf8(to_json {
"total" => 1,
"part" => 0,
"size" => 7,
"name" => "foobaré.txt",
"type" => "text/plain",
"delay" => "0",
"del_at_first_view" => 1,
"id" => undef,
"zipped" => 0,
"i" => 0
});
my $filename_test = Encode::encode_utf8('foobaré');
my $encrypted = '"{\\"iv\\":\\"2RGAviAeYybBqcLCmnqlgA==\\",\\"v\\":1,\\"iter\\":10000,\\"ks\\":128,\\"ts\\":64,\\"mode\\":\\"ccm\\",\\"adata\\":\\"\\",\\"cipher\\":\\"aes\\",\\"salt\\":\\"1dvKtbZ8hxA=\\",\\"ct\\":\\"w9wDZCwNSyH/yL7q1GW5fPSdi+w=\\"}"';
my $encrypted_rgx = $encrypted;
$encrypted_rgx =~ s@\\@\\\\@g;
$encrypted_rgx =~ s@\+@\\+@g;
$encrypted_rgx =~ s@(\{|\})@\\$1@g;
BEGIN {
use lib 'lib';
$m = Mojolicious->new;
$cfile = Mojo::File->new($Bin, '..', 'lufi.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $m->plugin(
'Config' => {
file => $cfile->to_abs->to_string,
default => {
prefix => '/',
provisioning => 100,
provis_step => 5,
length => 10,
token_length => 32,
secrets => ['hfudsifdsih'],
default_delay => 0,
max_delay => 0,
mail => {
how => 'sendmail'
},
mail_sender => 'no-reply@lufi.io',
theme => 'default',
upload_dir => 'files',
session_duration => 3600,
allow_pwd_on_files => 0,
dbtype => 'sqlite',
db_path => 'lufi.db',
force_burn_after_reading => 0,
x_frame_options => 'DENY',
x_content_type_options => 'nosniff',
x_xss_protection => '1; mode=block',
}
}
);
$m->plugin('Lufi::Plugin::Helpers');
$m->plugin('DebugDumperHelper');
} ## end BEGIN
Lufi::DB::Slice->new(app => $m)->delete_all;
Lufi::DB::File->new(app => $m)->delete_all;
$config_file = Mojo::File->new($cfile->to_abs->to_string);
$config_orig = $config_file->slurp;
my $t = Test::Mojo->new('Lufi');
## Wait for short generation
sleep 5;
## Let's go
$t->get_ok('/')
->status_is(200)
->content_like(qr@Lufi@i);
test_infos_api(false);
test_upload_file();
test_download_file();
## Test htpasswd
switch_to_htpasswd();
test_infos_api(true);
auth_test_suite('luc', 'toto');
restore_config();
## Test LDAP
switch_to_ldap();
test_infos_api(true);
auth_test_suite('zoidberg', 'zoidberg');
restore_config();
## Test Swift object storage
#switch_to_swift();
#test_upload_file();
#test_download_file();
#restore_config();
done_testing();
######
### Functions
##
sub test_infos_api {
my $auth = shift;
$t->get_ok('/about/config')
->status_is(200)
->json_has(
'/allow_pwd_on_files', '/need_authentication', '/max_delay',
'/instance_name', '/broadcast_message', '/max_file_size',
'/keep_ip_during', '/report', '/stop_upload',
'/delay_for_size', '/default_delay', '/force_burn_after_reading'
)
->json_is(
'/allow_pwd_on_files' => 1,
'/need_authentication' => $auth,
'/max_delay' => 0,
'/instance_name' => 'Lufi',
'/broadcast_message' => undef,
'/max_file_size' => undef,
'/keep_ip_during' => 365,
'/report' => 'mailto:report@example.com',
'/stop_upload' => false,
'/delay_for_size' => undef,
'/default_delay' => 0,
'/force_burn_after_reading' => 0
);
}
sub test_upload_file {
$t->websocket_ok('/upload/')
->send_ok($msg.'XXMOJOXX'.$encrypted)
->message_ok
->message_like(qr@"created_at":\d+@)
->message_like(qr@"del_at_first_view":true@)
->message_like(qr@"delay":0@)
->message_like(qr@"duration":\d+@)
->message_like(qr@"i":0@)
->message_like(qr@"j":0@)
->message_like(qr@"name":"$filename_test\.txt"@)
->message_like(qr@"parts":1@)
->message_like(qr@"sent_delay":0@)
->message_like(qr@"short":"[^"]+"@)
->message_like(qr@"size":7@)
->message_like(qr@"success":true@)
->message_like(qr@"token":"[^"]+"}@)
->finish_ok;
}
sub test_download_file {
my $ws_msg;
$t->ua->websocket_p('/upload/')->then(sub {
my $tx = shift;
my $promise = Mojo::Promise->new;
$tx->on(finish => sub { $promise->resolve });
$tx->on(message => sub {
my $tx = shift;
$ws_msg = shift;
$tx->finish;
});
$tx->send($msg.'XXMOJOXX'.$encrypted);
return $promise;
})->catch(sub {
my $err = shift;
is($err, undef);
})->wait;
$ws_msg = from_json($ws_msg);
$t->websocket_ok('/download/'.$ws_msg->{short})
->send_ok(to_json({part => 0}))
->message_ok
->message_like(qr@"total":1@)
->message_like(qr@"part":0@)
->message_like(qr@"i":0@)
->message_like(qr@"id":null@)
->message_like(qr@"del_at_first_view":1@)
->message_like(qr@"delay":"0"@)
->message_like(qr@"name":"$filename_test\.txt"@)
->message_like(qr@"size":7@)
->message_like(qr@"type":"text\\/plain"@)
->message_like(qr@XXMOJOXX@)
->message_like(qr@$encrypted_rgx@)
->send_ok(to_json({ended => true}))
->finish_ok;
# The file is not supposed to be available anymore
$t->websocket_ok('/download/'.$ws_msg->{short})
->send_ok(to_json({part => 0}))
->message_ok
->message_like(qr@"msg":"Error: the file existed but was deleted\."@)
->message_like(qr@"success":false@)
->send_ok(to_json({ended => true}))
->finish_ok;
}
sub auth_test_suite {
my ($login, $pass) = @_;
$t->get_ok('/')
->status_is(302)
->header_is(Location => '/login');
test_fail_upload();
test_login($login, $pass);
test_upload_file();
test_download_file();
my $token = '';
$t->post_ok('/logout' => form => { csrf_token => $token })
->status_is(200)
->content_like(qr@Bad CSRF token\.@);
$token = $t->ua->get('/')->res->dom->find('input[name="csrf_token"]')->first->attr('value');
$t->post_ok('/logout' => form => { csrf_token => $token })
->status_is(200)
->content_like(qr@You have been successfully logged out\.@);
test_fail_upload();
}
sub test_fail_upload {
# An empty message would make it fail if we were allowed to go in the authenticated part
$t->websocket_ok('/upload/')
->send_ok('')
->finish_ok;
}
sub test_login {
my ($login, $pass) = @_;
$t->get_ok('/login')
->status_is(200)
->content_like(qr@Signin@);
my $token = '';
$t->post_ok('/login' => form => { login => $login, password => $pass, csrf_token => $token })
->status_is(200)
->content_like(qr@Bad CSRF token\.@);
$token = $t->ua->get('/login')->res->dom->find('input[name="csrf_token"]')->first->attr('value');
$t->post_ok('/login' => form => { login => $login, password => $pass, csrf_token => $token })
->status_is(302)
->header_is(Location => '/');
$t->get_ok('/login')
->status_is(302)
->header_is(Location => '/');
}
sub restore_config {
$config_file->spew($config_orig);
}
sub switch_to_htpasswd {
$config_content = $config_orig;
$config_content =~ s/#?htpasswd.*/htpasswd => 't\/lufi.passwd',/gm;
$config_file->spew($config_content);
Lufi::DB::Slice->new(app => $m)->delete_all;
Lufi::DB::File->new(app => $m)->delete_all;
$t = Test::Mojo->new('Lufi');
## Wait for short generation
sleep 5;
}
sub switch_to_ldap {
$config_content = $config_orig;
$config_content =~ s/^( +)#?ldap => \{ uri/$1ldap => { uri/gm;
$config_file->spew($config_content);
Lufi::DB::Slice->new(app => $m)->delete_all;
Lufi::DB::File->new(app => $m)->delete_all;
$t = Test::Mojo->new('Lufi');
## Wait for short generation
sleep 5;
}
sub switch_to_swift {
$config_content = $config_orig;
$config_content =~ s/^( +)#?swift => \{ auth_url/$1swift => { auth_url/gm;
$config_file->spew($config_content);
Lufi::DB::Slice->new(app => $m)->delete_all;
Lufi::DB::File->new(app => $m)->delete_all;
$t = Test::Mojo->new('Lufi');
## Wait for short generation
sleep 5;
}
================================================
FILE: themes/default/lib/Lufi/I18N/ar.po
================================================
# ButterflyOfFire , 2018. #zanata
# ButterflyOfFire , 2019. #zanata
# Luc Didry , 2019. #zanata
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2021-10-08 03:05+0000\n"
"Last-Translator: ButterflyOfFire \n"
"Language-Team: Arabic \n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 4.6.2\n"
#. ($delay)
#. (max_delay)
#: themes/default/templates/index.html.ep:56 themes/default/templates/index.html.ep:65 themes/default/templates/index.html.ep:66
msgid "%1 days"
msgstr "زَمَن %1 يَوم"
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:2
msgid "%1 invites you to send him/her files"
msgstr "يَدعُوكَ %1 لإرسالِ المَلَفَّاتِ إليه"
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:6
msgid "%1 invites you to send him/her files through Lufi."
msgstr "يَدعُوكَ %1 لإرسالِ المَلَفَّاتِ إلَيه عَبرَ لُوفي."
#. (stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:4
msgid "%1 sent you files"
msgstr "لَقَد أرسَلَ %1 مَلَفَّاتٍ إليك"
#. (stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:8
msgid "%1 used your invitation to send you files:"
msgstr "لَقَد اِستَخدَمَ %1 دَعوَتَكَ لإرسالِ المَلَفَّاتِ إليك:"
#: lib/Lufi/Controller/Invitation.pm:159 lib/Lufi/Controller/Invitation.pm:84 themes/default/templates/invitations/my_invitations.html.ep:51 themes/default/templates/invitations/my_invitations.html.ep:52 themes/default/templates/invitations/my_invitations.html.ep:53 themes/default/templates/invitations/notification_files_sent.mail.ep:12
msgid "%A %d %B %Y at %T"
msgstr "%A %d %B %Y عَلَى %T"
#: themes/default/templates/partial/index.js.ep:27
msgid "(max size: XXX)"
msgstr "(الحَجمُ الأقصَى: XXX)"
#: themes/default/templates/index.html.ep:5
msgid "1 year"
msgstr "سَنَةٌ واحِدَةٌ"
#: themes/default/templates/index.html.ep:4 themes/default/templates/index.html.ep:65
msgid "24 hours"
msgstr "زَمَن 24 ساعَة"
#: themes/default/templates/partial/mail.js.ep:40
msgid ":"
msgstr ":"
#: themes/default/templates/render.html.ep:42
msgid "Abort"
msgstr "إجهَاض"
#: themes/default/templates/layouts/default.html.ep:53 themes/default/templates/layouts/default.html.ep:86
msgid "About"
msgstr "حَول"
#: themes/default/templates/index.html.ep:107
msgid "Add a password to file(s)"
msgstr "إضافَةُ كَلِمَةُ مُرُورٍ إلى المَلَفَّات"
#: themes/default/templates/mail.html.ep:16
msgid ""
"Adding URLs not related to this Lufi instance to the mail body or subject is "
"prohibited."
msgstr ""
"يُمنَعُ إضافَةُ رابِط غَيرُ مُرتَبِطٍ بِمَثِيلِ Lufi هَذَا فِي نَصِّ البَريدِ"
" أو مَوضُوعِه."
#: themes/default/templates/partial/invitations.js.ep:3
msgid "Are you sure you want to delete the selected invitations?"
msgstr "هَل أنتَ مُتَأكِّدٌ مِن رَغبَتِكَ فِي حَذفِ الدَّعَواتِ المُحَدَّدَة؟"
#: themes/default/templates/partial/invitations.js.ep:4
msgid ""
"Are you sure you want to resend the invitation mail for the selected "
"invitations?"
msgstr ""
"هَل أنتَ مُتَأكِّدٌ مِن رَغبَتِكَ فِي إعادَةِ إرسالِ الدَّعَوَة البَريديَّة "
"لِلدَعَوَات المُحَدَّدَة؟"
#: themes/default/templates/about.html.ep:17
msgid ""
"As Lufi is a free software licensed under of the terms of the AGPLv3, you can "
"install it on you own server. Have a look on the Wiki for the procedure."
msgstr ""
#. (stash('nbslices')
#: themes/default/templates/partial/render.js.ep:9
msgid "Asking for file part XX1 of %1"
msgstr "يَجري طَلَبُ جُزءِ المَلَفّ XX1 مِن %1"
#: themes/default/templates/about.html.ep:20
msgid "Back to homepage"
msgstr "العَودَةُ إلى الصَّفحَةِ الرَّئِيسَة"
#: lib/Lufi/Controller/Mail.pm:25
msgid "Bad CSRF token!"
msgstr "رَمزُ CSRF غَيرُ صَالِح!"
#: lib/Lufi/Controller/Auth.pm:27 lib/Lufi/Controller/Auth.pm:49
msgid "Bad CSRF token."
msgstr "رَمزُ CSRF غَيرُ صَالِح."
#: themes/default/templates/partial/render.js.ep:5
msgid "Click here to refresh the page and restart the download."
msgstr "اِضغَط لِتَحدِيثِ الصَّفحَة وَإِعادَةِ التَّنزِيل."
#: themes/default/templates/invitations/invite.mail.ep:8
msgid "Click on the following URL to upload files on Lufi:"
msgstr "اِضغَط عَلَى عُنوَانِ URL التَّالِي قَصدُ رَفعِ مَلَفَّاتٍ عَلَى لوفي:"
#: themes/default/templates/index.html.ep:126
msgid "Click to open the file browser"
msgstr "اِضغَط لِفَتحِ مُتَصَفِّحِ المَلَفَّات"
#: themes/default/templates/delays.html.ep:38 themes/default/templates/invitations/my_invitations.html.ep:80
msgid "Close"
msgstr "إغلاق"
#: themes/default/templates/mail.html.ep:23
msgid "Comma-separated email addresses"
msgstr "عَنَاوِينُ البَريدِ الإلكتُرونِيِّ مُقَسَّمَةٌ-بِفَوَاصِلِ"
#: themes/default/templates/index.html.ep:140
msgid "Compressing zip file…"
msgstr "يَجري ضَغطُ مَلَفِّ zip…"
#: themes/default/templates/partial/index.js.ep:15
msgid "Copy all links to clipboard"
msgstr "نَسخُ جَميعِ الرَّوابِطِ إلى الحافِظَة"
#: themes/default/templates/partial/index.js.ep:18
msgid "Copy to clipboard"
msgstr "النَّسخُ إلى الحافِظَة"
#: lib/Lufi/Controller/Files.pm:507
msgid "Could not delete the file. You are not authenticated."
msgstr "يَتَعَذَّرُ حَذفُ المَلَفّ. أَنتَ غَيرُ مُصَدَّق."
#: lib/Lufi/Controller/Files.pm:489
msgid "Could not find the file. Are you sure of the URL and the token?"
msgstr ""
"يَتَعَذَّرُ حَذفُ المَلَفّ. هَل أنتَ مُتَأَكِّدٌ مِن عُنوانِ URL وَالرَّمز؟"
#: lib/Lufi/Controller/Files.pm:400
msgid "Could not find the file. Are you sure of the URL?"
msgstr ""
"يَتَعَذَّرُ العُثُورُ عَلَى المَلَفّ. هَل أنتَ مُتَأَكِّدٌ مِن عُنوانِ URL؟"
#: themes/default/templates/files.html.ep:29
msgid "Counter"
msgstr "العَدَّاد"
#: themes/default/templates/index.html.ep:100
msgid "Create a zip archive with the files before uploading?"
msgstr "إنشاءُ أرشيفِ zip مِنَ المَلَفَّاتِ قَبلَ الرَّفع؟"
#: themes/default/templates/invitations/my_invitations.html.ep:26
msgid "Created at"
msgstr "أُنشِئ فِي"
#: themes/default/templates/invitations/my_invitations.html.ep:14
msgid "Delete"
msgstr "حَذف"
#: themes/default/templates/files.html.ep:30 themes/default/templates/index.html.ep:90
msgid "Delete at first download?"
msgstr "الحَذفُ عِندَ أوَّلِ تَنزيل؟"
#: themes/default/templates/files.html.ep:19
msgid "Delete selected files"
msgstr "حَذفُ المَلَفَّاتِ المُحَدَّدَة"
#: themes/default/templates/files.html.ep:33 themes/default/templates/partial/index.js.ep:19
msgid "Deletion link"
msgstr "رابِطُ الحَذف"
#: themes/default/templates/delays.html.ep:8
msgid ""
"Don't worry: if a user begins to download the file before the expiration and "
"the download ends after the expiration, he will be able to get the file."
msgstr ""
"لا تَقلَق: إذا بَدَأ المُستخدِمُ فِي تَنزيلِ المَلَفِّ قَبلَ اِنتِهاءِ "
"الصَّلاحِيَّة ثُمَّ اِنتَهَى التَّنزِيلُ بَعدَ اِنتِهاءِ الصَّلاحِيَّة، "
"فَسَوفَ يَكُونُ قادِرًا عَلَى الحُصُولِ عَلَى المَلَفّ."
#: themes/default/templates/partial/index.js.ep:21 themes/default/templates/render.html.ep:28
msgid "Download"
msgstr "تَنزِيل"
#: themes/default/templates/partial/render.js.ep:4
msgid "Download aborted."
msgstr "أُجهِضَ التَّنزيل."
#: themes/default/templates/files.html.ep:28 themes/default/templates/partial/index.js.ep:20
msgid "Download link"
msgstr "رابِط التَّنزيل"
#: themes/default/templates/about.html.ep:10
msgid ""
"Drag and drop files in the appropriate area or use the traditional way to "
"send files and the files will be chunked, encrypted and sent to the server. "
"You will get two links per file: a download link, that you give to the "
"people you want to share the file with and a deletion link, allowing you to "
"delete the file whenever you want."
msgstr ""
"اسحب الملفات إلى الحقل المخصص لذلك أو حدد ملفًا بطريقة كلاسيكية وسيتم قص "
"الملفات إلى أجزاء مشفرة وإرسالها إلى الخادم. ستحصل على روابط لكل ملف: رابط "
"للتنزيل ورابط لحذف الملف عند الحاجة لذلك."
#: themes/default/templates/index.html.ep:122
msgid "Drop files here"
msgstr "اِرمِ المَلَفَّاتَ هُنَا"
#: themes/default/templates/invitations/invite.html.ep:40
msgid "Email address of your guest"
msgstr "عنوان البريد الإلكتروني لضيفك"
#: themes/default/templates/mail.html.ep:39
msgid "Email body"
msgstr "مضمون الرسالة الإلكترونية"
#: themes/default/templates/mail.html.ep:31
msgid "Email subject"
msgstr "موضوع الرسالة الإلكترونية"
#: themes/default/templates/mail.html.ep:25 themes/default/templates/mail.html.ep:27
msgid "Emails"
msgstr "عناوين البريد الإلكترونية"
#: themes/default/templates/partial/index.js.ep:22
msgid "Encrypting part XX1 of XX2"
msgstr "تعمية الجزء XX1 مِن XX2"
#: lib/Lufi/Controller/Files.pm:289
msgid "Error: the file existed but was deleted."
msgstr "خطأ: لقد كان الملف موجودا بالفعل و لكن تم حذفه."
#: lib/Lufi/Controller/Files.pm:369
msgid "Error: the file has not been sent entirely."
msgstr "خطأ: لم يتم إرسال الملف كاملا."
#: lib/Lufi/Controller/Files.pm:379
msgid "Error: unable to find the file. Are you sure of your URL?"
msgstr "خطأ: غير قادر على العثور على الملف. هل أنت متأكد من العنوان؟"
#: themes/default/templates/partial/index.js.ep:23
msgid "Expiration:"
msgstr "تاريخ نهاية الصلاحية:"
#: themes/default/templates/invitations/my_invitations.html.ep:27
msgid "Expire at"
msgstr "تنتهي صلاحيتها في"
#: themes/default/templates/files.html.ep:32
msgid "Expires at"
msgstr "تنتهي صلاحيته في"
#: themes/default/templates/files.html.ep:12
msgid "Export localStorage data"
msgstr "تصدير بيانات localStorage"
#: lib/Lufi/Controller/Files.pm:471
msgid "File deleted"
msgstr "تم حذف الملف"
#: themes/default/templates/files.html.ep:27
msgid "File name"
msgstr "اسم الملف"
#: themes/default/templates/invitations/my_invitations.html.ep:61
msgid "Files"
msgstr "الملفات"
#: themes/default/templates/index.html.ep:80
msgid "Files deleted at first download"
msgstr "تم حذف الملفات عند أول تنزيل"
#: themes/default/templates/invitations/my_invitations.html.ep:28
msgid "Files sent at"
msgstr "تم إرسال الملف في"
#: themes/default/templates/partial/invitations.js.ep:8
msgid "Files sent in invitation XX1 by XX2"
msgstr "الملفات المرسلة في الدعوة XX1 بواسطة XX2"
#: themes/default/templates/partial/render.js.ep:8
msgid "Get the file"
msgstr "تحصّل على الملف"
#: themes/default/templates/about.html.ep:18
msgid ""
"Get the source code on the official repository or on its Github mirror"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:24
msgid "Guest mail"
msgstr "البريد الإلكتروني للضيف"
#. (ucfirst(stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:6
msgid "Hello %1,"
msgstr "سلام %1,"
#: themes/default/templates/invitations/invite.mail.ep:4
msgid "Hello,"
msgstr "سلام،"
#: themes/default/templates/partial/mail.js.ep:35
msgid "Hello,\\n\\nHere's some files I want to share with you:\\n"
msgstr "السلام،\\n\\nها هي بعض الملفات التي أحببتُ مشاركتها معك:\\n"
#: themes/default/templates/mail.html.ep:35
msgid "Here's some files"
msgstr "ها هي بعض الملفات"
#: themes/default/templates/partial/invitations.js.ep:7
msgid "Hide hidden invitations"
msgstr "اخف الدعوات المخفية"
#: themes/default/templates/partial/index.js.ep:25
msgid "Hit Enter, then Ctrl+C to copy all the download links"
msgstr "اضغط على Enter، ثم Ctrl+C لنسخ جميع روابط التحميل"
#: themes/default/templates/partial/index.js.ep:24
msgid "Hit Enter, then Ctrl+C to copy the download link"
msgstr "اضغط على Enter، ثم Ctrl+C لنسخ رابط التحميل"
#: themes/default/templates/about.html.ep:9
msgid "How does it work?"
msgstr "كيف يعمل؟"
#: themes/default/templates/invitations/invite.html.ep:46
msgid "How many days would you like the invitation to be valid?"
msgstr "ما هي مدة صلاحية الدعوة بالأيام؟"
#: themes/default/templates/about.html.ep:16
msgid "How to install the software on my server?"
msgstr "كيف يمكنني تثبيت البرنامج على خادومي؟"
#: themes/default/templates/about.html.ep:12
msgid "How to report an illegal file?"
msgstr "كيف يمكن الإبلاغ عن ملف غير قانوني؟"
#: themes/default/templates/delays.html.ep:7
msgid "If you choose a delay, the file will be deleted after that delay."
msgstr "إذا قمت باختيار تأخير، سيتم حذف الملف بعد ذلك التأخير."
#: themes/default/templates/mail.html.ep:15
msgid ""
"If you send the mail from this server, the links will be sent to the server, "
"which may lower your privacy protection."
msgstr ""
"إذا قمت بإرسال البريد من هذا الخادوم، سيتم إرسال الروابط إلى الخادم، الأمر "
"الذي يمكن أن يقلل من حماية خصوصيتك."
#: themes/default/templates/files.html.ep:14
msgid "Import localStorage data"
msgstr "استيراد بيانات localStorage"
#: themes/default/templates/index.html.ep:53
msgid "Important: more information on delays"
msgstr "هام: مزيد من المعلومات عن التأخير"
#: themes/default/templates/delays.html.ep:5
msgid "Information about delays"
msgstr "معلومات حول التأخير"
#: themes/default/templates/files.html.ep:18 themes/default/templates/invitations/my_invitations.html.ep:12
msgid "Invert selection"
msgstr "عكس الاختيار"
#. ($i->guest_mail, $url)
#: lib/Lufi/Controller/Invitation.pm:171
msgid "Invitation resent to %1.
URL: %2"
msgstr "تم إعادة إرسال الدعوة إلى %1.
الرابط: %2"
#. ($invitation->guest_mail, $url)
#: lib/Lufi/Controller/Invitation.pm:87
msgid "Invitation sent to %1.
URL: %2"
msgstr "تم إرسال الدعوة إلى %1.
الرابط: %2"
#: themes/default/templates/invitations/invite.html.ep:27 themes/default/templates/layouts/default.html.ep:36 themes/default/templates/layouts/default.html.ep:69
msgid "Invite a guest"
msgstr "أدع ضيفا"
#: themes/default/templates/partial/render.js.ep:6
msgid ""
"It seems that the key in your URL is incorrect. Please, verify your URL."
msgstr ""
"ويبدو أن المفتاح في عنوان URL الخاص بك غير صحيح. الرجاء تحقق من عنوان رابطك."
#: themes/default/templates/index.html.ep:12
msgid "Javascript is disabled. You won't be able to use Lufi."
msgstr "الجافا سكريبت غير مفعل. ليس بإمكانك استخدام لوفي Lufi."
#: themes/default/templates/layouts/default.html.ep:44 themes/default/templates/layouts/default.html.ep:46 themes/default/templates/layouts/default.html.ep:77 themes/default/templates/layouts/default.html.ep:79
msgid "Language"
msgstr "اللغة"
#: themes/default/templates/login.html.ep:15
msgid "Login"
msgstr "تسجيل الدخول"
#: themes/default/templates/layouts/default.html.ep:58 themes/default/templates/layouts/default.html.ep:91
msgid "Logout"
msgstr "الخروج"
#: themes/default/templates/about.html.ep:4
msgid "Lufi is a free (as in free speech) file hosting software."
msgstr ""
"لوفي أو Lufi برنامج حر (كما هو الحال في حرية التعبير) لاستضافة الملفات."
#: themes/default/templates/partial/files.js.ep:12
msgid ""
"Lufi recently changed its way to store files information.\\n\\nNo files have "
"been found in the new localStorage location but we found files in the old "
"one.\\nDo you want to import those informations?\\n\\nPlease note that this "
"is the only time that we will ask you this."
msgstr ""
"Lufi مؤاخرا غير طريقه تخزين معلومات الملفات\\n"
"\\n"
"لم يتم العثور على ملفات في موقع التخزين المحلي الجديد ولكننا وجدنا ملفات في "
"الموقع القديم.\\n"
"هل تريد استيراد تلك المعلومات؟\\n"
"\\n"
"يرجى ملاحظة أن هذه هي المرة الوحيدة التي سنطلب فيها منك ذلك."
#: themes/default/templates/files.html.ep:34
msgid "Mail"
msgstr "البريد"
#: themes/default/templates/files.html.ep:3 themes/default/templates/layouts/default.html.ep:34 themes/default/templates/layouts/default.html.ep:67
msgid "My files"
msgstr "ملفاتي"
#: themes/default/templates/invitations/my_invitations.html.ep:5 themes/default/templates/layouts/default.html.ep:37 themes/default/templates/layouts/default.html.ep:70
msgid "My invitations"
msgstr "دعواتي"
#: themes/default/templates/invitations/notification_files_sent.mail.ep:17
msgid ""
"NB: this list includes the list of files that have already been sent to you."
msgstr "ملاحظة: هذه القائمة تتضمن قائمة الملفات التي أرسِلت إليك."
#: themes/default/templates/index.html.ep:115
msgid "Name of the zip file"
msgstr "اسم الملف المضغوط zip"
#. (format_bytes($json->{size})
#: lib/Lufi/Controller/Files.pm:108
msgid "No enough space available on the server for this file (size: %1)."
msgstr "لا توجد مساحة كافية متوفرة على السيرفر لاستضافة الملف (الحجم: %1)."
#: themes/default/templates/partial/files.js.ep:10 themes/default/templates/partial/index.js.ep:28
msgid "No expiration delay"
msgstr "دون تاريخ لنهاية الصلاحية"
#: themes/default/templates/files.html.ep:8
msgid ""
"Only the files sent with this browser will be listed here. This list is "
"stored in localStorage: if you delete your localStorage data, you'll lose "
"this list."
msgstr ""
"سيتم سرد الملفات المرسلة باستخدام هذا المتصفح فقط هنا. يتم تخزين هذه القائمة "
"في التخزين المحلي: إذا قمت بحذف بيانات التخزين المحلية ، فستفقد هذه القائمة."
#: themes/default/templates/index.html.ep:106 themes/default/templates/login.html.ep:21 themes/default/templates/render.html.ep:26
msgid "Password"
msgstr "الكلمة السرية"
#. (config('contact')
#: themes/default/templates/about.html.ep:13
msgid "Please contact the administrator: %1"
msgstr "يرجى الاتصال بالمدير: %1"
#: themes/default/templates/render.html.ep:33
msgid ""
"Please wait while we are getting your file. We first need to download and "
"decrypt all parts before you can get it."
msgstr ""
"يرجى الانتظار من فضلكم حتى نحصل على ملفكم. نحتاج أولا تنزيل وفك تشفير كل "
"الأجزاء قبل أن تتمكنوا مِن الحصول عليه."
#: lib/Lufi/Controller/Auth.pm:38
msgid ""
"Please, check your credentials or your right to access this service: unable "
"to authenticate."
msgstr ""
"من فضلك ، تحقق من بيناتك أو حقك في الوصول إلى هذه الخدمة: غير قادر على "
"التحقق."
#: themes/default/templates/about.html.ep:5
msgid "Privacy"
msgstr "الخصوصية"
#: themes/default/templates/files.html.ep:13
msgid "Purge expired files from localStorage"
msgstr "تطهير الملفات منتهية الصلاحية من مساحة التخزين المحلية"
#: themes/default/templates/invitations/notification_files_sent.mail.ep:20
msgid "Regards,"
msgstr "تحياتي،"
#: themes/default/templates/invitations/invite.mail.ep:15
msgid "Regards."
msgstr "تحياتي."
#: themes/default/templates/layouts/default.html.ep:31 themes/default/templates/layouts/default.html.ep:64
msgid "Report file"
msgstr "الإبلاغ عن ملف"
#: themes/default/templates/invitations/my_invitations.html.ep:15
msgid "Resend invitation mail"
msgstr "أعد إرسال رسالة الدعوة"
#: themes/default/templates/invitations/my_invitations.html.ep:9
msgid "Rows in purple mean that the invitations have expired."
msgstr "الصفوف باللون الأرجواني تعني أن الدعوات انتهت صلاحيتها."
#: themes/default/templates/files.html.ep:9
msgid ""
"Rows in red mean that the files have expired and are no longer available."
msgstr "تعني الأعمدة باللون الأحمر انتهاء صلاحية الملفات وأنها لم تعد متوفرة."
#: themes/default/templates/partial/index.js.ep:26
msgid "Send all links by email"
msgstr "إرسال كافة الروابط عبر البريد الإلكتروني"
#: themes/default/templates/invitations/invite.html.ep:50
msgid "Send the invitation"
msgstr "ابعث الدعوة"
#: themes/default/templates/mail.html.ep:46
msgid "Send with this server"
msgstr "إرسال عبر هذا الخادوم"
#: themes/default/templates/mail.html.ep:47
msgid "Send with your own mail software"
msgstr "إرسال باستخدام برنامج البريد الخاص بك"
#: themes/default/templates/partial/index.js.ep:29
msgid ""
"Sending part XX1 of XX2. Please, be patient, the progress bar can take a "
"while to move."
msgstr ""
"إرسال الجزء XX1 من XX2. يرجى التحلي بالصبر ، قد يستغرق شريط التقدم بعض الوقت "
"للتحرك."
#. (url_for('/')
#: themes/default/templates/partial/mail.js.ep:48
msgid "Share your files in total privacy on %1"
msgstr "شارك ملفاتك بخصوصية تامة على %1"
#: themes/default/templates/invitations/my_invitations.html.ep:13 themes/default/templates/partial/invitations.js.ep:9
msgid "Show hidden invitations"
msgstr "أظهر الدعوات المخفية"
#: themes/default/templates/partial/render.js.ep:11
msgid "Show zip content"
msgstr "أظهر محتوى الملف المضغوط zip"
#: themes/default/templates/layouts/default.html.ep:40 themes/default/templates/layouts/default.html.ep:73 themes/default/templates/login.html.ep:28 themes/default/templates/logout.html.ep:17
msgid "Signin"
msgstr "تسجيل الدخول"
#: lib/Lufi/Controller/Invitation.pm:283 themes/default/templates/invitations/exception.html.ep:16
msgid ""
"Sorry, the invitation doesn’t exist. Are you sure you are on the right URL?"
msgstr "آسف ، الدعوة غير موجودة. هل أنت متأكد مِن صحة العنوان؟"
#: themes/default/templates/index.html.ep:46
msgid "Sorry, the uploading is currently disabled. Please try again later."
msgstr "عذراً، التحميل معطل حاليا. يرجى إعادة المحاولة لاحقًا."
#: lib/Lufi/Controller/Files.pm:82
msgid "Sorry, uploading is disabled."
msgstr "عذرًا ، التحميل معطل."
#: themes/default/templates/invitations/exception.html.ep:7
msgid "Sorry, your invitation has expired or has been deleted."
msgstr "المعذرة، إنّ دعوتك منتهية الصلاحية أو تم حذفها."
#. ($invit->ldap_user_mail)
#: lib/Lufi/Controller/Files.pm:122
msgid ""
"Sorry, your invitation has expired or has been deleted. Please contact %1 to "
"have another invitation."
msgstr ""
"المعذرة ، لقد انتهت مدة صلاحية دعوتك أو قد تم حذفها. الرجاء الاتصال بـ %1 "
"للحصول على دعوة أخرى."
#. ($invitation->ldap_user_mail)
#: lib/Lufi/Controller/Invitation.pm:276
msgid "The URLs of your files have been sent by email to %1."
msgstr "لقد تم إرسال روابط ملفاتك بالبريد الإلكتروني إلى %1."
#: themes/default/templates/about.html.ep:7
msgid ""
"The administrator can only see the file's name, its size and its mimetype "
"(what kind of file it is: video, text, etc.)."
msgstr ""
"يمكن للمدير رؤية اسم الملف فقط ، وحجمه ونوع الملف (أي نوع من الملفات هو: "
"فيديو ، نص ، وما إلى ذلك).)."
#: lib/Lufi/Controller/Mail.pm:53
msgid ""
"The body of the mail must contain at least one URL pointing to a file hosted "
"on this instance."
msgstr ""
"يجب أن يحتوي نص البريد على رابط واحد على الأقل يشير إلى ملف مستضاف على هذا "
"السيرفر."
#: themes/default/templates/partial/files.js.ep:11
msgid "The data has been successfully imported."
msgstr "تم استيراد البيانات بنجاح."
#: lib/Lufi/Controller/Mail.pm:73
msgid "The email body can't be empty."
msgstr "لا يمكن أن يُترك مضمون الرسالة فارغًا."
#: lib/Lufi/Controller/Mail.pm:72
msgid "The email subject can't be empty."
msgstr "لا يمكن أن يُترك موضوع الرسالة فارغًا."
#. ($expire_at, $max_expire_at)
#: lib/Lufi/Controller/Invitation.pm:51
msgid "The expiration delay (%1) is not between 1 and %2 days."
msgstr "مدة انتهاء الصلاحية (%1) ليس محصور بين 1 و %2 أيام."
#: lib/Lufi/Controller/Files.pm:468
msgid "The file has already been deleted"
msgstr "لقد تم حذف الملف مِن قبلُ"
#: themes/default/templates/about.html.ep:6
msgid ""
"The files uploaded on a Lufi instance are encrypted before the upload to the "
"server: the administrator of the server can not see the file's content."
msgstr ""
"يتم تشفير الملفات التي يتم تحميلها على خادوم لوفي قبل تحميلها على الخادم: لا "
"يمكن لمدير الخادم الإطلاع على محتوى الملف."
#. (join(', ', @bad)
#: lib/Lufi/Controller/Mail.pm:68
msgid "The following email addresses are not valid: %1"
msgstr "عناوين البريد الإلكترونية التالية غير صحيحة: %1"
#. ($guest_mail)
#: lib/Lufi/Controller/Invitation.pm:48
msgid "The guest email address (%1) is unvalid."
msgstr "عنوان البريد الإلكتروني للضيف (%1) غير صالح.."
#. ($i->token, $i->guest_mail)
#: lib/Lufi/Controller/Invitation.pm:150
msgid ""
"The invitation %1 can’t be resent: %2 has already sent files.
Please "
"create a new invitation."
msgstr ""
"لا يمكن إعادة إرسال الدعوة %1: لقد أرسل %2 ملفات بالفعل.
الرجاء إنشاء "
"دعوة جديدة."
#. ($i->token)
#: lib/Lufi/Controller/Invitation.pm:130
msgid "The invitation %1 has been deleted."
msgstr "لقد تم حذف الدعوة %1."
#. (stash('user_mail')
#: themes/default/templates/invitations/invite.html.ep:34
msgid "The invitation mail will be send from your email address (%1)."
msgstr "سيتم إرسال بريد الدعوة من عنوان بريدك الإلكتروني (%1)."
#: themes/default/templates/partial/index.js.ep:16
msgid "The link(s) has been copied to your clipboard"
msgstr "لقد تم نسخ الرابط أو الروابط إلى الحافظة"
#. (stash('invitation')
#: themes/default/templates/index.html.ep:30
msgid ""
"The link(s) of your file(s) will automatically be sent by mail to %1 (%2)"
msgstr "سيتم إرسال الرابط (الروابط) لملفاتك تلقائيًا عن طريق البريد إلى %1 (%2)"
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:11
msgid "The links of your file(s) will automatically be sent by mail to %1."
msgstr ""
"سيتم إرسال الارتباط (الارتباطات) لملفاتك تلقائيًا عن طريق البريد إلى %1."
#: lib/Lufi/Controller/Mail.pm:97
msgid "The mail has been sent."
msgstr "لقد تم إرسال الرسالة الإلكترونية."
#: themes/default/templates/about.html.ep:15
msgid ""
"The original (and only for now) author is Luc Didry."
msgstr ""
"المؤلف الأصلي (إلى غاية الآن) هو Luc Didry."
#: lib/Lufi/Controller/Files.pm:236
msgid ""
"The server was unable to find the file record to add your file part to. "
"Please, contact the administrator."
msgstr ""
"لم يتمكن الخادم من العثور على سجل الملف لإضافة جزء ملفك إليه. من فضلك، تواصل "
"بالمسؤول."
#: lib/Lufi/Controller/Files.pm:295
msgid ""
"This file has been deactivated by the admins. Contact them to know why."
msgstr "لقد تم تعطيل الملف من طرف المشرفين. الرجاء الإتصال بهم لمعرفة السبب."
#: themes/default/templates/invitations/my_invitations.html.ep:46 themes/default/templates/partial/invitations.js.ep:6
msgid "This invitation is normally hidden"
msgstr "الدعوة مخفية بشكل عادي"
#. (stash('expires')
#: themes/default/templates/invitations/invite.mail.ep:13
msgid "This invitation is valid until %1."
msgstr "هذه الدعوة صالحة إلى غاية %1."
#: themes/default/templates/delays.html.ep:10
msgid ""
"This server sets limitations according to the file size. The expiration "
"delay of your file will be the minimum between what you choose and the "
"following limitations:"
msgstr ""
"يضع هذا الخادم قيودًا وفقًا لحجم الملف. سيكون تأخير انتهاء صلاحية ملفك هو "
"الحد الأدنى بين ما تختاره والقيود التالية:"
#: themes/default/templates/invitations/my_invitations.html.ep:16
msgid "Toggle visibility"
msgstr "تبديل الرؤية"
#: themes/default/templates/invitations/my_invitations.html.ep:25
msgid "URL"
msgstr "الرابط"
#: themes/default/templates/partial/index.js.ep:17
msgid "Unable to copy the link(s) to your clipboard"
msgstr "تعذر نسخ كافة الروابط إلى الحافظة"
#. ($short)
#: lib/Lufi/Controller/Files.pm:439
msgid ""
"Unable to get counter for %1. The file does not exists. It will be removed "
"from your localStorage."
msgstr ""
"تعذّر الحصول على عداد لـ %1. الملف غير موجود. ستتم إزالته من localStorage."
#. ($short)
#: lib/Lufi/Controller/Files.pm:429
msgid "Unable to get counter for %1. The token is invalid."
msgstr "لا يمكن جلب عداد %1. إن الرمز غير صالح."
#. ($short)
#: lib/Lufi/Controller/Files.pm:449
msgid "Unable to get counter for %1. You are not authenticated."
msgstr "لا يمكن جلب عداد %1. إنك لم تقم بتسجيل الدخول."
#: themes/default/templates/layouts/default.html.ep:33 themes/default/templates/layouts/default.html.ep:66
msgid "Upload files"
msgstr "إرسال ملفات"
#: themes/default/templates/index.html.ep:145
msgid "Upload generated zip file"
msgstr "حمّل الملف المضغوط الذي تم توليده"
#: themes/default/templates/files.html.ep:31
msgid "Uploaded at"
msgstr "أرسِل بتاريخ"
#: themes/default/templates/index.html.ep:153
msgid "Uploaded files"
msgstr "الملفات المُرسَلة"
#: themes/default/templates/partial/index.js.ep:30
msgid "Websocket communication error"
msgstr "خطأ في اتصال ويب سوكيت"
#: themes/default/templates/about.html.ep:3
msgid "What is Lufi?"
msgstr "ماذا نعني بـ لوفي Lufi؟"
#: themes/default/templates/about.html.ep:14
msgid "Who wrote this software?"
msgstr "من قام بتطوير هذا البرنامج؟"
#: themes/default/templates/partial/index.js.ep:13
msgid "XXX file has been added to upload queue."
msgstr "تم إضافة الملف XXX إلى قائمة الإنتظار للتحميل."
#: themes/default/templates/invitations/invite.html.ep:30
msgid ""
"You can invite someone to send you files through this Lufi instance even if "
"they don’t have an account on it."
msgstr ""
"يمكنك دعوة شخص ما لإرسال الملفات إليك من خلال سيرفر Lufi هذا حتى إذا لم يكن "
"لديه حساب عليه."
#: themes/default/templates/about.html.ep:11
msgid ""
"You can see the list of your files by clicking on the \"My files\" link at "
"the top right of this page."
msgstr ""
"يمكنك عرض قائمة ملفاتك عن طريق النقر على وصلة \"ملفاتي\" في الجزء العلوي "
"الأيمن من هذه الصفحة."
#: lib/Lufi/Controller/Mail.pm:42
msgid "You can't add URLs that are not related to this instance."
msgstr "لا يمكنك إضافة روابط غير مرتبطة بهذا السيرفر."
#: themes/default/templates/about.html.ep:8
msgid ""
"You don't need to register yourself to upload files but be aware that, for "
"legal reasons, your IP address will be stored when you send a file. Don't "
"panic, this is normally the case for all sites on which you send files."
msgstr ""
"لا تحتاج إلى تسجيل نفسك لإرسال ملفات ولكن انتبه إلى أنه، لأسباب قانونية، "
"سيتم تخزين عنوان IP الخاص بك عندما تقوم بإرسال ملف. لا داعي للقلق فهذا شيء "
"عادي ينطبق على جميع المواقع التي توفر خدمة إرسال الملفات."
#: themes/default/templates/partial/render.js.ep:10
msgid ""
"You don't seem to have a key in your URL. You won't be able to decrypt the "
"file. Download canceled."
msgstr ""
"يبدو أنك لا تملك مفتاحًا في الرابط الخاص بك. لن تتمكن من فك تشفير الملف. تم "
"إلغاء التنزيل."
#: themes/default/templates/partial/render.js.ep:7
msgid ""
"You have attempted to leave this page. The download will be canceled. Are "
"you sure?"
msgstr ""
"لقد حاولتَ ترك هذه الصفحة. سيتم إلغاء تنزيل الملف الذي أنت بصدد تنزيله. هل "
"أنت متأكد؟"
#: themes/default/templates/partial/index.js.ep:14
msgid ""
"You have attempted to leave this page. The upload will be canceled. Are you "
"sure?"
msgstr ""
"لقد حاولتَ ترك هذه الصفحة. سيتم إلغاء إرسال لملف الذي أنت بصدد تحميله. هل "
"أنت متأكد؟"
#: themes/default/templates/logout.html.ep:14
msgid "You have been successfully logged out."
msgstr "لقد تم تسجيل خروجك بنجاح."
#: lib/Lufi/Controller/Mail.pm:71
msgid "You must give email addresses."
msgstr "يجب إدخال عناوين للبريد الإلكتروني."
#: themes/default/templates/index.html.ep:38
msgid ""
"Your browser does not have enough entropy to generate a strong encryption "
"key. Please wait (it's better if you do things on your computer while "
"waiting)."
msgstr "لا يحتوي متصفحك على قدر كافٍ من الإنتروبيا لإنشاء مفتاح تشفير قوي."
#. (format_bytes($json->{size})
#: lib/Lufi/Controller/Files.pm:95
msgid "Your file is too big: %1 (maximum size allowed: %2)"
msgstr "الملف كبير جدًا: %1 (الحد الأقصى المسموح به للحجم هو: %2)"
#: lib/Lufi/Controller/Files.pm:351
msgid "Your password is not valid. Please refresh the page to retry."
msgstr "الكلمة السرية غير صالحة. يرجى تحديث الصفحة لإعادة المحاولة."
#: themes/default/templates/partial/render.js.ep:12
msgid "Zip content:"
msgstr "محتوى ملف Zip المضغوط:"
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:20
msgid "between %1 and %2, the file will be kept %3 day(s)."
msgstr "بين %1 و %2، سيتم الاحتفاظ بالملف %1 يوم (أيام)."
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:22
msgid "between %1 and %2, the file will be kept forever."
msgstr "بين %1 و%2 ، سيتم الاحتفاظ بالملف إلى الأبد."
#: themes/default/templates/partial/mail.js.ep:42
msgid "deadline: "
msgstr "آخر أجل: "
#: themes/default/templates/partial/invitations.js.ep:5
msgid "expires on XXX"
msgstr "منتهية صلاحيتها في XXX"
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:26
msgid "for %1 and more, the file will be kept %2 day(s)"
msgstr "ل%1 والمزيد ، سيتم الاحتفاظ بالملف %2 يوم (أيام)"
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:28
msgid "for %1 and more, the file will be kept forever."
msgstr "ل%1 والمزيد ، سيتم الاحتفاظ بالملف إلى الأبد."
#: themes/default/templates/index.html.ep:3
msgid "no time limit"
msgstr "بلا حدود"
#: themes/default/templates/index.html.ep:124
msgid "or"
msgstr "أو"
#. ($e->{name}, format_bytes($e->{size})
#: themes/default/templates/invitations/notification_files_sent.mail.ep:12
msgid "— %1 (%2), that will expire on %3"
msgstr "— %1 (%2)، ستنتهي صلاحيته في %3"
#: themes/default/templates/about.html.ep:18
msgid "Get the source code on the official repository or on its Github mirror"
msgstr ""
"احصل علي الكود من المستودع الرسمي او من Github "
#: themes/default/templates/partial/index.js.ep:24
msgid "File uploaded"
msgstr "تم رفع الملف"
#: themes/default/templates/partial/render.js.ep:9
msgid "File downloaded"
msgstr "تم تنزيل الملف"
#: themes/default/templates/about.html.ep:17
msgid "As Lufi is a free software licensed under of the terms of the AGPLv3, you can install it on you own server. Have a look on the Wiki for the procedure."
msgstr ""
"بِما أنَّ لوفي هُوَ بَرنامَجٌ حُرٌّ مُرَخَصٌّ بِمُوجَبِ شُرُوطِ AGPLv3 يُمكِنُكَ"
" تَثبيته عَلَى خادِمِكَ الخاصّ. ألق نَظرَةً عَلَى الويكي "
"لِفِعلِ ذَلِك."
#: themes/default/templates/about.html.ep:20
msgid "Version"
msgstr "الإصدار"
#. (sprintf('%s', stash('version')
#: themes/default/templates/about.html.ep:21
msgid "Latest tag of this instance: %1"
msgstr "آخر علامة على مثيل الخادم هذا: %1"
#. (sprintf('%s', stash('version')
#: themes/default/templates/about.html.ep:22
msgid "Latest commit of this instance: %1"
msgstr "آخر مراجعة على مثيل الخادم هذا: %1"
#: themes/default/templates/partial/render.js.ep:13
msgid "Unable to download the file: too much unsuccessful attempts to open a websocket. Please, contact the administrator."
msgstr ""
"استحالة تنزيل الملف: العديد من المحاولات غير الناجحة لفتح websocket. الرجاء "
"الاتصال بالمدير."
================================================
FILE: themes/default/lib/Lufi/I18N/br.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2023-11-23 12:06+0000\n"
"Last-Translator: Ewen \n"
"Language-Team: Breton \n"
"Language: br\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=5; plural=(n % 10 == 1 && n % 100 != 11 && n % 100 != "
"71 && n % 100 != 91) ? 0 : ((n % 10 == 2 && n % 100 != 12 && n % 100 != 72 "
"&& n % 100 != 92) ? 1 : ((((n % 10 == 3 || n % 10 == 4) || n % 10 == 9) && ("
"n % 100 < 10 || n % 100 > 19) && (n % 100 < 70 || n % 100 > 79) && (n % 100 "
"< 90 || n % 100 > 99)) ? 2 : ((n != 0 && n % 1000000 == 0) ? 3 : 4)));\n"
"X-Generator: Weblate 5.2\n"
#. ($delay)
#. (max_delay)
#: themes/default/templates/index.html.ep:56 themes/default/templates/index.html.ep:65 themes/default/templates/index.html.ep:66
msgid "%1 days"
msgstr "%1 devezh"
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:2
msgid "%1 invites you to send him/her files"
msgstr "Pedet oc'h gant %1 da gas dezhi/dezhañ oh fichennaoueg"
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:6
msgid "%1 invites you to send him/her files through Lufi."
msgstr "Pedet oc'h gant %1 da gas dezhi/dezhañ oh fichennaoueg dre Lufi."
#. (stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:4
msgid "%1 sent you files"
msgstr "%1 o deus kaset deoc'h fichennaouegoù"
#. (stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:8
msgid "%1 used your invitation to send you files:"
msgstr "Implijet eo bet oh pedadenn gant %1 :"
#: lib/Lufi/Controller/Invitation.pm:172 lib/Lufi/Controller/Invitation.pm:85 themes/default/templates/invitations/my_invitations.html.ep:51 themes/default/templates/invitations/my_invitations.html.ep:52 themes/default/templates/invitations/my_invitations.html.ep:53 themes/default/templates/invitations/notification_files_sent.mail.ep:12
msgid "%A %d %B %Y at %T"
msgstr "%A %d %B %Y da %T"
#: themes/default/templates/partial/index.js.ep:28
msgid "(max size: XXX)"
msgstr "(pouezh maks : XXX)"
#: themes/default/templates/index.html.ep:5
msgid "1 year"
msgstr "1 bloazh"
#: themes/default/templates/index.html.ep:4 themes/default/templates/index.html.ep:65
msgid "24 hours"
msgstr "24 eur"
#: themes/default/templates/partial/mail.js.ep:40
msgid ":"
msgstr ":"
#: themes/default/templates/render.html.ep:42
msgid "Abort"
msgstr "Nullañ"
#: themes/default/templates/layouts/default.html.ep:53 themes/default/templates/layouts/default.html.ep:86
msgid "About"
msgstr "Diwar benn"
#: themes/default/templates/index.html.ep:107
msgid "Add a password to file(s)"
msgstr "Ouzhpenn ur ger kuzh d'ho fichennaoueg(où)"
#: themes/default/templates/mail.html.ep:16
msgid "Adding URLs not related to this Lufi instance to the mail body or subject is prohibited."
msgstr ""
"Ouzhpennañ liammoù diliam gant al lec'hienn Lufi-mañ e-barzh sujed pe korf "
"oh mail a zo difennet."
#: themes/default/templates/partial/invitations.js.ep:3
msgid "Are you sure you want to delete the selected invitations?"
msgstr "Ah sur oc'h dilemel ar pedadennoù choazet ?"
#: themes/default/templates/partial/invitations.js.ep:4
msgid "Are you sure you want to resend the invitation mail for the selected invitations?"
msgstr "Ah sur oc'h adkas ho mail pediñ evit ar pedadenn choazet ?"
#: themes/default/templates/about.html.ep:17
msgid "As Lufi is a free software licensed under of the terms of the AGPLv3, you can install it on you own server. Have a look on the Wiki for the procedure."
msgstr ""
"Lufi a zo ur poellad frank diwar aotre-implijout an AGPLv3, posubl eo deoc'h "
"staliañ anezhañ war oh servijer. Kit war omp Wiki da "
"wellout penaos."
#. (stash('nbslices')
#: themes/default/templates/partial/render.js.ep:10
msgid "Asking for file part XX1 of %1"
msgstr "Oh goulenn evit ar fichennaoueg lodenn XX1 diwar %1"
#: themes/default/templates/about.html.ep:23
msgid "Back to homepage"
msgstr "Distro war ar bajenn degemer"
#: lib/Lufi/Controller/Mail.pm:26
msgid "Bad CSRF token!"
msgstr "Token CSRF fall !"
#: lib/Lufi/Controller/Auth.pm:27 lib/Lufi/Controller/Auth.pm:49
msgid "Bad CSRF token."
msgstr "Token CSRF fall."
#: themes/default/templates/partial/render.js.ep:5
msgid "Click here to refresh the page and restart the download."
msgstr "Klik amañ da adkarg ar bajenn ha krog ar pellgargañ adare."
#: themes/default/templates/invitations/invite.mail.ep:8
msgid "Click on the following URL to upload files on Lufi:"
msgstr "Klik war al liammoù dindan da gas fichennaouegoù gant Lufi :"
#: themes/default/templates/index.html.ep:126
msgid "Click to open the file browser"
msgstr "Klik da zigor an ergerzher fichennoù"
#: themes/default/templates/delays.html.ep:42 themes/default/templates/invitations/my_invitations.html.ep:80
msgid "Close"
msgstr "Seriñ"
#: themes/default/templates/mail.html.ep:23
msgid "Comma-separated email addresses"
msgstr "Mailoù dispartiet gant virgulennoù"
#: themes/default/templates/index.html.ep:140
msgid "Compressing zip file…"
msgstr "Gwaskañ en ur zip fichenn…"
#: themes/default/templates/partial/index.js.ep:15
msgid "Copy all links to clipboard"
msgstr "Kopiañ holl ar liammoù d'ar gwask-paper"
#: themes/default/templates/partial/index.js.ep:18
msgid "Copy to clipboard"
msgstr "Kopiañ d'ar gwask-paper"
#: lib/Lufi/Controller/Files.pm:501
msgid "Could not delete the file. You are not authenticated."
msgstr "N'eo ket be posubl dilemel ar fichenn. N'och ket luget."
#: lib/Lufi/Controller/Files.pm:483
msgid "Could not find the file. Are you sure of the URL and the token?"
msgstr "Ne oa ket posubl kavout ar fichenn. Ah sur oc'h eus oh liamm ha token ?"
#: lib/Lufi/Controller/Files.pm:394
msgid "Could not find the file. Are you sure of the URL?"
msgstr "Ne oa ket posubl kavout ar fichenn. Ah sur oc'h eus oh liamm ?"
#: themes/default/templates/files.html.ep:29
msgid "Counter"
msgstr "Kont"
#: themes/default/templates/index.html.ep:100
msgid "Create a zip archive with the files before uploading?"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:26
msgid "Created at"
msgstr "Krouet d'an/d'ar"
#: themes/default/templates/invitations/my_invitations.html.ep:14
msgid "Delete"
msgstr "Dilemel"
#: themes/default/templates/files.html.ep:30 themes/default/templates/index.html.ep:90
msgid "Delete at first download?"
msgstr ""
#: themes/default/templates/files.html.ep:19
msgid "Delete selected files"
msgstr ""
#: themes/default/templates/files.html.ep:33 themes/default/templates/partial/index.js.ep:19
msgid "Deletion link"
msgstr ""
#: themes/default/templates/delays.html.ep:9
msgid "Don't worry: if a user begins to download the file before the expiration and the download ends after the expiration, he will be able to get the file."
msgstr ""
#: themes/default/templates/partial/index.js.ep:21 themes/default/templates/render.html.ep:28
msgid "Download"
msgstr ""
#: themes/default/templates/partial/render.js.ep:4
msgid "Download aborted."
msgstr ""
#: themes/default/templates/files.html.ep:28 themes/default/templates/partial/index.js.ep:20
msgid "Download link"
msgstr ""
#: themes/default/templates/about.html.ep:10
msgid "Drag and drop files in the appropriate area or use the traditional way to send files and the files will be chunked, encrypted and sent to the server. You will get two links per file: a download link, that you give to the people you want to share the file with and a deletion link, allowing you to delete the file whenever you want."
msgstr ""
#: themes/default/templates/index.html.ep:122
msgid "Drop files here"
msgstr ""
#: themes/default/templates/invitations/invite.html.ep:40
msgid "Email address of your guest"
msgstr ""
#: themes/default/templates/mail.html.ep:39
msgid "Email body"
msgstr ""
#: themes/default/templates/mail.html.ep:31
msgid "Email subject"
msgstr ""
#: themes/default/templates/mail.html.ep:25 themes/default/templates/mail.html.ep:27
msgid "Emails"
msgstr ""
#: themes/default/templates/partial/index.js.ep:22
msgid "Encrypting part XX1 of XX2"
msgstr ""
#: lib/Lufi/Controller/Files.pm:283
msgid "Error: the file existed but was deleted."
msgstr ""
#: lib/Lufi/Controller/Files.pm:363
msgid "Error: the file has not been sent entirely."
msgstr ""
#: lib/Lufi/Controller/Files.pm:373
msgid "Error: unable to find the file. Are you sure of your URL?"
msgstr ""
#: themes/default/templates/partial/index.js.ep:23
msgid "Expiration:"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:27
msgid "Expire at"
msgstr ""
#: themes/default/templates/files.html.ep:32
msgid "Expires at"
msgstr ""
#: themes/default/templates/files.html.ep:12
msgid "Export localStorage data"
msgstr ""
#: lib/Lufi/Controller/Files.pm:465
msgid "File deleted"
msgstr ""
#: themes/default/templates/partial/render.js.ep:9
msgid "File downloaded"
msgstr ""
#: themes/default/templates/files.html.ep:27
msgid "File name"
msgstr ""
#: themes/default/templates/partial/index.js.ep:24
msgid "File uploaded"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:61
msgid "Files"
msgstr ""
#: themes/default/templates/index.html.ep:80
msgid "Files deleted at first download"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:28
msgid "Files sent at"
msgstr ""
#: themes/default/templates/partial/invitations.js.ep:8
msgid "Files sent in invitation XX1 by XX2"
msgstr ""
#: themes/default/templates/partial/render.js.ep:8
msgid "Get the file"
msgstr ""
#: themes/default/templates/about.html.ep:18
msgid "Get the source code on the official repository or on its Github mirror"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:24
msgid "Guest mail"
msgstr ""
#. (ucfirst(stash('invitation')
#: themes/default/templates/invitations/notification_files_sent.mail.ep:6
msgid "Hello %1,"
msgstr ""
#: themes/default/templates/invitations/invite.mail.ep:4
msgid "Hello,"
msgstr ""
#: themes/default/templates/partial/mail.js.ep:35
msgid "Hello,\\n\\nHere's some files I want to share with you:\\n"
msgstr ""
#: themes/default/templates/mail.html.ep:35
msgid "Here's some files"
msgstr ""
#: themes/default/templates/partial/invitations.js.ep:7
msgid "Hide hidden invitations"
msgstr ""
#: themes/default/templates/partial/index.js.ep:26
msgid "Hit Enter, then Ctrl+C to copy all the download links"
msgstr ""
#: themes/default/templates/partial/index.js.ep:25
msgid "Hit Enter, then Ctrl+C to copy the download link"
msgstr ""
#: themes/default/templates/about.html.ep:9
msgid "How does it work?"
msgstr ""
#: themes/default/templates/invitations/invite.html.ep:46
msgid "How many days would you like the invitation to be valid?"
msgstr ""
#: themes/default/templates/about.html.ep:16
msgid "How to install the software on my server?"
msgstr ""
#: themes/default/templates/about.html.ep:12
msgid "How to report an illegal file?"
msgstr ""
#: themes/default/templates/delays.html.ep:8
msgid "If you choose a delay, the file will be deleted after that delay."
msgstr ""
#: themes/default/templates/mail.html.ep:15
msgid "If you send the mail from this server, the links will be sent to the server, which may lower your privacy protection."
msgstr ""
#: themes/default/templates/files.html.ep:14
msgid "Import localStorage data"
msgstr ""
#: themes/default/templates/index.html.ep:53
msgid "Important: more information on delays"
msgstr ""
#: themes/default/templates/delays.html.ep:5
msgid "Information about delays"
msgstr ""
#: themes/default/templates/files.html.ep:18 themes/default/templates/invitations/my_invitations.html.ep:12
msgid "Invert selection"
msgstr ""
#. ($i->guest_mail, $url)
#: lib/Lufi/Controller/Invitation.pm:184
msgid "Invitation resent to %1.
URL: %2"
msgstr ""
#. ($invitation->guest_mail, $url)
#: lib/Lufi/Controller/Invitation.pm:88
msgid "Invitation sent to %1.
URL: %2"
msgstr ""
#: themes/default/templates/invitations/invite.html.ep:27 themes/default/templates/layouts/default.html.ep:36 themes/default/templates/layouts/default.html.ep:69
msgid "Invite a guest"
msgstr ""
#: themes/default/templates/partial/render.js.ep:6
msgid "It seems that the key in your URL is incorrect. Please, verify your URL."
msgstr ""
#: themes/default/templates/index.html.ep:12
msgid "Javascript is disabled. You won't be able to use Lufi."
msgstr ""
#: themes/default/templates/layouts/default.html.ep:44 themes/default/templates/layouts/default.html.ep:46 themes/default/templates/layouts/default.html.ep:77 themes/default/templates/layouts/default.html.ep:79
msgid "Language"
msgstr ""
#. (sprintf('%s', stash('version')
#: themes/default/templates/about.html.ep:22
msgid "Latest commit of this instance: %1"
msgstr ""
#. (sprintf('%s', stash('version')
#: themes/default/templates/about.html.ep:21
msgid "Latest tag of this instance: %1"
msgstr ""
#: themes/default/templates/login.html.ep:15
msgid "Login"
msgstr ""
#: themes/default/templates/layouts/default.html.ep:58 themes/default/templates/layouts/default.html.ep:91
msgid "Logout"
msgstr ""
#: themes/default/templates/about.html.ep:4
msgid "Lufi is a free (as in free speech) file hosting software."
msgstr ""
#: themes/default/templates/partial/files.js.ep:12
msgid "Lufi recently changed its way to store files information.\\n\\nNo files have been found in the new localStorage location but we found files in the old one.\\nDo you want to import those informations?\\n\\nPlease note that this is the only time that we will ask you this."
msgstr ""
#: themes/default/templates/files.html.ep:34
msgid "Mail"
msgstr ""
#: themes/default/templates/files.html.ep:3 themes/default/templates/layouts/default.html.ep:34 themes/default/templates/layouts/default.html.ep:67
msgid "My files"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:5 themes/default/templates/layouts/default.html.ep:37 themes/default/templates/layouts/default.html.ep:70
msgid "My invitations"
msgstr ""
#: themes/default/templates/invitations/notification_files_sent.mail.ep:17
msgid "NB: this list includes the list of files that have already been sent to you."
msgstr ""
#: themes/default/templates/index.html.ep:115
msgid "Name of the zip file"
msgstr ""
#. (format_bytes($json->{size})
#: lib/Lufi/Controller/Files.pm:109
msgid "No enough space available on the server for this file (size: %1)."
msgstr ""
#: themes/default/templates/partial/files.js.ep:10 themes/default/templates/partial/index.js.ep:29
msgid "No expiration delay"
msgstr ""
#: themes/default/templates/files.html.ep:8
msgid "Only the files sent with this browser will be listed here. This list is stored in localStorage: if you delete your localStorage data, you'll lose this list."
msgstr ""
#: themes/default/templates/index.html.ep:106 themes/default/templates/login.html.ep:21 themes/default/templates/render.html.ep:26
msgid "Password"
msgstr ""
#. (config('contact')
#: themes/default/templates/about.html.ep:13
msgid "Please contact the administrator: %1"
msgstr ""
#: themes/default/templates/render.html.ep:33
msgid "Please wait while we are getting your file. We first need to download and decrypt all parts before you can get it."
msgstr ""
#: lib/Lufi/Controller/Auth.pm:38
msgid "Please, check your credentials or your right to access this service: unable to authenticate."
msgstr ""
#: themes/default/templates/about.html.ep:5
msgid "Privacy"
msgstr ""
#: themes/default/templates/files.html.ep:13
msgid "Purge expired files from localStorage"
msgstr ""
#: themes/default/templates/invitations/notification_files_sent.mail.ep:20
msgid "Regards,"
msgstr ""
#: themes/default/templates/invitations/invite.mail.ep:15
msgid "Regards."
msgstr ""
#: themes/default/templates/layouts/default.html.ep:31 themes/default/templates/layouts/default.html.ep:64
msgid "Report file"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:15
msgid "Resend invitation mail"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:9
msgid "Rows in purple mean that the invitations have expired."
msgstr ""
#: themes/default/templates/files.html.ep:9
msgid "Rows in red mean that the files have expired and are no longer available."
msgstr ""
#: themes/default/templates/partial/index.js.ep:27
msgid "Send all links by email"
msgstr ""
#: themes/default/templates/invitations/invite.html.ep:50
msgid "Send the invitation"
msgstr ""
#: themes/default/templates/mail.html.ep:47
msgid "Send with this server"
msgstr ""
#: themes/default/templates/mail.html.ep:49
msgid "Send with your own mail software"
msgstr ""
#: themes/default/templates/partial/index.js.ep:30
msgid "Sending part XX1 of XX2. Please, be patient, the progress bar can take a while to move."
msgstr ""
#. (url_for('/')
#: themes/default/templates/partial/mail.js.ep:49
msgid "Share your files in total privacy on %1"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:13 themes/default/templates/partial/invitations.js.ep:9
msgid "Show hidden invitations"
msgstr ""
#: themes/default/templates/partial/render.js.ep:12
msgid "Show zip content"
msgstr ""
#: themes/default/templates/layouts/default.html.ep:40 themes/default/templates/layouts/default.html.ep:73 themes/default/templates/login.html.ep:28 themes/default/templates/logout.html.ep:17
msgid "Signin"
msgstr ""
#: lib/Lufi/Controller/Invitation.pm:302 themes/default/templates/invitations/exception.html.ep:16
msgid "Sorry, the invitation doesn’t exist. Are you sure you are on the right URL?"
msgstr ""
#: themes/default/templates/index.html.ep:46
msgid "Sorry, the uploading is currently disabled. Please try again later."
msgstr ""
#: lib/Lufi/Controller/Files.pm:82
msgid "Sorry, uploading is disabled."
msgstr ""
#: themes/default/templates/invitations/exception.html.ep:7
msgid "Sorry, your invitation has expired or has been deleted."
msgstr ""
#. ($invit->ldap_user_mail)
#: lib/Lufi/Controller/Files.pm:123
msgid "Sorry, your invitation has expired or has been deleted. Please contact %1 to have another invitation."
msgstr ""
#. ($invitation->ldap_user_mail)
#: lib/Lufi/Controller/Invitation.pm:295
msgid "The URLs of your files have been sent by email to %1."
msgstr ""
#: themes/default/templates/about.html.ep:7
msgid "The administrator can only see the file's name, its size and its mimetype (what kind of file it is: video, text, etc.)."
msgstr ""
#: lib/Lufi/Controller/Mail.pm:67
msgid "The body of the mail must contain at least one URL pointing to a file hosted on this instance."
msgstr ""
#: themes/default/templates/partial/files.js.ep:11
msgid "The data has been successfully imported."
msgstr ""
#: lib/Lufi/Controller/Mail.pm:87
msgid "The email body can't be empty."
msgstr ""
#: lib/Lufi/Controller/Mail.pm:86
msgid "The email subject can't be empty."
msgstr ""
#. ($expire_at, $max_expire_at)
#: lib/Lufi/Controller/Invitation.pm:52
msgid "The expiration delay (%1) is not between 1 and %2 days."
msgstr ""
#: lib/Lufi/Controller/Files.pm:462
msgid "The file has already been deleted"
msgstr ""
#: themes/default/templates/about.html.ep:6
msgid "The files uploaded on a Lufi instance are encrypted before the upload to the server: the administrator of the server can not see the file's content."
msgstr ""
#. (join(', ', @bad)
#: lib/Lufi/Controller/Mail.pm:82
msgid "The following email addresses are not valid: %1"
msgstr ""
#. ($guest_mail)
#: lib/Lufi/Controller/Invitation.pm:49
msgid "The guest email address (%1) is unvalid."
msgstr ""
#. ($i->token, $c->current_user->{username})
#: lib/Lufi/Controller/Invitation.pm:136
msgid "The invitation %1 can’t be deleted: it wasn’t created by you (%2)."
msgstr ""
#. ($i->token, $i->guest_mail)
#: lib/Lufi/Controller/Invitation.pm:163
msgid "The invitation %1 can’t be resent: %2 has already sent files.
Please create a new invitation."
msgstr ""
#. ($i->token, $c->current_user->{username})
#: lib/Lufi/Controller/Invitation.pm:187
msgid "The invitation %1 can’t be resent: it wasn’t created by you (%2)."
msgstr ""
#. ($i->token)
#: lib/Lufi/Controller/Invitation.pm:134
msgid "The invitation %1 has been deleted."
msgstr ""
#. (stash('user_mail')
#: themes/default/templates/invitations/invite.html.ep:34
msgid "The invitation mail will be send from your email address (%1)."
msgstr ""
#: themes/default/templates/partial/index.js.ep:16
msgid "The link(s) has been copied to your clipboard"
msgstr ""
#. (stash('invitation')
#: themes/default/templates/index.html.ep:30
msgid "The link(s) of your file(s) will automatically be sent by mail to %1 (%2)"
msgstr ""
#. (stash('ldap_user')
#: themes/default/templates/invitations/invite.mail.ep:11
msgid "The links of your file(s) will automatically be sent by mail to %1."
msgstr ""
#: lib/Lufi/Controller/Mail.pm:111
msgid "The mail has been sent."
msgstr ""
#: themes/default/templates/about.html.ep:15
msgid "The original (and only for now) author is Luc Didry."
msgstr ""
#: lib/Lufi/Controller/Files.pm:230
msgid "The server was unable to find the file record to add your file part to. Please, contact the administrator."
msgstr ""
#: lib/Lufi/Controller/Files.pm:289
msgid "This file has been deactivated by the admins. Contact them to know why."
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:46 themes/default/templates/partial/invitations.js.ep:6
msgid "This invitation is normally hidden"
msgstr ""
#. (stash('expires')
#: themes/default/templates/invitations/invite.mail.ep:13
msgid "This invitation is valid until %1."
msgstr ""
#: themes/default/templates/delays.html.ep:13
msgid "This server sets limitations according to the file size. The expiration delay of your file will be the minimum between what you choose and the following limitations:"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:16
msgid "Toggle visibility"
msgstr ""
#: themes/default/templates/invitations/my_invitations.html.ep:25
msgid "URL"
msgstr ""
#: themes/default/templates/partial/index.js.ep:17
msgid "Unable to copy the link(s) to your clipboard"
msgstr ""
#: themes/default/templates/partial/render.js.ep:13
msgid "Unable to download the file: too much unsuccessful attempts to open a websocket. Please, contact the administrator."
msgstr ""
#. ($short)
#: lib/Lufi/Controller/Files.pm:433
msgid "Unable to get counter for %1. The file does not exists. It will be removed from your localStorage."
msgstr ""
#. ($short)
#: lib/Lufi/Controller/Files.pm:423
msgid "Unable to get counter for %1. The token is invalid."
msgstr ""
#. ($short)
#: lib/Lufi/Controller/Files.pm:443
msgid "Unable to get counter for %1. You are not authenticated."
msgstr ""
#: themes/default/templates/layouts/default.html.ep:33 themes/default/templates/layouts/default.html.ep:66
msgid "Upload files"
msgstr ""
#: themes/default/templates/index.html.ep:145
msgid "Upload generated zip file"
msgstr ""
#: themes/default/templates/files.html.ep:31
msgid "Uploaded at"
msgstr ""
#: themes/default/templates/index.html.ep:153
msgid "Uploaded files"
msgstr ""
#: themes/default/templates/about.html.ep:20
msgid "Version"
msgstr ""
#: themes/default/templates/partial/index.js.ep:31
msgid "Websocket communication error"
msgstr ""
#: themes/default/templates/about.html.ep:3
msgid "What is Lufi?"
msgstr ""
#: themes/default/templates/about.html.ep:14
msgid "Who wrote this software?"
msgstr ""
#: themes/default/templates/partial/index.js.ep:13
msgid "XXX file has been added to upload queue."
msgstr ""
#: themes/default/templates/invitations/invite.html.ep:30
msgid "You can invite someone to send you files through this Lufi instance even if they don’t have an account on it."
msgstr ""
#: themes/default/templates/about.html.ep:11
msgid "You can see the list of your files by clicking on the \"My files\" link at the top right of this page."
msgstr ""
#. ($orig_uri)
#: lib/Lufi/Controller/Mail.pm:43 lib/Lufi/Controller/Mail.pm:59
msgid "You can't add URLs that are not related to this instance (%1)."
msgstr ""
#: themes/default/templates/about.html.ep:8
msgid "You don't need to register yourself to upload files but be aware that, for legal reasons, your IP address will be stored when you send a file. Don't panic, this is normally the case for all sites on which you send files."
msgstr ""
#: themes/default/templates/partial/render.js.ep:11
msgid "You don't seem to have a key in your URL. You won't be able to decrypt the file. Download canceled."
msgstr ""
#: themes/default/templates/partial/render.js.ep:7
msgid "You have attempted to leave this page. The download will be canceled. Are you sure?"
msgstr ""
#: themes/default/templates/partial/index.js.ep:14
msgid "You have attempted to leave this page. The upload will be canceled. Are you sure?"
msgstr ""
#: themes/default/templates/logout.html.ep:14
msgid "You have been successfully logged out."
msgstr ""
#: lib/Lufi/Controller/Mail.pm:85
msgid "You must give email addresses."
msgstr ""
#: themes/default/templates/index.html.ep:38
msgid "Your browser does not have enough entropy to generate a strong encryption key. Please wait (it's better if you do things on your computer while waiting)."
msgstr ""
#. (format_bytes($json->{size})
#: lib/Lufi/Controller/Files.pm:95
msgid "Your file is too big: %1 (maximum size allowed: %2)"
msgstr ""
#: lib/Lufi/Controller/Files.pm:345
msgid "Your password is not valid. Please refresh the page to retry."
msgstr ""
#: themes/default/templates/partial/render.js.ep:14
msgid "Zip content:"
msgstr ""
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:24
msgid "between %1 and %2, the file will be kept %3 day(s)."
msgstr ""
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:26
msgid "between %1 and %2, the file will be kept forever."
msgstr ""
#: themes/default/templates/partial/mail.js.ep:42
msgid "deadline: "
msgstr ""
#: themes/default/templates/partial/invitations.js.ep:5
msgid "expires on XXX"
msgstr ""
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:30
msgid "for %1 and more, the file will be kept %2 day(s)"
msgstr ""
#. (format_bytes($keys[$i])
#: themes/default/templates/delays.html.ep:32
msgid "for %1 and more, the file will be kept forever."
msgstr ""
#: themes/default/templates/index.html.ep:3
msgid "no time limit"
msgstr ""
#: themes/default/templates/index.html.ep:124
msgid "or"
msgstr ""
#. ($e->{name}, format_bytes($e->{size})
#: themes/default/templates/invitations/notification_files_sent.mail.ep:12
msgid "— %1 (%2), that will expire on %3"
msgstr ""
================================================
FILE: themes/default/lib/Lufi/I18N/ca.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
# xd