├── .debian
├── compat
├── source
│ └── format
├── rules
├── changelog
├── postinst
├── control
├── apache.conf
└── copyright
├── templates
├── layout.html.php
├── components
│ ├── footer.html.php
│ ├── header.html.php
│ ├── common.html.php
│ └── navtab.html.php
├── compress.html.php
├── metadata.html.php
└── index.html.php
├── public
├── .htaccess
├── favicon.ico
├── favicon.png
├── index.php
├── favicon-compress.ico
├── favicon-compress.png
├── favicon-metadata.ico
├── favicon-metadata.png
├── favicon-organization.ico
├── favicon-organization.png
├── vendor
│ └── fonts
│ │ ├── Caveat-Regular.ttf
│ │ ├── Tajawal-Medium.ttf
│ │ ├── bootstrap-icons.woff
│ │ ├── bootstrap-icons.woff2
│ │ └── FiraSans-MediumItalic.ttf
├── css
│ ├── pdf_viewer.css
│ └── app.css
├── js
│ └── compress.js
├── favicon.svg
├── logo.svg
└── logo-small.svg
├── locale
├── application_d1c7cd04648b474242845a0e66cc38b2.pot
├── ar
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── br
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── de
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── es
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── eu
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── fr
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── gl
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── it
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── ko
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── nl
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── oc
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── pl
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── ro
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── ta
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
├── tr
│ └── LC_MESSAGES
│ │ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ │ └── application.mo
└── kab
│ └── LC_MESSAGES
│ ├── application_d1c7cd04648b474242845a0e66cc38b2.mo
│ └── application.mo
├── tests
└── files
│ ├── tampon.png
│ ├── document.pdf
│ ├── document_ar.pdf
│ └── signature.png
├── config
├── php.ini
├── config.ini.tpl
├── apache.conf
└── config.ini.example
├── .gitignore
├── .editorconfig
├── lib
├── Compression.class.php
├── Image2SVG.class.php
├── GPGCryptography.class.php
├── NSSCryptography.class.php
└── PDFSignature.class.php
├── vendor
└── fatfree
│ ├── composer.json
│ ├── README.md
│ ├── sessionadapter.php
│ ├── f3.php
│ ├── code.css
│ ├── web
│ ├── google
│ │ ├── recaptcha.php
│ │ └── staticmap.php
│ ├── geo.php
│ ├── oauth2.php
│ ├── pingback.php
│ └── openid.php
│ ├── log.php
│ ├── test.php
│ ├── flash.php
│ ├── bcrypt.php
│ ├── magic.php
│ ├── db
│ ├── mongo.php
│ ├── jig.php
│ ├── jig
│ │ └── session.php
│ ├── mongo
│ │ └── session.php
│ └── sql
│ │ └── session.php
│ ├── matrix.php
│ ├── utf.php
│ ├── session.php
│ ├── basket.php
│ ├── audit.php
│ └── auth.php
├── entrypoint.sh
├── .github
├── FUNDING.yml
└── workflows
│ └── docker.yaml
├── Dockerfile
├── tools
└── create_nss_certs.sh
├── Makefile
└── installation.md
/.debian/compat:
--------------------------------------------------------------------------------
1 | 9
2 |
--------------------------------------------------------------------------------
/templates/layout.html.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (quilt)
2 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | FallbackResource /index.php
2 |
--------------------------------------------------------------------------------
/.debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | %:
3 | dh $@
4 |
--------------------------------------------------------------------------------
/locale/application_d1c7cd04648b474242845a0e66cc38b2.pot:
--------------------------------------------------------------------------------
1 | application.pot
--------------------------------------------------------------------------------
/locale/ar/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/br/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/de/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/es/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/eu/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/fr/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/gl/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/it/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/ko/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/nl/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/oc/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/pl/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/ro/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/ta/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/tr/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/locale/kab/LC_MESSAGES/application_d1c7cd04648b474242845a0e66cc38b2.mo:
--------------------------------------------------------------------------------
1 | application.mo
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | run();
--------------------------------------------------------------------------------
/tests/files/tampon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/tests/files/tampon.png
--------------------------------------------------------------------------------
/tests/files/document.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/tests/files/document.pdf
--------------------------------------------------------------------------------
/public/favicon-compress.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-compress.ico
--------------------------------------------------------------------------------
/public/favicon-compress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-compress.png
--------------------------------------------------------------------------------
/public/favicon-metadata.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-metadata.ico
--------------------------------------------------------------------------------
/public/favicon-metadata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-metadata.png
--------------------------------------------------------------------------------
/tests/files/document_ar.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/tests/files/document_ar.pdf
--------------------------------------------------------------------------------
/tests/files/signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/tests/files/signature.png
--------------------------------------------------------------------------------
/public/favicon-organization.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-organization.ico
--------------------------------------------------------------------------------
/public/favicon-organization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/favicon-organization.png
--------------------------------------------------------------------------------
/locale/ar/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/ar/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/br/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/br/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/de/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/de/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/es/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/es/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/eu/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/eu/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/fr/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/fr/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/gl/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/gl/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/it/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/it/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/kab/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/kab/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/ko/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/ko/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/nl/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/nl/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/oc/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/oc/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/pl/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/pl/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/ro/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/ro/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/ta/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/ta/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/locale/tr/LC_MESSAGES/application.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/locale/tr/LC_MESSAGES/application.mo
--------------------------------------------------------------------------------
/public/vendor/fonts/Caveat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/vendor/fonts/Caveat-Regular.ttf
--------------------------------------------------------------------------------
/public/vendor/fonts/Tajawal-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/vendor/fonts/Tajawal-Medium.ttf
--------------------------------------------------------------------------------
/config/php.ini:
--------------------------------------------------------------------------------
1 | upload_max_filesize = $UPLOAD_MAX_FILESIZE
2 | post_max_size = $POST_MAX_SIZE
3 | max_file_uploads = $MAX_FILE_UPLOADS
4 |
--------------------------------------------------------------------------------
/public/vendor/fonts/bootstrap-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/vendor/fonts/bootstrap-icons.woff
--------------------------------------------------------------------------------
/public/vendor/fonts/bootstrap-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/vendor/fonts/bootstrap-icons.woff2
--------------------------------------------------------------------------------
/public/vendor/fonts/FiraSans-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/24eme/signaturepdf/HEAD/public/vendor/fonts/FiraSans-MediumItalic.ttf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | package.json
3 | node_modules
4 | tests/downloads/
5 | config/config.ini
6 | public/css/app-specific.css
7 | *~
8 | .idea/
9 |
--------------------------------------------------------------------------------
/.debian/changelog:
--------------------------------------------------------------------------------
1 | ~#PKGNAME#~ (~#VERSION#~-~#RELEASE#~) UNRELEASED; urgency=low
2 |
3 | * Please check the
4 | https://github.com/~#VENDOR#~/~#PROJECT#~
5 | commit history
6 |
7 | -- ~#MAINTAINER#~ ~#DATE#~
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | indent_style = space
10 | indent_size = 4
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
16 | [Makefile]
17 | indent_style = tab
18 |
--------------------------------------------------------------------------------
/lib/Compression.class.php:
--------------------------------------------------------------------------------
1 | =7.2"
8 | },
9 | "autoload": {
10 | "classmap": ["."]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.debian/postinst:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # postinstall script for signature pdf
3 |
4 | set -e
5 |
6 | # Droits sur les fichiers
7 | chgrp -R www-data /~#LIBPATH#~data
8 | chgrp -R www-data /~#LIBPATH#~pdf
9 |
10 | chmod -R g+rwx /~#LIBPATH#~data
11 | chmod -R g+rwx /~#LIBPATH#~pdf
12 |
13 | # Activation de la conf apache2
14 | mkdir -p /etc/apache2/conf-available
15 | ln -sf ../../~#PKGNAME#~/apache.conf /etc/apache2/conf-available/signaturepdf.conf
16 |
17 | a2enconf signaturepdf
18 | systemctl reload apache2
19 |
--------------------------------------------------------------------------------
/.debian/control:
--------------------------------------------------------------------------------
1 | Source: ~#PKGNAME#~
2 | Maintainer: ~#MAINTAINER#~
3 | Section: php
4 | Priority: optional
5 | Build-Depends: debhelper (>= 9)
6 | Standards-Version: 3.9.7
7 | Homepage: https://github.com/~#VENDOR#~/~#PROJECT#~
8 | Vcs-Git: https://github.com/~#VENDOR#~/~#PROJECT#~.git
9 |
10 | Package: ~#PKGNAME#~
11 | Provides: php-~#PROJECT#~
12 | Architecture: all
13 | Depends: php (>= 7.4.0), pdftk, librsvg2-bin, imagemagick, potrace, ghostscript, locales, ${misc:Depends}
14 | Description: Outils de signature PDF en ligne libre et open-source
15 |
--------------------------------------------------------------------------------
/config/config.ini.tpl:
--------------------------------------------------------------------------------
1 | [globals]
2 |
3 | ; Path to which stored pdf to activate the mode of sharing a signature to several.
4 | ; To deactivate this mode, simply do not configure it or leave it empty
5 | PDF_STORAGE_PATH=${PDF_STORAGE_PATH}
6 |
7 | ; Disable organization tab and routes
8 | DISABLE_ORGANIZATION=${DISABLE_ORGANIZATION}
9 |
10 | ; Manage demo link pdf : true (by default, show), false (hide), or custom link
11 | PDF_DEMO_LINK=${PDF_DEMO_LINK}
12 |
13 | ; Encryption activation (default activation if GPG is installed)
14 | PDF_STORAGE_ENCRYPTION=${PDF_STORAGE_ENCRYPTION}
15 |
--------------------------------------------------------------------------------
/.debian/apache.conf:
--------------------------------------------------------------------------------
1 | # signaturepdf default Apache configuration
2 |
3 | Alias /signaturepdf /~#LIBPATH#~public
4 |
5 | DocumentRoot /~#LIBPATH#~public
6 | AddDefaultCharset UTF-8
7 |
8 |
9 | Options SymLinksIfOwnerMatch
10 | DirectoryIndex index.php
11 | AllowOverride All
12 |
13 | = 2.3>
14 | Require all granted
15 |
16 |
17 | Order Deny,Allow
18 | Allow from all
19 |
20 |
21 |
22 | LogLevel warn
23 | ErrorLog /var/log/apache2/ssp_error.log
24 | CustomLog /var/log/apache2/ssp_access.log combined
25 |
--------------------------------------------------------------------------------
/config/apache.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerName ${SERVERNAME}
3 | DocumentRoot /usr/local/signaturepdf/public
4 | DirectoryIndex index.php
5 |
6 | AddDefaultCharset UTF-8
7 |
8 |
9 | AllowOverride All
10 | = 2.3>
11 | Require all granted
12 |
13 |
14 | Order Deny,Allow
15 | Allow from all
16 |
17 |
18 |
19 | LogLevel warn
20 | ErrorLog /var/log/apache2/ssp_error.log
21 | CustomLog /var/log/apache2/ssp_access.log combined
22 |
23 |
--------------------------------------------------------------------------------
/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf
4 | envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini
5 | envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini
6 |
7 | sed -i "/$DEFAULT_LANGUAGE/s/^# //g" /etc/locale.gen && locale-gen
8 | export LANG=$DEFAULT_LANGUAGE
9 | export LANGUAGE=$DEFAULT_LANGUAGE
10 | export LC_ALL=$DEFAULT_LANGUAGE
11 |
12 | if [[ -n $PDF_STORAGE_PATH ]] ; then
13 | mkdir -p "$PDF_STORAGE_PATH"
14 | chown www-data:www-data "$PDF_STORAGE_PATH"
15 | fi
16 |
17 | apache2-foreground
18 |
--------------------------------------------------------------------------------
/lib/Image2SVG.class.php:
--------------------------------------------------------------------------------
1 |
2 | Signature PDF - : []
3 |
4 | -
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.debian/copyright:
--------------------------------------------------------------------------------
1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: ~#PROJECT#~
3 | Source: https://github.com/~#VENDOR#~/~#PROJECT#~
4 |
5 | Files: *
6 | Copyright: Copyright 2021-2024 ~#MAINTAINER#~
7 | License: AGPL-3
8 |
9 | License: AGPL-3
10 | This program is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU Affero General Public License as
12 | published by the Free Software Foundation, either version 3 of the
13 | License, or (at your option) any later version.
14 | This program is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | GNU General Public License for more details.
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see or
20 | /usr/share/common-licenses/AGPL-3
21 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: SignaturePDF # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.2-apache
2 |
3 | ENV SERVERNAME=localhost
4 | ENV UPLOAD_MAX_FILESIZE=24M
5 | ENV POST_MAX_SIZE=24M
6 | ENV MAX_FILE_UPLOADS=201
7 | ENV PDF_STORAGE_PATH=/data
8 | ENV DISABLE_ORGANIZATION=false
9 | ENV DEFAULT_LANGUAGE=fr_FR.UTF-8
10 | ENV PDF_STORAGE_ENCRYPTION=false
11 |
12 | RUN apt update && \
13 | apt install -y vim locales gettext-base librsvg2-bin pdftk imagemagick potrace ghostscript gpg && \
14 | docker-php-ext-install gettext && \
15 | rm -rf /var/lib/apt/lists/*
16 |
17 | COPY . /usr/local/signaturepdf
18 |
19 | RUN envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
20 | envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \
21 | envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini && \
22 | a2enmod rewrite && a2ensite signaturepdf
23 |
24 | WORKDIR /usr/local/signaturepdf
25 |
26 | CMD /usr/local/signaturepdf/entrypoint.sh
27 |
--------------------------------------------------------------------------------
/vendor/fatfree/sessionadapter.php:
--------------------------------------------------------------------------------
1 | _handler = $handler;
13 | }
14 |
15 | public function close(): bool
16 | {
17 | return $this->_handler->close();
18 | }
19 |
20 | public function destroy(string $id): bool
21 | {
22 | return $this->_handler->destroy($id);
23 | }
24 |
25 | #[ReturnTypeWillChange]
26 | public function gc(int $max_lifetime): int
27 | {
28 | return $this->_handler->gc($max_lifetime);
29 | }
30 |
31 | public function open(string $path, string $name): bool
32 | {
33 | return $this->_handler->open($path, $name);
34 | }
35 |
36 | #[ReturnTypeWillChange]
37 | public function read(string $id): string
38 | {
39 | return $this->_handler->read($id);
40 | }
41 |
42 | public function write(string $id, string $data): bool
43 | {
44 | return $this->_handler->write($id, $data);
45 | }
46 | }
--------------------------------------------------------------------------------
/vendor/fatfree/f3.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Legacy mode enabler
24 | class F3 {
25 |
26 | static
27 | //! Framework instance
28 | $fw;
29 |
30 | /**
31 | * Forward function calls to framework
32 | * @return mixed
33 | * @param $func callback
34 | * @param $args array
35 | **/
36 | static function __callstatic($func,array $args) {
37 | if (!self::$fw)
38 | self::$fw=Base::instance();
39 | return call_user_func_array([self::$fw,$func],$args);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/vendor/fatfree/code.css:
--------------------------------------------------------------------------------
1 | code{word-wrap:break-word;color:black}.comment,.doc_comment,.ml_comment{color:dimgray;font-style:italic}.variable{color:blueviolet}.const,.constant_encapsed_string,.class_c,.dir,.file,.func_c,.halt_compiler,.line,.method_c,.lnumber,.dnumber{color:crimson}.string,.and_equal,.boolean_and,.boolean_or,.concat_equal,.dec,.div_equal,.inc,.is_equal,.is_greater_or_equal,.is_identical,.is_not_equal,.is_not_identical,.is_smaller_or_equal,.logical_and,.logical_or,.logical_xor,.minus_equal,.mod_equal,.mul_equal,.ns_c,.ns_separator,.or_equal,.plus_equal,.sl,.sl_equal,.sr,.sr_equal,.xor_equal,.start_heredoc,.end_heredoc,.object_operator,.paamayim_nekudotayim{color:black}.abstract,.array,.array_cast,.as,.break,.case,.catch,.class,.clone,.continue,.declare,.default,.do,.echo,.else,.elseif,.empty.enddeclare,.endfor,.endforach,.endif,.endswitch,.endwhile,.eval,.exit,.extends,.final,.for,.foreach,.function,.global,.goto,.if,.implements,.include,.include_once,.instanceof,.interface,.isset,.list,.namespace,.new,.print,.private,.public,.protected,.require,.require_once,.return,.static,.switch,.throw,.try,.unset,.use,.var,.while{color:royalblue}.open_tag,.open_tag_with_echo,.close_tag{color:orange}.ini_section{color:black}.ini_key{color:royalblue}.ini_value{color:crimson}.xml_tag{color:dodgerblue}.xml_attr{color:blueviolet}.xml_data{color:red}.section{color:black}.directive{color:blue}.data{color:dimgray}
2 |
--------------------------------------------------------------------------------
/templates/components/header.html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | " rel="stylesheet">
6 |
7 | " rel="stylesheet">
8 |
9 |
10 |
11 |
26 |
--------------------------------------------------------------------------------
/config/config.ini.example:
--------------------------------------------------------------------------------
1 | [globals]
2 |
3 | ; Path to which stored pdf to activate the mode of sharing a signature to several.
4 | ; To deactivate this mode, simply do not configure it or leave it empty
5 | PDF_STORAGE_PATH=/path/to/folder
6 |
7 | ; Disable organization tab and routes
8 | ;DISABLE_ORGANIZATION=false
9 |
10 | ; Manage demo link pdf : true (by default, show with default link), false (hide), or custom link
11 | ;PDF_DEMO_LINK=true
12 |
13 | ; Metadata default fields
14 | ;METADATA_DEFAULT_FIELDS[metadata_key].type = "text"
15 |
16 | ;Declare your public domain here if you can't handle location rewriting through your reverse proxy.
17 | ;REVERSE_PROXY_URL=http://127.0.0.1:8080
18 |
19 | ; Encryption activation (need GPG is installed)
20 | ;PDF_STORAGE_ENCRYPTION=false
21 |
22 | ;NSS3 configuration (used to sign pdf with pdfsig)
23 | ;NSS3_DIRECTORY=/path/to/nss3
24 | ;NSS3_PASSWORD="my secret password"
25 | ;NSS3_NICK="certificate nick"
26 |
27 | ; Authorize these IP to use debug mode (separate IP adresses with space ' ')
28 | ;ADMIN_AUTHORIZED_IP=
29 |
30 | ; Enable the edition of local server pdf metadata
31 | ; PDF_LOCAL_PATH=/path/to/pdf/metadata/edition
32 |
33 | ; Custom options for the signature page
34 | ;[signature]
35 |
36 | ; Enable custom retention period for shared PDF
37 | ; This override the default retention periods
38 | ; Warning: Text on the right of the colon will be translated according to the .po files
39 | ;retention[+1 year]="for one year"
40 | ;retention[+6 months]="for six months"
41 | ;retention[+1 month]="for one month"
42 | ;retention[+1 week]="for one week"
43 | ;retention[+1 day]="for one day"
44 | ;retention[+1 hour]="for one hour"
45 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yaml:
--------------------------------------------------------------------------------
1 | name: Docker publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | tags:
8 | - '*.*.*'
9 |
10 | jobs:
11 | docker:
12 | environment: docker-registry
13 | runs-on: ubuntu-latest
14 | steps:
15 | -
16 | name: Checkout
17 | uses: actions/checkout@v2
18 | -
19 | name: Docker meta
20 | id: meta
21 | uses: docker/metadata-action@v3
22 | with:
23 | # list of Docker images to use as base name for tags
24 | images: registry.hub.docker.com/${{ secrets.DOCKER_REGISTRY_USERNAME }}/signaturepdf
25 | # generate Docker tags based on the following events/attributes
26 | tags: |
27 | type=ref,event=branch
28 | type=ref,event=pr
29 | type=semver,pattern={{version}}
30 | type=semver,pattern={{major}}.{{minor}}
31 | type=semver,pattern={{major}}
32 | -
33 | name: Set up QEMU
34 | uses: docker/setup-qemu-action@v1
35 | -
36 | name: Set up Docker Buildx
37 | uses: docker/setup-buildx-action@v1
38 | -
39 | name: Login to registry
40 | uses: docker/login-action@v1
41 | with:
42 | registry: registry.hub.docker.com
43 | username: xgaia
44 | password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
45 | -
46 | name: Build and push
47 | uses: docker/build-push-action@v2
48 | with:
49 | context: .
50 | push: true
51 | tags: ${{ steps.meta.outputs.tags }}
52 | labels: ${{ steps.meta.outputs.labels }}
53 | secrets: |
54 | GIT_AUTH_TOKEN=${{ secrets.DOCKER_REGISTRY_TOKEN }}
55 |
--------------------------------------------------------------------------------
/templates/components/common.html.php:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
36 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/google/recaptcha.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web\Google;
24 |
25 | //! Google ReCAPTCHA v2 plug-in
26 | class Recaptcha {
27 |
28 | const
29 | //! API URL
30 | URL_Recaptcha='https://www.google.com/recaptcha/api/siteverify';
31 |
32 | /**
33 | * Verify reCAPTCHA response
34 | * @param string $secret
35 | * @param string $response
36 | * @return bool
37 | **/
38 | static function verify($secret,$response=NULL) {
39 | $fw=\Base::instance();
40 | if (!isset($response))
41 | $response=$fw->{'POST.g-recaptcha-response'};
42 | if (!$response)
43 | return FALSE;
44 | $web=\Web::instance();
45 | $out=$web->request(self::URL_Recaptcha,[
46 | 'method'=>'POST',
47 | 'content'=>http_build_query([
48 | 'secret'=>$secret,
49 | 'response'=>$response,
50 | 'remoteip'=>$fw->IP
51 | ]),
52 | ]);
53 | return isset($out['body']) &&
54 | ($json=json_decode($out['body'],TRUE)) &&
55 | isset($json['success']) && $json['success'];
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/google/staticmap.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web\Google;
24 |
25 | //! Google Static Maps API v2 plug-in
26 | class StaticMap {
27 |
28 | const
29 | //! API URL
30 | URL_Static='http://maps.googleapis.com/maps/api/staticmap';
31 |
32 | protected
33 | //! Query arguments
34 | $query=array();
35 |
36 | /**
37 | * Specify API key-value pair via magic call
38 | * @return object
39 | * @param $func string
40 | * @param $args array
41 | **/
42 | function __call($func,array $args) {
43 | $this->query[]=array($func,$args[0]);
44 | return $this;
45 | }
46 |
47 | /**
48 | * Generate map
49 | * @return string
50 | **/
51 | function dump() {
52 | $fw=\Base::instance();
53 | $web=\Web::instance();
54 | $out='';
55 | return ($req=$web->request(
56 | self::URL_Static.'?'.array_reduce(
57 | $this->query,
58 | function($out,$item) {
59 | return ($out.=($out?'&':'').
60 | urlencode($item[0]).'='.urlencode($item[1]));
61 | }
62 | ))) && $req['body']?$req['body']:FALSE;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/tools/create_nss_certs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eo pipefail
4 |
5 | nss_dir=$1
6 | nss_pass=$2
7 | nss_nick=$3
8 | signaturepdf_url=$4
9 |
10 | if ! test "$signaturepdf_url"; then
11 | echo "Usage:" 1>&2 ;
12 | echo " $0 " 1>&2 ;
13 | exit 1;
14 | fi
15 |
16 | if ! test -d "$nss_dir"; then
17 | echo "ERROR: nss_dir \"$nss_dir\" should exist" 1>&2 ;
18 | exit 2;
19 | fi
20 | if echo "$nss_nick" | grep -q -F "."; then
21 | echo "ERROR: $nss_nick should not contain . " 1>&2 ;
22 | exit 3;
23 | fi
24 |
25 | set -u
26 |
27 | signaturepdf_domain=$(echo "$signaturepdf_url" | sed -e 's@https*://@@' -e 's@/.*@@')
28 | signaturepdf_path=$(echo "$signaturepdf_url" | sed -e 's@https*://@@' -e "s@.*$signaturepdf_domain/*@@")
29 | signaturepdf_dc=$(echo "$signaturepdf_domain" | tr '.' '\n' | sed 's/^/DC=/' | tr '\n' ',' | sed 's/,$//')
30 | if test "$signaturepdf_path"; then
31 | signaturepdf_dc="DC=/${signaturepdf_path},${signaturepdf_dc}";
32 | fi
33 |
34 | echo "$nss_pass" > /tmp/nss.$$.tmp
35 | certutil -N -f /tmp/nss.$$.tmp -d "$nss_dir"
36 |
37 | echo "$RANDOM CACert $(date) $$ $RANDOM" | shasum > /tmp/nss_noise.$$.tmp
38 | certutil -S -s "CN=PDF Sign CA Cert,$signaturepdf_dc" -n "PDFSignCA" -z /tmp/nss_noise.$$.tmp -x -t "CT,CT,CT" -v 120 -m 1234 -d "$nss_dir" -f /tmp/nss.$$.tmp
39 |
40 | echo "$RANDOM $signaturepdf_dc $(date) $$ $RANDOM" | shasum >> /tmp/nss_noise.$$.tmp
41 | certutil -S -s "CN=$nss_nick,$signaturepdf_dc" -n "$nss_nick" -c "PDFSignCA" -z /tmp/nss_noise.$$.tmp -t ",," -m 730 -d "$nss_dir" -f /tmp/nss.$$.tmp
42 |
43 | echo "Certs created :"
44 | echo "==============="
45 | certutil -f /tmp/nss.$$.tmp -d "$nss_dir" -L
46 | echo "Private keys created :"
47 | echo "======================"
48 | certutil -f /tmp/nss.$$.tmp -d "$nss_dir" -K
49 |
50 | rm /tmp/nss.$$.tmp /tmp/nss_noise.$$.tmp
51 |
--------------------------------------------------------------------------------
/vendor/fatfree/log.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Custom logger
24 | class Log {
25 |
26 | protected
27 | //! File name
28 | $file;
29 |
30 | /**
31 | * Write specified text to log file
32 | * @return string
33 | * @param $text string
34 | * @param $format string
35 | **/
36 | function write($text,$format='r') {
37 | $fw=Base::instance();
38 | foreach (preg_split('/\r?\n|\r/',trim($text)) as $line)
39 | $fw->write(
40 | $this->file,
41 | date($format).
42 | (isset($_SERVER['REMOTE_ADDR'])?
43 | (' ['.$_SERVER['REMOTE_ADDR'].
44 | (($fwd=filter_var($fw->get('HEADERS.X-Forwarded-For'),
45 | FILTER_VALIDATE_IP))?(' ('.$fwd.')'):'')
46 | .']'):'').' '.
47 | trim($line).PHP_EOL,
48 | TRUE
49 | );
50 | }
51 |
52 | /**
53 | * Erase log
54 | * @return NULL
55 | **/
56 | function erase() {
57 | @unlink($this->file);
58 | }
59 |
60 | /**
61 | * Instantiate class
62 | * @param $file string
63 | **/
64 | function __construct($file) {
65 | $fw=Base::instance();
66 | if (!is_dir($dir=$fw->LOGS))
67 | mkdir($dir,Base::MODE,TRUE);
68 | $this->file=$dir.$file;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/vendor/fatfree/test.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Unit test kit
24 | class Test {
25 |
26 | //@{ Reporting level
27 | const
28 | FLAG_False=0,
29 | FLAG_True=1,
30 | FLAG_Both=2;
31 | //@}
32 |
33 | protected
34 | //! Test results
35 | $data=[],
36 | //! Success indicator
37 | $passed=TRUE,
38 | //! Reporting level
39 | $level;
40 |
41 | /**
42 | * Return test results
43 | * @return array
44 | **/
45 | function results() {
46 | return $this->data;
47 | }
48 |
49 | /**
50 | * Return FALSE if at least one test case fails
51 | * @return bool
52 | **/
53 | function passed() {
54 | return $this->passed;
55 | }
56 |
57 | /**
58 | * Evaluate condition and save test result
59 | * @return object
60 | * @param $cond bool
61 | * @param $text string
62 | **/
63 | function expect($cond,$text=NULL) {
64 | $out=(bool)$cond;
65 | if ($this->level==$out || $this->level==self::FLAG_Both) {
66 | $data=['status'=>$out,'text'=>$text,'source'=>NULL];
67 | foreach (debug_backtrace() as $frame)
68 | if (isset($frame['file'])) {
69 | $data['source']=Base::instance()->
70 | fixslashes($frame['file']).':'.$frame['line'];
71 | break;
72 | }
73 | $this->data[]=$data;
74 | }
75 | if (!$out && $this->passed)
76 | $this->passed=FALSE;
77 | return $this;
78 | }
79 |
80 | /**
81 | * Append message to test results
82 | * @return NULL
83 | * @param $text string
84 | **/
85 | function message($text) {
86 | $this->expect(TRUE,$text);
87 | }
88 |
89 | /**
90 | * Class constructor
91 | * @return NULL
92 | * @param $level int
93 | **/
94 | function __construct($level=self::FLAG_Both) {
95 | $this->level=$level;
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/templates/components/navtab.html.php:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/vendor/fatfree/flash.php:
--------------------------------------------------------------------------------
1 |
12 | * https://github.com/ikkez/F3-Sugar/
13 | *
14 | * @version 1.0.2
15 | * @date: 13.12.2020
16 | * @since: 21.01.2015
17 | **/
18 |
19 | class Flash extends \Prefab {
20 |
21 | /** @var \Base */
22 | protected $f3;
23 |
24 | /** @var array */
25 | protected $msg;
26 |
27 | /** @var array */
28 | protected $key;
29 |
30 | /**
31 | * Flash constructor.
32 | * @param string $key
33 | */
34 | public function __construct($key='flash') {
35 | $this->f3 = \Base::instance();
36 | $this->msg = &$this->f3->ref('SESSION.'.$key.'.msg');
37 | $this->key = &$this->f3->ref('SESSION.'.$key.'.key');
38 | }
39 |
40 | /**
41 | * add a message to the stack
42 | * @param $text
43 | * @param string $status
44 | */
45 | public function addMessage($text,$status='info') {
46 | $this->msg[] = ['text'=>$text,'status'=>$status];
47 | }
48 |
49 | /**
50 | * dump all messages and clear them
51 | * @return array
52 | */
53 | public function getMessages() {
54 | $out = $this->msg;
55 | $this->clearMessages();
56 | return $out ?: [];
57 | }
58 |
59 | /**
60 | * reset message stack
61 | */
62 | public function clearMessages() {
63 | $this->msg = [];
64 | }
65 |
66 | /**
67 | * check if there messages in the stack
68 | * @return bool
69 | */
70 | public function hasMessages() {
71 | return !empty($this->msg);
72 | }
73 |
74 | /**
75 | * set a flash key
76 | * @param $key
77 | * @param bool $val
78 | */
79 | public function setKey($key,$val=TRUE) {
80 | $this->key[$key] = $val;
81 | }
82 |
83 | /**
84 | * get and clear a flash key, if it's existing
85 | * @param $key
86 | * @return mixed|null
87 | */
88 | public function getKey($key) {
89 | $out = NULL;
90 | if ($this->hasKey($key)) {
91 | $out = $this->key[$key];
92 | unset($this->key[$key]);
93 | }
94 | return $out;
95 | }
96 |
97 | /**
98 | * check if there's a flash key existing
99 | * @param $key
100 | * @return bool
101 | */
102 | public function hasKey($key) {
103 | return ($this->key && array_key_exists($key, $this->key));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/vendor/fatfree/bcrypt.php:
--------------------------------------------------------------------------------
1 | .
19 | *
20 | **/
21 |
22 | /**
23 | * Lightweight password hashing library (PHP 5.5+ only)
24 | * @deprecated Use http://php.net/manual/en/ref.password.php instead
25 | **/
26 | class Bcrypt extends Prefab {
27 |
28 | //@{ Error messages
29 | const
30 | E_CostArg='Invalid cost parameter',
31 | E_SaltArg='Salt must be at least 22 alphanumeric characters';
32 | //@}
33 |
34 | //! Default cost
35 | const
36 | COST=10;
37 |
38 | /**
39 | * Generate bcrypt hash of string
40 | * @return string|FALSE
41 | * @param $pw string
42 | * @param $salt string
43 | * @param $cost int
44 | **/
45 | function hash($pw,$salt=NULL,$cost=self::COST) {
46 | if ($cost<4 || $cost>31)
47 | user_error(self::E_CostArg,E_USER_ERROR);
48 | $len=22;
49 | if ($salt) {
50 | if (!preg_match('/^[[:alnum:]\.\/]{'.$len.',}$/',$salt))
51 | user_error(self::E_SaltArg,E_USER_ERROR);
52 | }
53 | else {
54 | $raw=16;
55 | $iv='';
56 | if (!$iv && extension_loaded('openssl'))
57 | $iv=openssl_random_pseudo_bytes($raw);
58 | if (!$iv)
59 | for ($i=0;$i<$raw;++$i)
60 | $iv.=chr(mt_rand(0,255));
61 | $salt=str_replace('+','.',base64_encode($iv));
62 | }
63 | $salt=substr($salt,0,$len);
64 | $hash=crypt($pw,sprintf('$2y$%02d$',$cost).$salt);
65 | return strlen($hash)>13?$hash:FALSE;
66 | }
67 |
68 | /**
69 | * Check if password is still strong enough
70 | * @return bool
71 | * @param $hash string
72 | * @param $cost int
73 | **/
74 | function needs_rehash($hash,$cost=self::COST) {
75 | list($pwcost)=sscanf($hash,"$2y$%d$");
76 | return $pwcost<$cost;
77 | }
78 |
79 | /**
80 | * Verify password against hash using timing attack resistant approach
81 | * @return bool
82 | * @param $pw string
83 | * @param $hash string
84 | **/
85 | function verify($pw,$hash) {
86 | $val=crypt($pw,$hash);
87 | $len=strlen($val);
88 | if ($len!=strlen($hash) || $len<14)
89 | return FALSE;
90 | $out=0;
91 | for ($i=0;$i<$len;++$i)
92 | $out|=(ord($val[$i])^ord($hash[$i]));
93 | return $out===0;
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/public/css/pdf_viewer.css:
--------------------------------------------------------------------------------
1 | .textLayer{
2 | position:absolute;
3 | text-align:initial;
4 | inset:0;
5 | overflow:clip;
6 | opacity:1;
7 | line-height:1;
8 | -webkit-text-size-adjust:none;
9 | -moz-text-size-adjust:none;
10 | text-size-adjust:none;
11 | forced-color-adjust:none;
12 | transform-origin:0 0;
13 | caret-color:CanvasText;
14 | z-index:0;
15 | }
16 |
17 | .textLayer.highlighting{
18 | touch-action:none;
19 | }
20 |
21 | .textLayer :is(span,br){
22 | color:transparent;
23 | position:absolute;
24 | white-space:pre;
25 | cursor:text;
26 | transform-origin:0% 0%;
27 | }
28 |
29 | .textLayer > :not(.markedContent),.textLayer .markedContent span:not(.markedContent){
30 | z-index:1;
31 | }
32 |
33 | .textLayer span.markedContent{
34 | top:0;
35 | height:0;
36 | }
37 |
38 | .textLayer .highlight{
39 | --highlight-bg-color:rgb(180 0 170 / 0.25);
40 | --highlight-selected-bg-color:rgb(0 100 0 / 0.25);
41 | --highlight-backdrop-filter:none;
42 | --highlight-selected-backdrop-filter:none;
43 | }
44 |
45 | @media screen and (forced-colors: active){
46 |
47 | .textLayer .highlight{
48 | --highlight-bg-color:transparent;
49 | --highlight-selected-bg-color:transparent;
50 | --highlight-backdrop-filter:var(--hcm-highlight-filter);
51 | --highlight-selected-backdrop-filter:var(
52 | --hcm-highlight-selected-filter
53 | );
54 | }
55 | }
56 |
57 | .textLayer .highlight{
58 |
59 | margin:-1px;
60 | padding:1px;
61 | background-color:var(--highlight-bg-color);
62 | -webkit-backdrop-filter:var(--highlight-backdrop-filter);
63 | backdrop-filter:var(--highlight-backdrop-filter);
64 | border-radius:4px;
65 | }
66 |
67 | .appended:is(.textLayer .highlight){
68 | position:initial;
69 | }
70 |
71 | .begin:is(.textLayer .highlight){
72 | border-radius:4px 0 0 4px;
73 | }
74 |
75 | .end:is(.textLayer .highlight){
76 | border-radius:0 4px 4px 0;
77 | }
78 |
79 | .middle:is(.textLayer .highlight){
80 | border-radius:0;
81 | }
82 |
83 | .selected:is(.textLayer .highlight){
84 | background-color:var(--highlight-selected-bg-color);
85 | -webkit-backdrop-filter:var(--highlight-selected-backdrop-filter);
86 | backdrop-filter:var(--highlight-selected-backdrop-filter);
87 | }
88 |
89 | .textLayer ::-moz-selection{
90 | background:rgba(0 0 255 / 0.25);
91 | background:color-mix(in srgb, AccentColor, transparent 75%);
92 | }
93 |
94 | .textLayer ::selection{
95 | background:rgba(0 0 255 / 0.25);
96 | background:color-mix(in srgb, AccentColor, transparent 75%);
97 | }
98 |
99 | .textLayer br::-moz-selection{
100 | background:transparent;
101 | }
102 |
103 | .textLayer br::selection{
104 | background:transparent;
105 | }
106 |
107 | .textLayer .endOfContent{
108 | display:block;
109 | position:absolute;
110 | inset:100% 0 0;
111 | z-index:0;
112 | cursor:default;
113 | -webkit-user-select:none;
114 | -moz-user-select:none;
115 | user-select:none;
116 | }
117 |
118 | .textLayer.selecting .endOfContent{
119 | top:0;
120 | }
121 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/geo.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web;
24 |
25 | //! Geo plug-in
26 | class Geo extends \Prefab {
27 |
28 | /**
29 | * Return information about specified Unix time zone
30 | * @return array
31 | * @param $zone string
32 | **/
33 | function tzinfo($zone) {
34 | $ref=new \DateTimeZone($zone);
35 | $loc=$ref->getLocation();
36 | $trn=$ref->getTransitions($now=time(),$now);
37 | $out=[
38 | 'offset'=>$ref->
39 | getOffset(new \DateTime('now',new \DateTimeZone('UTC')))/3600,
40 | 'country'=>$loc['country_code'],
41 | 'latitude'=>$loc['latitude'],
42 | 'longitude'=>$loc['longitude'],
43 | 'dst'=>$trn[0]['isdst']
44 | ];
45 | unset($ref);
46 | return $out;
47 | }
48 |
49 | /**
50 | * Return geolocation data based on specified/auto-detected IP address
51 | * @return array|FALSE
52 | * @param $ip string
53 | **/
54 | function location($ip=NULL) {
55 | $fw=\Base::instance();
56 | $web=\Web::instance();
57 | if (!$ip)
58 | $ip=$fw->IP;
59 | $public=filter_var($ip,FILTER_VALIDATE_IP,
60 | FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|
61 | FILTER_FLAG_NO_RES_RANGE|FILTER_FLAG_NO_PRIV_RANGE);
62 | if (function_exists('geoip_db_avail') &&
63 | geoip_db_avail(GEOIP_CITY_EDITION_REV1) &&
64 | $out=@geoip_record_by_name($ip)) {
65 | $out['request']=$ip;
66 | $out['region_code']=$out['region'];
67 | $out['region_name']='';
68 | if (!empty($out['country_code']) && !empty($out['region']))
69 | $out['region_name']=geoip_region_name_by_code(
70 | $out['country_code'],$out['region']
71 | );
72 | unset($out['country_code3'],$out['region'],$out['postal_code']);
73 | return $out;
74 | }
75 | if (($req=$web->request('http://www.geoplugin.net/json.gp'.
76 | ($public?('?ip='.$ip):''))) &&
77 | $data=json_decode($req['body'],TRUE)) {
78 | $out=[];
79 | foreach ($data as $key=>$val)
80 | if (!strpos($key,'currency') && $key!=='geoplugin_status'
81 | && $key!=='geoplugin_region')
82 | $out[$fw->snakecase(substr($key, 10))]=$val;
83 | return $out;
84 | }
85 | return FALSE;
86 | }
87 |
88 | /**
89 | * Return weather data based on specified latitude/longitude
90 | * @return array|FALSE
91 | * @param $latitude float
92 | * @param $longitude float
93 | * @param $key string
94 | **/
95 | function weather($latitude,$longitude,$key) {
96 | $fw=\Base::instance();
97 | $web=\Web::instance();
98 | $query=[
99 | 'lat'=>$latitude,
100 | 'lon'=>$longitude,
101 | 'APPID'=>$key,
102 | 'units'=>'metric'
103 | ];
104 | return ($req=$web->request(
105 | 'http://api.openweathermap.org/data/2.5/weather?'.
106 | http_build_query($query)))?
107 | json_decode($req['body'],TRUE):
108 | FALSE;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/vendor/fatfree/magic.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! PHP magic wrapper
24 | abstract class Magic implements ArrayAccess {
25 |
26 | /**
27 | * Return TRUE if key is not empty
28 | * @return bool
29 | * @param $key string
30 | **/
31 | abstract function exists($key);
32 |
33 | /**
34 | * Bind value to key
35 | * @return mixed
36 | * @param $key string
37 | * @param $val mixed
38 | **/
39 | abstract function set($key,$val);
40 |
41 | /**
42 | * Retrieve contents of key
43 | * @return mixed
44 | * @param $key string
45 | **/
46 | abstract function &get($key);
47 |
48 | /**
49 | * Unset key
50 | * @return NULL
51 | * @param $key string
52 | **/
53 | abstract function clear($key);
54 |
55 | /**
56 | * Convenience method for checking property value
57 | * @return mixed
58 | * @param $key string
59 | **/
60 | #[\ReturnTypeWillChange]
61 | function offsetexists($key) {
62 | return Base::instance()->visible($this,$key)?
63 | isset($this->$key):
64 | ($this->exists($key) && $this->get($key)!==NULL);
65 | }
66 |
67 | /**
68 | * Convenience method for assigning property value
69 | * @return mixed
70 | * @param $key string
71 | * @param $val mixed
72 | **/
73 | #[\ReturnTypeWillChange]
74 | function offsetset($key,$val) {
75 | return Base::instance()->visible($this,$key)?
76 | ($this->$key=$val):$this->set($key,$val);
77 | }
78 |
79 | /**
80 | * Convenience method for retrieving property value
81 | * @return mixed
82 | * @param $key string
83 | **/
84 | #[\ReturnTypeWillChange]
85 | function &offsetget($key) {
86 | if (Base::instance()->visible($this,$key))
87 | $val=&$this->$key;
88 | else
89 | $val=&$this->get($key);
90 | return $val;
91 | }
92 |
93 | /**
94 | * Convenience method for removing property value
95 | * @return NULL
96 | * @param $key string
97 | **/
98 | #[\ReturnTypeWillChange]
99 | function offsetunset($key) {
100 | if (Base::instance()->visible($this,$key))
101 | unset($this->$key);
102 | else
103 | $this->clear($key);
104 | }
105 |
106 | /**
107 | * Alias for offsetexists()
108 | * @return mixed
109 | * @param $key string
110 | **/
111 | function __isset($key) {
112 | return $this->offsetexists($key);
113 | }
114 |
115 | /**
116 | * Alias for offsetset()
117 | * @return mixed
118 | * @param $key string
119 | * @param $val mixed
120 | **/
121 | function __set($key,$val) {
122 | return $this->offsetset($key,$val);
123 | }
124 |
125 | /**
126 | * Alias for offsetget()
127 | * @return mixed
128 | * @param $key string
129 | **/
130 | function &__get($key) {
131 | $val=&$this->offsetget($key);
132 | return $val;
133 | }
134 |
135 | /**
136 | * Alias for offsetunset()
137 | * @param $key string
138 | **/
139 | function __unset($key) {
140 | $this->offsetunset($key);
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/lib/GPGCryptography.class.php:
--------------------------------------------------------------------------------
1 | symmetricKey = $key;
10 | $this->pathHash = $pathHash;
11 | }
12 |
13 | private function getFiles($isGpg) {
14 | $suffix = "";
15 | if ($isGpg) {
16 | $suffix = ".gpg";
17 | }
18 | $filesTab = glob($this->pathHash.'/*.pdf'.$suffix);
19 |
20 | if(file_exists($this->pathHash."/filename.txt".$suffix)) {
21 | $filesTab[] = $this->pathHash."/filename.txt".$suffix;
22 | }
23 |
24 | return $filesTab;
25 | }
26 |
27 | public function encrypt() {
28 | putenv('HOME='.sys_get_temp_dir());
29 | foreach ($this->getFiles(false) as $file) {
30 | $outputFile = $file.".gpg";
31 | if(file_exists($outputFile)) {
32 | unlink($outputFile);
33 | }
34 | $command = "gpg --batch --passphrase $this->symmetricKey --symmetric --cipher-algo AES256 -o $outputFile $file > /dev/null";
35 | $result = shell_exec($command);
36 | if ($result) {
37 | echo "Cipher failure";
38 | return $result;
39 | }
40 | $this->hardUnlink($file);
41 |
42 | }
43 | return true;
44 | }
45 |
46 | public function decryptFile($file) {
47 | if (!file_exists($file.'.gpg')) {
48 | return $file;
49 | }
50 | if (!$this->symmetricKey) {
51 | return false;
52 | }
53 | $decryptTmpFile = sys_get_temp_dir()."/".uniqid('pdfsignature.decrypted.'.getmypid().md5($file), true).'_'.basename($file);
54 |
55 | $this->runDecryptFile($file.'.gpg', $decryptTmpFile);
56 |
57 | return $decryptTmpFile;
58 | }
59 |
60 | public function runDecryptFile($file, $outputFile) {
61 | putenv('HOME='.sys_get_temp_dir());
62 | return shell_exec("gpg --batch --passphrase $this->symmetricKey --decrypt -o $outputFile $file > /dev/null");
63 | }
64 |
65 | public function isEncrypted() {
66 | return self::isPathEncrypted($this->pathHash);
67 | }
68 |
69 | public static function isPathEncrypted($pathHash) {
70 | return file_exists($pathHash."/filename.txt.gpg");
71 | }
72 |
73 | public static function hardUnlink($element) {
74 | if (file_exists($element) === false) {
75 | return;
76 | }
77 | if (is_dir($element)) {
78 | foreach (glob($element.'/*') as $file) {
79 | self::hardUnlink($file);
80 | }
81 | rmdir($element);
82 | return;
83 | }
84 | $eraser = str_repeat(0, strlen(file_get_contents($element)));
85 | file_put_contents($element, $eraser);
86 | unlink($element);
87 | }
88 |
89 | public static function protectSymmetricKey($key) {
90 | return preg_replace('/[^0-9a-zA-Z]*/', '', $key);
91 | }
92 |
93 | public static function createSymmetricKey($length = 15) {
94 | $keySpace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
95 | $pieces = [];
96 | $max = mb_strlen($keySpace, '8bit') - 1;
97 | for ($i = 0; $i < $length; ++$i) {
98 | $pieces []= $keySpace[random_int(0, $max)];
99 | }
100 |
101 | return implode('', $pieces);
102 | }
103 |
104 | public static function isGpgInstalled() {
105 | $output = null;
106 | $returnCode = null;
107 |
108 | exec('gpg --version', $output, $returnCode);
109 |
110 | if (!$output) {
111 | return array(false);
112 | }
113 | return $output;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/vendor/fatfree/db/mongo.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace DB;
24 |
25 | //! MongoDB wrapper
26 | class Mongo {
27 |
28 | //@{
29 | const
30 | E_Profiler='MongoDB profiler is disabled';
31 | //@}
32 |
33 | protected
34 | //! UUID
35 | $uuid,
36 | //! Data source name
37 | $dsn,
38 | //! MongoDB object
39 | $db,
40 | //! Legacy flag
41 | $legacy,
42 | //! MongoDB log
43 | $log;
44 |
45 | /**
46 | * Return data source name
47 | * @return string
48 | **/
49 | function dsn() {
50 | return $this->dsn;
51 | }
52 |
53 | /**
54 | * Return UUID
55 | * @return string
56 | **/
57 | function uuid() {
58 | return $this->uuid;
59 | }
60 |
61 | /**
62 | * Return MongoDB profiler results (or disable logging)
63 | * @param $flag bool
64 | * @return string
65 | **/
66 | function log($flag=TRUE) {
67 | if ($flag) {
68 | $cursor=$this->db->selectcollection('system.profile')->find();
69 | foreach (iterator_to_array($cursor) as $frame)
70 | if (!preg_match('/\.system\..+$/',$frame['ns']))
71 | $this->log.=date('r',$this->legacy() ?
72 | $frame['ts']->sec : (round((string)$frame['ts'])/1000)).
73 | ' ('.sprintf('%.1f',$frame['millis']).'ms) '.
74 | $frame['ns'].' ['.$frame['op'].'] '.
75 | (empty($frame['query'])?
76 | '':json_encode($frame['query'])).
77 | (empty($frame['command'])?
78 | '':json_encode($frame['command'])).
79 | PHP_EOL;
80 | } else {
81 | $this->log=FALSE;
82 | if ($this->legacy)
83 | $this->db->setprofilinglevel(-1);
84 | else
85 | $this->db->command(['profile'=>-1]);
86 | }
87 | return $this->log;
88 | }
89 |
90 | /**
91 | * Intercept native call to re-enable profiler
92 | * @return int
93 | **/
94 | function drop() {
95 | $out=$this->db->drop();
96 | if ($this->log!==FALSE) {
97 | if ($this->legacy)
98 | $this->db->setprofilinglevel(2);
99 | else
100 | $this->db->command(['profile'=>2]);
101 | }
102 | return $out;
103 | }
104 |
105 | /**
106 | * Redirect call to MongoDB object
107 | * @return mixed
108 | * @param $func string
109 | * @param $args array
110 | **/
111 | function __call($func,array $args) {
112 | return call_user_func_array([$this->db,$func],$args);
113 | }
114 |
115 | /**
116 | * Return TRUE if legacy driver is loaded
117 | * @return bool
118 | **/
119 | function legacy() {
120 | return $this->legacy;
121 | }
122 |
123 | //! Prohibit cloning
124 | private function __clone() {
125 | }
126 |
127 | /**
128 | * Instantiate class
129 | * @param $dsn string
130 | * @param $dbname string
131 | * @param $options array
132 | **/
133 | function __construct($dsn,$dbname,?array $options=NULL) {
134 | $this->uuid=\Base::instance()->hash($this->dsn=$dsn);
135 | if ($this->legacy=class_exists('\MongoClient')) {
136 | $this->db=new \MongoDB(new \MongoClient($dsn,$options?:[]),$dbname);
137 | $this->db->setprofilinglevel(2);
138 | }
139 | else {
140 | $this->db=(new \MongoDB\Client($dsn,$options?:[]))->$dbname;
141 | $this->db->command(['profile'=>2]);
142 | }
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/lib/NSSCryptography.class.php:
--------------------------------------------------------------------------------
1 | nss_directory = $dir;
11 | $this->nss_password = $pass;
12 | $this->nss_nick = $nick;
13 | }
14 |
15 | private static $instance = null;
16 |
17 | public static function getInstance($dir = null, $pass = null, $nick = null) {
18 | if (!self::$instance) {
19 | self::$instance = new NSSCryptography($dir, $pass, $nick);
20 | }
21 | return self::$instance;
22 | }
23 |
24 | public function addSignature($pdf_path, $reason) {
25 | putenv('NSSPASS='.$this->nss_password);
26 | exec('pdfsig '.$pdf_path.' '.$pdf_path.'.signed.pdf -add-signature -nssdir "'.$this->nss_directory.'" -nss-pwd "$NSSPASS" -nick "'.$this->nss_nick.'" -reason "'.$reason.'" 2>&1', $output, $returnCode);
27 | if ($returnCode) {
28 | throw new Exception('pdfsign error: '.implode(' ', $output));
29 | }
30 | rename($pdf_path.'.signed.pdf', $pdf_path);
31 | }
32 |
33 | public function verify($pdf_path) {
34 | $signatures = [];
35 |
36 | putenv('NSSPASS='.$this->nss_password);
37 | exec('pdfsig -nssdir "'.$this->nss_directory.'" -nss-pwd "$NSSPASS" '.$pdf_path.' 2>&1', $output, $returnCode);
38 |
39 | if ($returnCode && !preg_match('/does not contain any signatures/', $output[0])) {
40 | throw new Exception('pdfsign error: '.implode(' ', $output));
41 | }
42 |
43 | $index = null;
44 | foreach($output as $l) {
45 | if (preg_match('/^(Signature[^:]*):/', $l, $m)) {
46 | $index = $m[1];
47 | $signatures[$index] = [];
48 | continue;
49 | }
50 | if (preg_match('/^ - ([^:]*):(.*)/', $l, $m)) {
51 | $signatures[$index][$m[1]] = $m[2];
52 | }elseif (preg_match('/^ - (.*) (document signed)/', $l, $m)) {
53 | $signatures[$index]["Document signed"] = $m[1];
54 | }
55 | }
56 | return $signatures;
57 | }
58 |
59 | public function isEnabled() {
60 | if (!$this->nss_directory || !$this->nss_nick) {
61 | return false;
62 | }
63 | return true;
64 | }
65 |
66 | public function isPDFSigConfigured() {
67 | if (!$this->isEnabled()) {
68 | return false;
69 | }
70 | if (!$this->isPDFSigInstalled()) {
71 | return false;
72 | }
73 | if (!$this->isCertUtilInstalled()) {
74 | return false;
75 | }
76 |
77 | $file = tempnam('/tmp', 'certutil');
78 | file_put_contents($file, $this->nss_password);
79 | exec('certutil -f '.$file.' -d '.$this->nss_directory.' -L -n "'.$this->nss_nick.'" 2>&1', $output, $returnCodeL);
80 | exec('certutil -f '.$file.' -d '.$this->nss_directory.' -K | grep ":'.$this->nss_nick.'" 2>&1', $output, $returnCodeK);
81 | unlink($file);
82 |
83 | return ($returnCodeL == 0 && $returnCodeK == 0);
84 | }
85 |
86 | public static function isCertUtilInstalled() {
87 | $output = null;
88 | $returnCode = null;
89 | exec('certutil -v 2>&1', $output, $returnCode);
90 | if ($returnCode != 1) {
91 | return false;
92 | }
93 | return "OK";
94 | }
95 |
96 |
97 | public static function isPDFSigInstalled() {
98 | $output = null;
99 | $returnCode = null;
100 | exec('pdfsig -v 2>&1', $output, $returnCode);
101 |
102 | if ($returnCode != 0) {
103 | return false;
104 | }
105 |
106 | $version = explode(' ', $output[0])[2];
107 | if (! $version >= "21") {
108 | return false;
109 | }
110 | return $version;
111 |
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test all
2 |
3 | .DEFAULT_GOAL := all
4 |
5 | VENDOR=24eme
6 | PROJECT=signaturepdf
7 | MAINTAINER=Équipe 24ème
8 | VERSION=$(shell git describe --abbrev=0)
9 | RELEASE=$(shell git describe | cut -d- -f2)
10 | PKGNAME=php-${PROJECT}
11 | DATADIR=usr/share
12 | LIBPATH=$(DATADIR)/$(PKGNAME)/
13 | CONFIGPATH=etc/$(PKGNAME)/
14 |
15 | CURRENTDIR=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
16 | TARGETDIR=$(CURRENTDIR)target
17 | PATHDEBPKG=$(TARGETDIR)/DEB
18 |
19 | all: update_trad
20 |
21 | node_modules/jest/bin/jest.js:
22 | npm install jest
23 |
24 | node_modules/puppeteer:
25 | npm install puppeteer
26 |
27 | test: node_modules/jest/bin/jest.js node_modules/puppeteer
28 | ./node_modules/jest/bin/jest.js
29 |
30 | update_trad:
31 | # Extraction des phrases traductibles...
32 | @xgettext --from-code=utf-8 --no-location --output=./locale/application.pot *.php templates/*.php templates/components/*.php
33 |
34 | # Mise a jour des fichiers .po...
35 | @for lang in $$(find locale -mindepth 1 -maxdepth 1 -type d); do \
36 | po_file="$$lang/LC_MESSAGES/application.po"; \
37 | msgmerge --update -N "$$po_file" ./locale/application.pot; \
38 | done
39 |
40 | # Creation des fichiers .mo...
41 | @for lang in $$(find locale -mindepth 1 -maxdepth 1 -type d); do \
42 | po_file="$$lang/LC_MESSAGES/application.po"; \
43 | rm -f "$$lang/LC_MESSAGES/application.mo"; \
44 | msgfmt "$$po_file" --output-file="$$lang/LC_MESSAGES/application.mo"; \
45 | git add "$$lang/LC_MESSAGES/application.mo"; \
46 | done
47 |
48 | # Génération des empreintes de .mo..
49 |
50 | @for lang in $$(find locale -mindepth 1 -maxdepth 1 -type d); do \
51 | checksum="$$(md5sum locale/*/LC_MESSAGES/application.mo | md5sum | cut -d " " -f 1)"; \
52 | rm $$lang/LC_MESSAGES/application_*.mo; \
53 | git rm $$lang/LC_MESSAGES/application_*.mo; \
54 | rm locale/application_*.pot; \
55 | git rm locale/application_*.pot; \
56 | ln -s "application.mo" "$$lang/LC_MESSAGES/application_$$checksum.mo"; \
57 | ln -s "application.pot" "locale/application_$$checksum.pot"; \
58 | git add "$$lang/LC_MESSAGES/application_$$checksum.mo"; \
59 | git add "locale/application_$$checksum.pot"; \
60 | done
61 |
62 | # Build a DEB package for Debian-like Linux distributions
63 | .PHONY: deb
64 | deb:
65 | rm -rf $(TARGETDIR)
66 | git clone . $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(LIBPATH)
67 | rm -rf $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(LIBPATH).git
68 | rm -rf $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(LIBPATH).debian
69 | tar -zcvf $(PATHDEBPKG)/$(PKGNAME)_$(VERSION).orig.tar.gz -C $(PATHDEBPKG)/ $(PKGNAME)-$(VERSION)
70 | rsync -av --exclude=*~ ./.debian/ $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian
71 | mkdir -p $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(CONFIGPATH)
72 | mv $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/apache.conf $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(CONFIGPATH)
73 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#DATE#~/`date -R`/" {} \;
74 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#VENDOR#~/$(VENDOR)/" {} \;
75 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#PROJECT#~/$(PROJECT)/" {} \;
76 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#MAINTAINER#~/$(MAINTAINER)/" {} \;
77 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/ -type f -exec sed -i "s/~#PKGNAME#~/$(PKGNAME)/" {} \;
78 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#VERSION#~/$(VERSION)/" {} \;
79 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#RELEASE#~/$(RELEASE)/" {} \;
80 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/ -type f -exec sed -i "s|~#LIBPATH#~|$(LIBPATH)|" {} \;
81 | echo $(LIBPATH) > $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).dirs
82 | echo "$(LIBPATH)* $(LIBPATH)" > $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/install
83 | ifneq ($(strip $(CONFIGPATH)),)
84 | echo $(CONFIGPATH) >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).dirs
85 | echo "$(CONFIGPATH)* $(CONFIGPATH)" >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/install
86 | endif
87 | cd $(PATHDEBPKG)/$(PKGNAME)-$(VERSION) && debuild -us -uc
88 |
--------------------------------------------------------------------------------
/public/js/compress.js:
--------------------------------------------------------------------------------
1 | async function handleFileChange() {
2 | document.querySelector('#error_message').classList.add('d-none');
3 | document.querySelector('#card_resultat').classList.add('d-none');
4 | document.querySelector('#compressBtn').classList.add('btn-primary');
5 | document.querySelector('#compressBtn').classList.remove('btn-outline-primary');
6 | const fileInput = document.getElementById('input_pdf_upload');
7 |
8 | if(fileInput.files[0].size > maxSize) {
9 | alert("Le PDF ne doit pas dépasser " + convertOctet2MegoOctet(maxSize) + " Mo");
10 | fileInput.value = null;
11 | return;
12 | }
13 |
14 | const compressBtn = document.getElementById('compressBtn');
15 | const dropdownCompressBtn = document.getElementById('dropdownMenuReference');
16 |
17 | if (fileInput.files.length > 0) {
18 | compressBtn.disabled = false;
19 | dropdownCompressBtn.disabled = false;
20 | } else {
21 | compressBtn.disabled = true;
22 | dropdownCompressBtn.disabled = true;
23 | }
24 | }
25 |
26 | document.addEventListener('DOMContentLoaded', function () {
27 | document.getElementById('form_compress').addEventListener('submit', async function(e) {
28 | document.querySelector('#error_message').classList.add('d-none');
29 | document.querySelector('#card_resultat').classList.add('d-none');
30 | document.querySelector('#compressBtn').classList.add('btn-primary');
31 | document.querySelector('#compressBtn').classList.remove('btn-outline-primary');
32 | startProcessingMode(document.getElementById('compressBtn'));
33 | const form = e.target;
34 | const formData = new FormData(form);
35 | formData.set(e.submitter.name, e.submitter.value);
36 |
37 | fetch(form.action, { method: form.method, body: formData })
38 | .then(async function(response) {
39 | if (response.status == 204 ) {
40 | document.querySelector('#error_message').classList.remove('d-none');
41 | document.querySelector('#error_message').innerText = trad["Your pdf is already optimized"];
42 | } else if (response.ok) {
43 | let filename = decodeURI(response.headers.get('content-disposition').replace('attachment; filename=', ''));
44 | let blob = await response.blob();
45 |
46 | document.querySelector('#compressBtn').classList.remove('btn-primary');
47 | document.querySelector('#compressBtn').classList.add('btn-outline-primary');
48 | document.querySelector('#uploaded_size').innerText = document.querySelector('#uploaded_size').dataset.templateText.replace("%s", convertOctet2MegoOctet(document.getElementById('input_pdf_upload').files[0].size));
49 | document.querySelector('#size_compressed').innerText = document.querySelector('#size_compressed').dataset.templateText.replace("%s", convertOctet2MegoOctet(blob.size));
50 | document.querySelector('#pourcentage_compressed').innerText = document.querySelector('#pourcentage_compressed').dataset.templateText.replace("%s", 100 - Math.round((blob.size * 100)/ document.getElementById('input_pdf_upload').files[0].size));
51 | document.querySelector('#card_resultat').classList.remove('d-none');
52 |
53 | let dataTransfer = new DataTransfer();
54 | dataTransfer.items.add(new File([blob], filename, {
55 | type: 'application/pdf'
56 | }));
57 | document.getElementById('input_pdf_compressed').files = dataTransfer.files;
58 | document.querySelector('#downloadBtn').focus();
59 | } else {
60 | document.querySelector('#error_message').classList.remove('d-none');
61 | document.querySelector('#error_message').innerText = await response.text();
62 | }
63 | endProcessingMode(document.getElementById('compressBtn'));
64 | })
65 |
66 | e.preventDefault();
67 | return false;
68 | });
69 |
70 | document.getElementById('downloadBtn').addEventListener('click', async function(e) {
71 | await download(document.getElementById('input_pdf_compressed').files[0], document.getElementById('input_pdf_compressed').files[0].name);
72 | });
73 | })
74 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/oauth2.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web;
24 |
25 | //! Lightweight OAuth2 client
26 | class OAuth2 extends \Magic {
27 |
28 | protected
29 | //! Scopes and claims
30 | $args=[],
31 | //! Encoding
32 | $enc_type = PHP_QUERY_RFC1738;
33 |
34 | /**
35 | * Return OAuth2 authentication URI
36 | * @return string
37 | * @param $endpoint string
38 | * @param $query bool
39 | **/
40 | function uri($endpoint,$query=TRUE) {
41 | return $endpoint.($query?('?'.
42 | http_build_query($this->args,'','&',$this->enc_type)):'');
43 | }
44 |
45 | /**
46 | * Send request to API/token endpoint
47 | * @return string|array|FALSE
48 | * @param $uri string
49 | * @param $method string
50 | * @param $token string
51 | **/
52 | function request($uri,$method,$token=NULL) {
53 | $web=\Web::instance();
54 | $options=[
55 | 'method'=>$method,
56 | 'content'=>http_build_query($this->args,'','&',$this->enc_type),
57 | 'header'=>['Accept: application/json']
58 | ];
59 | if ($token)
60 | array_push($options['header'],'Authorization: Bearer '.$token);
61 | elseif ($method=='POST' && isset($this->args['client_id']))
62 | array_push($options['header'],'Authorization: Basic '.
63 | base64_encode(
64 | $this->args['client_id'].':'.
65 | $this->args['client_secret']
66 | )
67 | );
68 | $response=$web->request($uri,$options);
69 | if ($response['error'])
70 | user_error($response['error'],E_USER_ERROR);
71 | if (isset($response['body'])) {
72 | if (preg_grep('/^Content-Type:.*application\/json/i',
73 | $response['headers'])) {
74 | $token=json_decode($response['body'],TRUE);
75 | if (isset($token['error_description']))
76 | user_error($token['error_description'],E_USER_ERROR);
77 | if (isset($token['error']))
78 | user_error($token['error'],E_USER_ERROR);
79 | return $token;
80 | }
81 | else
82 | return $response['body'];
83 | }
84 | return FALSE;
85 | }
86 |
87 | /**
88 | * Parse JSON Web token
89 | * @return array
90 | * @param $token string
91 | **/
92 | function jwt($token) {
93 | return json_decode(
94 | base64_decode(
95 | str_replace(['-','_'],['+','/'],explode('.',$token)[1])
96 | ),
97 | TRUE
98 | );
99 | }
100 |
101 | /**
102 | * change default url encoding type, i.E. PHP_QUERY_RFC3986
103 | * @param $type
104 | */
105 | function setEncoding($type) {
106 | $this->enc_type = $type;
107 | }
108 |
109 | /**
110 | * URL-safe base64 encoding
111 | * @return array
112 | * @param $data string
113 | **/
114 | function b64url($data) {
115 | return trim(strtr(base64_encode($data),'+/','-_'),'=');
116 | }
117 |
118 | /**
119 | * Return TRUE if scope/claim exists
120 | * @return bool
121 | * @param $key string
122 | **/
123 | function exists($key) {
124 | return isset($this->args[$key]);
125 | }
126 |
127 | /**
128 | * Bind value to scope/claim
129 | * @return string
130 | * @param $key string
131 | * @param $val string
132 | **/
133 | function set($key,$val) {
134 | return $this->args[$key]=$val;
135 | }
136 |
137 | /**
138 | * Return value of scope/claim
139 | * @return mixed
140 | * @param $key string
141 | **/
142 | function &get($key) {
143 | if (isset($this->args[$key]))
144 | $val=&$this->args[$key];
145 | else
146 | $val=NULL;
147 | return $val;
148 | }
149 |
150 | /**
151 | * Remove scope/claim
152 | * @return NULL
153 | * @param $key string
154 | **/
155 | function clear($key=NULL) {
156 | if ($key)
157 | unset($this->args[$key]);
158 | else
159 | $this->args=[];
160 | }
161 |
162 | }
163 |
164 |
--------------------------------------------------------------------------------
/vendor/fatfree/matrix.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Generic array utilities
24 | class Matrix extends Prefab {
25 |
26 | /**
27 | * Retrieve values from a specified column of a multi-dimensional
28 | * array variable
29 | * @return array
30 | * @param $var array
31 | * @param $col mixed
32 | **/
33 | function pick(array $var,$col) {
34 | return array_map(
35 | function($row) use($col) {
36 | return $row[$col];
37 | },
38 | $var
39 | );
40 | }
41 |
42 | /**
43 | * select a subset of fields from an input array
44 | * @param string|array $fields splittable string or array
45 | * @param string|array $data hive key or array
46 | * @return array
47 | */
48 | function select($fields, $data) {
49 | return array_intersect_key(is_array($data) ? $data : \Base::instance()->get($data),
50 | array_flip(is_array($fields) ? $fields : \Base::instance()->split($fields)));
51 | }
52 |
53 | /**
54 | * walk with a callback function through a subset of fields from an input array
55 | * the callback receives the value, index-key and the full input array as parameters
56 | * set value parameter as reference and you're able to modify the data as well
57 | * @param string|array $fields splittable string or array of fields
58 | * @param string|array $data hive key or input array
59 | * @param callable $callback (mixed &$value, string $key, array $data)
60 | * @return array modified subset data
61 | */
62 | function walk($fields, $data, $callback) {
63 | $subset=$this->select($fields, $data);
64 | array_walk($subset, $callback, $data);
65 | return $subset;
66 | }
67 |
68 | /**
69 | * Rotate a two-dimensional array variable
70 | * @return NULL
71 | * @param $var array
72 | **/
73 | function transpose(array &$var) {
74 | $out=[];
75 | foreach ($var as $keyx=>$cols)
76 | foreach ($cols as $keyy=>$valy)
77 | $out[$keyy][$keyx]=$valy;
78 | $var=$out;
79 | }
80 |
81 | /**
82 | * Sort a multi-dimensional array variable on a specified column
83 | * @return bool
84 | * @param $var array
85 | * @param $col mixed
86 | * @param $order int
87 | **/
88 | function sort(array &$var,$col,$order=SORT_ASC) {
89 | uasort(
90 | $var,
91 | function($val1,$val2) use($col,$order) {
92 | list($v1,$v2)=[$val1[$col],$val2[$col]];
93 | $out=is_numeric($v1) && is_numeric($v2)?
94 | Base::instance()->sign($v1-$v2):strcmp($v1,$v2);
95 | if ($order==SORT_DESC)
96 | $out=-$out;
97 | return $out;
98 | }
99 | );
100 | $var=array_values($var);
101 | }
102 |
103 | /**
104 | * Change the key of a two-dimensional array element
105 | * @return NULL
106 | * @param $var array
107 | * @param $old string
108 | * @param $new string
109 | **/
110 | function changekey(array &$var,$old,$new) {
111 | $keys=array_keys($var);
112 | $vals=array_values($var);
113 | $keys[array_search($old,$keys)]=$new;
114 | $var=array_combine($keys,$vals);
115 | }
116 |
117 | /**
118 | * Return month calendar of specified date, with optional setting for
119 | * first day of week (0 for Sunday)
120 | * @return array
121 | * @param $date string|int
122 | * @param $first int
123 | **/
124 | function calendar($date='now',$first=0) {
125 | $out=FALSE;
126 | if (extension_loaded('calendar')) {
127 | if (is_string($date))
128 | $date=strtotime($date);
129 | $parts=getdate($date);
130 | $days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']);
131 | $ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7;
132 | $out=[];
133 | for ($i=0;$i<$days;++$i)
134 | $out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1;
135 | }
136 | return $out;
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/vendor/fatfree/db/jig.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace DB;
24 |
25 | //! In-memory/flat-file DB wrapper
26 | class Jig {
27 |
28 | //@{ Storage formats
29 | const
30 | FORMAT_JSON=0,
31 | FORMAT_Serialized=1;
32 | //@}
33 |
34 | protected
35 | //! UUID
36 | $uuid,
37 | //! Storage location
38 | $dir,
39 | //! Current storage format
40 | $format,
41 | //! Jig log
42 | $log,
43 | //! Memory-held data
44 | $data,
45 | //! lazy load/save files
46 | $lazy;
47 |
48 | /**
49 | * Read data from memory/file
50 | * @return array
51 | * @param $file string
52 | **/
53 | function &read($file) {
54 | if (!$this->dir || !is_file($dst=$this->dir.$file)) {
55 | if (!isset($this->data[$file]))
56 | $this->data[$file]=[];
57 | return $this->data[$file];
58 | }
59 | if ($this->lazy && isset($this->data[$file]))
60 | return $this->data[$file];
61 | $fw=\Base::instance();
62 | $raw=$fw->read($dst);
63 | switch ($this->format) {
64 | case self::FORMAT_JSON:
65 | $data=json_decode($raw,TRUE);
66 | break;
67 | case self::FORMAT_Serialized:
68 | $data=$fw->unserialize($raw);
69 | break;
70 | }
71 | $this->data[$file] = $data;
72 | return $this->data[$file];
73 | }
74 |
75 | /**
76 | * Write data to memory/file
77 | * @return int
78 | * @param $file string
79 | * @param $data array
80 | **/
81 | function write($file,?array $data=NULL) {
82 | if (!$this->dir || $this->lazy)
83 | return count($this->data[$file]=$data);
84 | $fw=\Base::instance();
85 | switch ($this->format) {
86 | case self::FORMAT_JSON:
87 | if(version_compare(PHP_VERSION, '7.2.0') >= 0)
88 | $out=json_encode($data,JSON_PRETTY_PRINT | JSON_INVALID_UTF8_IGNORE);
89 | else
90 | $out=json_encode($data,JSON_PRETTY_PRINT | JSON_PARTIAL_OUTPUT_ON_ERROR);
91 | break;
92 | case self::FORMAT_Serialized:
93 | $out=$fw->serialize($data);
94 | break;
95 | }
96 | return $fw->write($this->dir.$file,$out);
97 | }
98 |
99 | /**
100 | * Return directory
101 | * @return string
102 | **/
103 | function dir() {
104 | return $this->dir;
105 | }
106 |
107 | /**
108 | * Return UUID
109 | * @return string
110 | **/
111 | function uuid() {
112 | return $this->uuid;
113 | }
114 |
115 | /**
116 | * Return profiler results (or disable logging)
117 | * @param $flag bool
118 | * @return string
119 | **/
120 | function log($flag=TRUE) {
121 | if ($flag)
122 | return $this->log;
123 | $this->log=FALSE;
124 | }
125 |
126 | /**
127 | * Jot down log entry
128 | * @return NULL
129 | * @param $frame string
130 | **/
131 | function jot($frame) {
132 | if ($frame)
133 | $this->log.=date('r').' '.$frame.PHP_EOL;
134 | }
135 |
136 | /**
137 | * Clean storage
138 | * @return NULL
139 | **/
140 | function drop() {
141 | if ($this->lazy) // intentional
142 | $this->data=[];
143 | if (!$this->dir)
144 | $this->data=[];
145 | elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT))
146 | foreach ($glob as $file)
147 | @unlink($file);
148 | }
149 |
150 | //! Prohibit cloning
151 | private function __clone() {
152 | }
153 |
154 | /**
155 | * Instantiate class
156 | * @param $dir string
157 | * @param $format int
158 | **/
159 | function __construct($dir=NULL,$format=self::FORMAT_JSON,$lazy=FALSE) {
160 | if ($dir && !is_dir($dir))
161 | mkdir($dir,\Base::MODE,TRUE);
162 | $this->uuid=\Base::instance()->hash($this->dir=$dir);
163 | $this->format=$format;
164 | $this->lazy=$lazy;
165 | }
166 |
167 | /**
168 | * save file on destruction
169 | **/
170 | function __destruct() {
171 | if ($this->lazy) {
172 | $this->lazy = FALSE;
173 | foreach ($this->data?:[] as $file => $data)
174 | $this->write($file,$data);
175 | }
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/templates/compress.html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Signature PDF - Compresser un PDF en ligne
6 |
7 |
8 |
9 |
14 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/vendor/fatfree/db/jig/session.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace DB\Jig;
24 |
25 | use ReturnTypeWillChange;
26 | use SessionAdapter;
27 |
28 | //! Jig-managed session handler
29 | class Session extends Mapper {
30 |
31 | protected
32 | //! Session ID
33 | $sid,
34 | //! Anti-CSRF token
35 | $_csrf,
36 | //! User agent
37 | $_agent,
38 | //! IP,
39 | $_ip,
40 | //! Suspect callback
41 | $onsuspect;
42 |
43 | /**
44 | * Open session
45 | * @return TRUE
46 | * @param $path string
47 | * @param $name string
48 | **/
49 | function open(string $path, string $name): bool
50 | {
51 | return TRUE;
52 | }
53 |
54 | /**
55 | * Close session
56 | * @return TRUE
57 | **/
58 | function close(): bool
59 | {
60 | $this->reset();
61 | $this->sid=NULL;
62 | return TRUE;
63 | }
64 |
65 | /**
66 | * Return session data in serialized format
67 | * @return string|false
68 | * @param $id string
69 | **/
70 | #[ReturnTypeWillChange]
71 | function read($id)
72 | {
73 | $this->load(['@session_id=?',$this->sid=$id]);
74 | if ($this->dry())
75 | return '';
76 | if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
77 | $fw=\Base::instance();
78 | if (!isset($this->onsuspect) ||
79 | $fw->call($this->onsuspect,[$this,$id])===FALSE) {
80 | // NB: `session_destroy` can't be called at that stage;
81 | // `session_start` not completed
82 | $this->destroy($id);
83 | $this->close();
84 | unset($fw->{'COOKIE.'.session_name()});
85 | $fw->error(403);
86 | }
87 | }
88 | return $this->get('data');
89 | }
90 |
91 | /**
92 | * Write session data
93 | * @return TRUE
94 | * @param $id string
95 | * @param $data string
96 | **/
97 | function write(string $id, string $data): bool
98 | {
99 | $this->set('session_id',$id);
100 | $this->set('data',$data);
101 | $this->set('ip',$this->_ip);
102 | $this->set('agent',$this->_agent);
103 | $this->set('stamp',time());
104 | $this->save();
105 | return TRUE;
106 | }
107 |
108 | /**
109 | * Destroy session
110 | * @return TRUE
111 | * @param $id string
112 | **/
113 | function destroy($id): bool
114 | {
115 | $this->erase(['@session_id=?',$id]);
116 | return TRUE;
117 | }
118 |
119 | /**
120 | * Garbage collector
121 | **/
122 | #[ReturnTypeWillChange]
123 | function gc(int $max_lifetime): int
124 | {
125 | return (int) $this->erase(['@stamp+?',$max_lifetime,time()]);
126 | }
127 |
128 | /**
129 | * Return session id (if session has started)
130 | * @return string|NULL
131 | **/
132 | function sid() {
133 | return $this->sid;
134 | }
135 |
136 | /**
137 | * Return anti-CSRF token
138 | * @return string
139 | **/
140 | function csrf() {
141 | return $this->_csrf;
142 | }
143 |
144 | /**
145 | * Return IP address
146 | * @return string
147 | **/
148 | function ip() {
149 | return $this->_ip;
150 | }
151 |
152 | /**
153 | * Return Unix timestamp
154 | * @return string|FALSE
155 | **/
156 | function stamp() {
157 | if (!$this->sid)
158 | session_start();
159 | return $this->dry()?FALSE:$this->get('stamp');
160 | }
161 |
162 | /**
163 | * Return HTTP user agent
164 | * @return string|FALSE
165 | **/
166 | function agent() {
167 | return $this->_agent;
168 | }
169 |
170 | /**
171 | * Instantiate class
172 | * @param $db \DB\Jig
173 | * @param $file string
174 | * @param $onsuspect callback
175 | * @param $key string
176 | **/
177 | function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) {
178 | parent::__construct($db,$file);
179 | $this->onsuspect=$onsuspect;
180 | if (version_compare(PHP_VERSION, '8.4.0')>=0) {
181 | // TODO: remove this when php7 support is dropped
182 | session_set_save_handler(new SessionAdapter($this));
183 | } else {
184 | session_set_save_handler(
185 | [$this,'open'],
186 | [$this,'close'],
187 | [$this,'read'],
188 | [$this,'write'],
189 | [$this,'destroy'],
190 | [$this,'gc']
191 | );
192 | }
193 | register_shutdown_function('session_commit');
194 | $fw=\Base::instance();
195 | $headers=$fw->HEADERS;
196 | $this->_csrf=$fw->hash($fw->SEED.
197 | extension_loaded('openssl')?
198 | implode(unpack('L',openssl_random_pseudo_bytes(4))):
199 | mt_rand()
200 | );
201 | if ($key)
202 | $fw->$key=$this->_csrf;
203 | $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
204 | $this->_ip=$fw->IP;
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/vendor/fatfree/db/mongo/session.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace DB\Mongo;
24 |
25 | use ReturnTypeWillChange;
26 | use SessionAdapter;
27 |
28 | //! MongoDB-managed session handler
29 | class Session extends Mapper {
30 |
31 | protected
32 | //! Session ID
33 | $sid,
34 | //! Anti-CSRF token
35 | $_csrf,
36 | //! User agent
37 | $_agent,
38 | //! IP,
39 | $_ip,
40 | //! Suspect callback
41 | $onsuspect;
42 |
43 | /**
44 | * Open session
45 | * @return TRUE
46 | * @param $path string
47 | * @param $name string
48 | **/
49 | function open(string $path, string $name): bool
50 | {
51 | return TRUE;
52 | }
53 |
54 | /**
55 | * Close session
56 | * @return TRUE
57 | **/
58 | function close(): bool
59 | {
60 | $this->reset();
61 | $this->sid=NULL;
62 | return TRUE;
63 | }
64 |
65 | /**
66 | * Return session data in serialized format
67 | * @return string
68 | * @param $id string
69 | **/
70 | #[ReturnTypeWillChange]
71 | function read(string $id)
72 | {
73 | $this->load(['session_id'=>$this->sid=$id]);
74 | if ($this->dry())
75 | return '';
76 | if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
77 | $fw=\Base::instance();
78 | if (!isset($this->onsuspect) ||
79 | $fw->call($this->onsuspect,[$this,$id])===FALSE) {
80 | // NB: `session_destroy` can't be called at that stage;
81 | // `session_start` not completed
82 | $this->destroy($id);
83 | $this->close();
84 | unset($fw->{'COOKIE.'.session_name()});
85 | $fw->error(403);
86 | }
87 | }
88 | return $this->get('data');
89 | }
90 |
91 | /**
92 | * Write session data
93 | * @return TRUE
94 | * @param $id string
95 | * @param $data string
96 | **/
97 | function write(string $id, string $data): bool
98 | {
99 | $this->set('session_id',$id);
100 | $this->set('data',$data);
101 | $this->set('ip',$this->_ip);
102 | $this->set('agent',$this->_agent);
103 | $this->set('stamp',time());
104 | $this->save();
105 | return TRUE;
106 | }
107 |
108 | /**
109 | * Destroy session
110 | * @return TRUE
111 | * @param $id string
112 | **/
113 | function destroy($id): bool
114 | {
115 | $this->erase(['session_id'=>$id]);
116 | return TRUE;
117 | }
118 |
119 | /**
120 | * Garbage collector
121 | **/
122 | #[ReturnTypeWillChange]
123 | function gc(int $max_lifetime): int
124 | {
125 | return (int) $this->erase(['$where'=>'this.stamp+'.$max_lifetime.'<'.time()]);
126 | }
127 |
128 | /**
129 | * Return session id (if session has started)
130 | * @return string|NULL
131 | **/
132 | function sid() {
133 | return $this->sid;
134 | }
135 |
136 | /**
137 | * Return anti-CSRF token
138 | * @return string
139 | **/
140 | function csrf() {
141 | return $this->_csrf;
142 | }
143 |
144 | /**
145 | * Return IP address
146 | * @return string
147 | **/
148 | function ip() {
149 | return $this->_ip;
150 | }
151 |
152 | /**
153 | * Return Unix timestamp
154 | * @return string|FALSE
155 | **/
156 | function stamp() {
157 | if (!$this->sid)
158 | session_start();
159 | return $this->dry()?FALSE:$this->get('stamp');
160 | }
161 |
162 | /**
163 | * Return HTTP user agent
164 | * @return string
165 | **/
166 | function agent() {
167 | return $this->_agent;
168 | }
169 |
170 | /**
171 | * Instantiate class
172 | * @param $db \DB\Mongo
173 | * @param $table string
174 | * @param $onsuspect callback
175 | * @param $key string
176 | **/
177 | function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) {
178 | parent::__construct($db,$table);
179 | $this->onsuspect=$onsuspect;
180 | if (version_compare(PHP_VERSION, '8.4.0')>=0) {
181 | // TODO: remove this when php7 support is dropped
182 | session_set_save_handler(new SessionAdapter($this));
183 | } else {
184 | session_set_save_handler(
185 | [$this,'open'],
186 | [$this,'close'],
187 | [$this,'read'],
188 | [$this,'write'],
189 | [$this,'destroy'],
190 | [$this,'gc']
191 | );
192 | }
193 | register_shutdown_function('session_commit');
194 | $fw=\Base::instance();
195 | $headers=$fw->HEADERS;
196 | $this->_csrf=$fw->hash($fw->SEED.
197 | extension_loaded('openssl')?
198 | implode(unpack('L',openssl_random_pseudo_bytes(4))):
199 | mt_rand()
200 | );
201 | if ($key)
202 | $fw->$key=$this->_csrf;
203 | $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
204 | $this->_ip=$fw->IP;
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/public/css/app.css:
--------------------------------------------------------------------------------
1 | #container-pages {
2 | overflow: auto;
3 | }
4 |
5 | #sidebarTools {
6 | width: 350px;
7 | }
8 |
9 | #sidebarTools .list-item-add label:hover {
10 | background: #e8ebed;
11 | border: 1px solid #505050;
12 | }
13 |
14 | #sidebarTools .list-item-add label:active, #sidebarTools .list-item-add label.active, #sidebarTools .list-item-add .btn-check:active + .btn-outline-secondary, #sidebarTools .list-item-add .btn-check:checked + .btn-outline-secondary {
15 | background: #c9d1d8;
16 | border: 1px solid #000;
17 | box-shadow: 0 .25rem .5rem rgba(0,0,0,.075) !important;
18 | }
19 |
20 | #top_bar {
21 | z-index: 10000;
22 | }
23 |
24 | #search_tools::placeholder {
25 | opacity: 0.65;
26 | }
27 |
28 | #modalFichier {
29 | z-index: 10001;
30 | }
31 |
32 | #input-text-signature:dir(ltr) {
33 | font-family: Caveat;
34 | font-size: 48px;
35 | }
36 |
37 | #input-text-signature:dir(rtl) {
38 | font-family: Tajawal;
39 | font-size: 48px;
40 | }
41 |
42 | #img-upload {
43 | max-width: 460px;
44 | max-height: 200px;
45 | }
46 |
47 | .btn-svg-list-suppression {
48 | top: 2px;
49 | }
50 |
51 | .btn-svg-list-suppression:dir(ltr) {
52 | right: 6px;
53 | }
54 |
55 | .btn-svg-list-suppression:dir(rtl) {
56 | left: 6px;
57 | }
58 |
59 | #color-picker {
60 | display: block; position: fixed; height:28px; width: 28px; border-radius: 24px; background: #000; top: 14px; right: 366px; border: 2px solid #fff; color: #fff; font-size: 10px; text-align: center; line-height: 20px; opacity: 0.25;
61 | }
62 |
63 | #color-picker:hover {
64 | opacity: 1;
65 | }
66 |
67 | #watermark-color-picker {
68 | display: inline-block; position: absolute; height:28px; width: 28px; border-radius: 24px; top: 5px; right: 6px; z-index: 20;
69 | }
70 |
71 | #watermark-color-picker::-moz-color-swatch, #watermark-color-picker::-webkit-color-swatch {
72 | border-radius: 24px;
73 | }
74 |
75 | #toolbox div {
76 | cursor: pointer;
77 | padding: 1px;
78 | display: inline-block;
79 | padding-left: .25rem;
80 | padding-right: .25rem;
81 | }
82 |
83 | #toolbox div + div {
84 | border-left: 1px solid #CCC;
85 | }
86 |
87 | #toolbox .dropdown-menu {
88 | padding-top: 0;
89 | padding-bottom: 0;
90 | border-radius: 0;
91 | }
92 |
93 | .canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
94 | font-size: 30px;
95 | cursor: move;
96 | background: rgb(255,255,255,0.6);
97 | }
98 |
99 | .canvas-container .btn-drag-here, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
100 | cursor: pointer;
101 | z-index: 9999;
102 | }
103 |
104 | .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here {
105 | cursor: pointer;
106 | font-size: 25px;
107 | }
108 |
109 | .canvas-container .btn-rotate:hover, .canvas-container .btn-delete:hover, .canvas-container .btn-select:hover, .canvas-container .btn-download:hover, .canvas-container .btn-restore:hover, .canvas-container .btn-drag:hover {
110 | background: rgb(255, 255, 255, 1);
111 | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15) !important;
112 | }
113 |
114 | .canvas-container .btn-cancel {
115 | font-size: 20px;
116 | }
117 |
118 | .border-transparent {
119 | border-color: transparent !important;
120 | }
121 |
122 | .delete-metadata {
123 | display: none;
124 | cursor: pointer;
125 | position: absolute;
126 | right: 10px;
127 | top: 0;
128 | font-size: 1.2rem;
129 | user-select: none;
130 | }
131 |
132 | .input-metadata > label {
133 | width: 110%;
134 | }
135 |
136 | .input-metadata:hover > .delete-metadata {
137 | display: block;
138 | }
139 |
140 | #page-metadata #container-main {
141 | width: 55%;
142 | }
143 |
144 | #page-metadata #sidebarTools {
145 | width: 45%;
146 | }
147 |
148 | #page-metadata #bottom_bar {
149 | z-index: 3;
150 | }
151 |
152 | @media (max-width: 1500px) {
153 | #page-metadata #container-main {
154 | width: 50%;
155 | }
156 | #page-metadata #sidebarTools {
157 | width: 50%;
158 | }
159 | }
160 |
161 | @media (max-width: 575.98px) {
162 | #page-metadata #container-main {
163 | width: 100%;
164 | }
165 | #page-metadata #sidebarTools {
166 | display:none;
167 | width: 100%;
168 | }
169 | }
170 | @media (max-width: 480px) {
171 | .subtitle {
172 | font-size: .875em
173 | }
174 | }
175 |
176 | html.ltr .decalage-pdf-div {
177 | padding-right: 350px;
178 | }
179 |
180 | html.rtl .decalage-pdf-div {
181 | padding-left: 350px;
182 | }
183 |
184 | #container-btn-zoom {
185 | top: 6px;
186 | }
187 |
188 | html.ltr #container-btn-zoom {
189 | right: 368px;
190 | }
191 |
192 | html.rtl #container-btn-zoom {
193 | left: 357px;
194 | }
195 |
196 | html.ltr .file-list-checkbox {
197 | right: 10px;
198 | }
199 |
200 | html.rtl .file-list-checkbox {
201 | left: 10px;
202 | }
203 |
204 | .fullpage {
205 | min-height: calc(100dvh - 95px);
206 | }
207 |
208 | .clamp-2-lines {
209 | display: -webkit-box;
210 | -webkit-box-orient: vertical;
211 | -webkit-line-clamp: 2;
212 | overflow: hidden;
213 | text-overflow: ellipsis;
214 | }
215 |
--------------------------------------------------------------------------------
/vendor/fatfree/utf.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Unicode string manager
24 | class UTF extends Prefab {
25 |
26 | /**
27 | * Get string length
28 | * @return int
29 | * @param $str string
30 | **/
31 | function strlen($str) {
32 | preg_match_all('/./us',$str,$parts);
33 | return count($parts[0]);
34 | }
35 |
36 | /**
37 | * Reverse a string
38 | * @return string
39 | * @param $str string
40 | **/
41 | function strrev($str) {
42 | preg_match_all('/./us',$str,$parts);
43 | return implode('',array_reverse($parts[0]));
44 | }
45 |
46 | /**
47 | * Find position of first occurrence of a string (case-insensitive)
48 | * @return int|FALSE
49 | * @param $stack string
50 | * @param $needle string
51 | * @param $ofs int
52 | **/
53 | function stripos($stack,$needle,$ofs=0) {
54 | return $this->strpos($stack,$needle,$ofs,TRUE);
55 | }
56 |
57 | /**
58 | * Find position of first occurrence of a string
59 | * @return int|FALSE
60 | * @param $stack string
61 | * @param $needle string
62 | * @param $ofs int
63 | * @param $case bool
64 | **/
65 | function strpos($stack,$needle,$ofs=0,$case=FALSE) {
66 | return preg_match('/^(.{'.$ofs.'}.*?)'.
67 | preg_quote($needle,'/').'/us'.($case?'i':''),$stack,$match)?
68 | $this->strlen($match[1]):FALSE;
69 | }
70 |
71 | /**
72 | * Returns part of haystack string from the first occurrence of
73 | * needle to the end of haystack (case-insensitive)
74 | * @return string|FALSE
75 | * @param $stack string
76 | * @param $needle string
77 | * @param $before bool
78 | **/
79 | function stristr($stack,$needle,$before=FALSE) {
80 | return $this->strstr($stack,$needle,$before,TRUE);
81 | }
82 |
83 | /**
84 | * Returns part of haystack string from the first occurrence of
85 | * needle to the end of haystack
86 | * @return string|FALSE
87 | * @param $stack string
88 | * @param $needle string
89 | * @param $before bool
90 | * @param $case bool
91 | **/
92 | function strstr($stack,$needle,$before=FALSE,$case=FALSE) {
93 | if (!$needle)
94 | return FALSE;
95 | preg_match('/^(.*?)'.preg_quote($needle,'/').'/us'.($case?'i':''),
96 | $stack,$match);
97 | return isset($match[1])?
98 | ($before?
99 | $match[1]:
100 | $this->substr($stack,$this->strlen($match[1]))):
101 | FALSE;
102 | }
103 |
104 | /**
105 | * Return part of a string
106 | * @return string|FALSE
107 | * @param $str string
108 | * @param $start int
109 | * @param $len int
110 | **/
111 | function substr($str,$start,$len=0) {
112 | if ($start<0)
113 | $start=$this->strlen($str)+$start;
114 | if (!$len)
115 | $len=$this->strlen($str)-$start;
116 | return preg_match('/^.{'.$start.'}(.{0,'.$len.'})/us',$str,$match)?
117 | $match[1]:FALSE;
118 | }
119 |
120 | /**
121 | * Count the number of substring occurrences
122 | * @return int
123 | * @param $stack string
124 | * @param $needle string
125 | **/
126 | function substr_count($stack,$needle) {
127 | preg_match_all('/'.preg_quote($needle,'/').'/us',$stack,
128 | $matches,PREG_SET_ORDER);
129 | return count($matches);
130 | }
131 |
132 | /**
133 | * Strip whitespaces from the beginning of a string
134 | * @return string
135 | * @param $str string
136 | **/
137 | function ltrim($str) {
138 | return preg_replace('/^[\pZ\pC]+/u','',$str);
139 | }
140 |
141 | /**
142 | * Strip whitespaces from the end of a string
143 | * @return string
144 | * @param $str string
145 | **/
146 | function rtrim($str) {
147 | return preg_replace('/[\pZ\pC]+$/u','',$str);
148 | }
149 |
150 | /**
151 | * Strip whitespaces from the beginning and end of a string
152 | * @return string
153 | * @param $str string
154 | **/
155 | function trim($str) {
156 | return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u','',$str);
157 | }
158 |
159 | /**
160 | * Return UTF-8 byte order mark
161 | * @return string
162 | **/
163 | function bom() {
164 | return chr(0xef).chr(0xbb).chr(0xbf);
165 | }
166 |
167 | /**
168 | * Convert code points to Unicode symbols
169 | * @return string
170 | * @param $str string
171 | **/
172 | function translate($str) {
173 | return html_entity_decode(
174 | preg_replace('/\\\\u([[:xdigit:]]+)/i','\1;',$str));
175 | }
176 |
177 | /**
178 | * Translate emoji tokens to Unicode font-supported symbols
179 | * @return string
180 | * @param $str string
181 | **/
182 | function emojify($str) {
183 | $map=[
184 | ':('=>'\u2639', // frown
185 | ':)'=>'\u263a', // smile
186 | '<3'=>'\u2665', // heart
187 | ':D'=>'\u1f603', // grin
188 | 'XD'=>'\u1f606', // laugh
189 | ';)'=>'\u1f609', // wink
190 | ':P'=>'\u1f60b', // tongue
191 | ':,'=>'\u1f60f', // think
192 | ':/'=>'\u1f623', // skeptic
193 | '8O'=>'\u1f632', // oops
194 | ]+Base::instance()->EMOJI;
195 | return $this->translate(str_replace(array_keys($map),
196 | array_values($map),$str));
197 | }
198 |
199 | }
200 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/pingback.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web;
24 |
25 | //! Pingback 1.0 protocol (client and server) implementation
26 | class Pingback extends \Prefab {
27 |
28 | protected
29 | //! Transaction history
30 | $log;
31 |
32 | /**
33 | * Return TRUE if URL points to a pingback-enabled resource
34 | * @return bool
35 | * @param $url
36 | **/
37 | protected function enabled($url) {
38 | $web=\Web::instance();
39 | $req=$web->request($url);
40 | $found=FALSE;
41 | if ($req['body']) {
42 | // Look for pingback header
43 | foreach ($req['headers'] as $header)
44 | if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) {
45 | $found=$href[1];
46 | break;
47 | }
48 | if (!$found &&
49 | // Scan page for pingback link tag
50 | preg_match('//i',$req['body'],$parts) &&
51 | preg_match('/rel\h*=\h*"pingback"/i',$parts[1]) &&
52 | preg_match('/href\h*=\h*"\h*(.+?)\h*"/i',$parts[1],$href))
53 | $found=$href[1];
54 | }
55 | return $found;
56 | }
57 |
58 | /**
59 | * Load local page contents, parse HTML anchor tags, find permalinks,
60 | * and send XML-RPC calls to corresponding pingback servers
61 | * @return NULL
62 | * @param $source string
63 | **/
64 | function inspect($source) {
65 | $fw=\Base::instance();
66 | $web=\Web::instance();
67 | $parts=parse_url($source);
68 | if (empty($parts['scheme']) || empty($parts['host']) ||
69 | $parts['host']==$fw->HOST) {
70 | $req=$web->request($source);
71 | $doc=new \DOMDocument('1.0',$fw->ENCODING);
72 | $doc->strictErrorChecking=FALSE;
73 | $doc->recover=TRUE;
74 | if (@$doc->loadhtml($req['body'])) {
75 | // Parse anchor tags
76 | $links=$doc->getelementsbytagname('a');
77 | foreach ($links as $link) {
78 | $permalink=$link->getattribute('href');
79 | // Find pingback-enabled resources
80 | if ($permalink && $found=$this->enabled($permalink)) {
81 | $req=$web->request($found,
82 | [
83 | 'method'=>'POST',
84 | 'header'=>'Content-Type: application/xml',
85 | 'content'=>xmlrpc_encode_request(
86 | 'pingback.ping',
87 | [$source,$permalink],
88 | ['encoding'=>$fw->ENCODING]
89 | )
90 | ]
91 | );
92 | if ($req['body'])
93 | $this->log.=date('r').' '.
94 | $permalink.' [permalink:'.$found.']'.PHP_EOL.
95 | $req['body'].PHP_EOL;
96 | }
97 | }
98 | }
99 | unset($doc);
100 | }
101 | }
102 |
103 | /**
104 | * Receive ping, check if local page is pingback-enabled, verify
105 | * source contents, and return XML-RPC response
106 | * @return string
107 | * @param $func callback
108 | * @param $path string
109 | **/
110 | function listen($func,$path=NULL) {
111 | $fw=\Base::instance();
112 | if (PHP_SAPI!='cli') {
113 | header('X-Powered-By: '.$fw->PACKAGE);
114 | header('Content-Type: application/xml; '.
115 | 'charset='.$charset=$fw->ENCODING);
116 | }
117 | if (!$path)
118 | $path=$fw->BASE;
119 | $web=\Web::instance();
120 | $args=xmlrpc_decode_request($fw->BODY,$method,$charset);
121 | $options=['encoding'=>$charset];
122 | if ($method=='pingback.ping' && isset($args[0],$args[1])) {
123 | list($source,$permalink)=$args;
124 | $doc=new \DOMDocument('1.0',$fw->ENCODING);
125 | // Check local page if pingback-enabled
126 | $parts=parse_url($permalink);
127 | if ((empty($parts['scheme']) ||
128 | $parts['host']==$fw->HOST) &&
129 | preg_match('/^'.preg_quote($path,'/').'/'.
130 | ($fw->CASELESS?'i':''),$parts['path']) &&
131 | $this->enabled($permalink)) {
132 | // Check source
133 | $parts=parse_url($source);
134 | if ((empty($parts['scheme']) ||
135 | $parts['host']==$fw->HOST) &&
136 | ($req=$web->request($source)) &&
137 | $doc->loadhtml($req['body'])) {
138 | $links=$doc->getelementsbytagname('a');
139 | foreach ($links as $link) {
140 | if ($link->getattribute('href')==$permalink) {
141 | call_user_func_array($func,[$source,$req['body']]);
142 | // Success
143 | die(xmlrpc_encode_request(NULL,$source,$options));
144 | }
145 | }
146 | // No link to local page
147 | die(xmlrpc_encode_request(NULL,0x11,$options));
148 | }
149 | // Source failure
150 | die(xmlrpc_encode_request(NULL,0x10,$options));
151 | }
152 | // Doesn't exist (or not pingback-enabled)
153 | die(xmlrpc_encode_request(NULL,0x21,$options));
154 | }
155 | // Access denied
156 | die(xmlrpc_encode_request(NULL,0x31,$options));
157 | }
158 |
159 | /**
160 | * Return transaction history
161 | * @return string
162 | **/
163 | function log() {
164 | return $this->log;
165 | }
166 |
167 | /**
168 | * Instantiate class
169 | * @return object
170 | **/
171 | function __construct() {
172 | // Suppress errors caused by invalid HTML structures
173 | libxml_use_internal_errors(TRUE);
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/vendor/fatfree/session.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Cache-based session handler
24 | class Session extends Magic {
25 |
26 | protected
27 | //! Session ID
28 | $sid,
29 | //! Anti-CSRF token
30 | $_csrf,
31 | //! User agent
32 | $_agent,
33 | //! IP,
34 | $_ip,
35 | //! Suspect callback
36 | $onsuspect,
37 | //! Cache instance
38 | $_cache,
39 | //! Session meta data
40 | $_data=[];
41 |
42 | /**
43 | * Open session
44 | * @return TRUE
45 | * @param $path string
46 | * @param $name string
47 | **/
48 | function open(string $path, string $name): bool
49 | {
50 | return TRUE;
51 | }
52 |
53 | /**
54 | * Close session
55 | * @return TRUE
56 | **/
57 | function close(): bool
58 | {
59 | $this->sid=NULL;
60 | $this->_data=[];
61 | return TRUE;
62 | }
63 |
64 | /**
65 | * Return session data in serialized format
66 | * @return false|string
67 | * @param $id string
68 | **/
69 | #[ReturnTypeWillChange]
70 | function read(string $id)
71 | {
72 | $this->sid=$id;
73 | if (!$data=$this->_cache->get($id.'.@'))
74 | return '';
75 | $this->_data = $data;
76 | if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
77 | $fw=Base::instance();
78 | if (!isset($this->onsuspect) ||
79 | $fw->call($this->onsuspect,[$this,$id])===FALSE) {
80 | //NB: `session_destroy` can't be called at that stage (`session_start` not completed)
81 | $this->destroy($id);
82 | $this->close();
83 | unset($fw->{'COOKIE.'.session_name()});
84 | $fw->error(403);
85 | }
86 | }
87 | return $data['data'];
88 | }
89 |
90 | /**
91 | * Write session data
92 | */
93 | function write(string $id, string $data): bool
94 | {
95 | $fw=Base::instance();
96 | $jar=$fw->JAR;
97 | $this->_cache->set($id.'.@',
98 | [
99 | 'data'=>$data,
100 | 'ip'=>$this->_ip,
101 | 'agent'=>$this->_agent,
102 | 'stamp'=>time()
103 | ],
104 | $jar['expire']
105 | );
106 | return TRUE;
107 | }
108 |
109 | /**
110 | * Destroy session
111 | */
112 | function destroy(string $id): bool
113 | {
114 | $this->_cache->clear($id.'.@');
115 | return TRUE;
116 | }
117 |
118 | /**
119 | * Garbage collector
120 | **/
121 | #[ReturnTypeWillChange]
122 | function gc(int $max_lifetime)
123 | {
124 | $this->_cache->reset('.@',$max_lifetime);
125 | return TRUE;
126 | }
127 |
128 | /**
129 | * Return session id (if session has started)
130 | * @return string|NULL
131 | **/
132 | function sid() {
133 | return $this->sid;
134 | }
135 |
136 | /**
137 | * Return anti-CSRF token
138 | * @return string
139 | **/
140 | function csrf() {
141 | return $this->_csrf;
142 | }
143 |
144 | /**
145 | * Return IP address
146 | * @return string
147 | **/
148 | function ip() {
149 | return $this->_ip;
150 | }
151 |
152 | /**
153 | * Return Unix timestamp
154 | * @return string|FALSE
155 | **/
156 | function stamp() {
157 | if (!$this->sid)
158 | session_start();
159 | return $this->_cache->exists($this->sid.'.@',$data)?
160 | $data['stamp']:FALSE;
161 | }
162 |
163 | /**
164 | * Return HTTP user agent
165 | * @return string
166 | **/
167 | function agent() {
168 | return $this->_agent;
169 | }
170 |
171 | /**
172 | * Instantiate class
173 | * @param $onsuspect callback
174 | * @param $key string
175 | **/
176 | function __construct($onsuspect=NULL,$key=NULL,$cache=null) {
177 | $this->onsuspect=$onsuspect;
178 | $this->_cache=$cache?:Cache::instance();
179 | if (version_compare(PHP_VERSION, '8.4.0')>=0) {
180 | // TODO: remove this when php7 support is dropped
181 | session_set_save_handler(new SessionAdapter($this));
182 | } else {
183 | session_set_save_handler(
184 | [$this,'open'],
185 | [$this,'close'],
186 | [$this,'read'],
187 | [$this,'write'],
188 | [$this,'destroy'],
189 | [$this,'gc']
190 | );
191 | }
192 | register_shutdown_function('session_commit');
193 | $fw=\Base::instance();
194 | $headers=$fw->HEADERS;
195 | $this->_csrf=$fw->hash($fw->SEED.
196 | extension_loaded('openssl')?
197 | implode(unpack('L',openssl_random_pseudo_bytes(4))):
198 | mt_rand()
199 | );
200 | if ($key)
201 | $fw->$key=$this->_csrf;
202 | $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
203 | $this->_ip=$fw->IP;
204 | }
205 |
206 | /**
207 | * check latest meta data existence
208 | * @param string $key
209 | * @return bool
210 | */
211 | function exists($key) {
212 | return isset($this->_data[$key]);
213 | }
214 |
215 | /**
216 | * get meta data from latest session
217 | * @param string $key
218 | * @return mixed
219 | */
220 | function &get($key) {
221 | return $this->_data[$key];
222 | }
223 |
224 | function set($key,$val) {
225 | trigger_error('Unable to set data on previous session');
226 | }
227 |
228 | function clear($key) {
229 | trigger_error('Unable to clear data on previous session');
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/vendor/fatfree/basket.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Session-based pseudo-mapper
24 | class Basket extends Magic {
25 |
26 | //@{ Error messages
27 | const
28 | E_Field='Undefined field %s';
29 | //@}
30 |
31 | protected
32 | //! Session key
33 | $key,
34 | //! Current item identifier
35 | $id,
36 | //! Current item contents
37 | $item=[];
38 |
39 | /**
40 | * Return TRUE if field is defined
41 | * @return bool
42 | * @param $key string
43 | **/
44 | function exists($key) {
45 | return array_key_exists($key,$this->item);
46 | }
47 |
48 | /**
49 | * Assign value to field
50 | * @return scalar|FALSE
51 | * @param $key string
52 | * @param $val scalar
53 | **/
54 | function set($key,$val) {
55 | return ($key=='_id')?FALSE:($this->item[$key]=$val);
56 | }
57 |
58 | /**
59 | * Retrieve value of field
60 | * @return scalar|FALSE
61 | * @param $key string
62 | **/
63 | function &get($key) {
64 | if ($key=='_id')
65 | return $this->id;
66 | if (array_key_exists($key,$this->item))
67 | return $this->item[$key];
68 | user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
69 | return FALSE;
70 | }
71 |
72 | /**
73 | * Delete field
74 | * @return NULL
75 | * @param $key string
76 | **/
77 | function clear($key) {
78 | unset($this->item[$key]);
79 | }
80 |
81 | /**
82 | * Return items that match key/value pair;
83 | * If no key/value pair specified, return all items
84 | * @return array
85 | * @param $key string
86 | * @param $val mixed
87 | **/
88 | function find($key=NULL,$val=NULL) {
89 | $out=[];
90 | if (isset($_SESSION[$this->key])) {
91 | foreach ($_SESSION[$this->key] as $id=>$item)
92 | if (!isset($key) ||
93 | array_key_exists($key,$item) && $item[$key]==$val ||
94 | $key=='_id' && $id==$val) {
95 | $obj=clone($this);
96 | $obj->id=$id;
97 | $obj->item=$item;
98 | $out[]=$obj;
99 | }
100 | }
101 | return $out;
102 | }
103 |
104 | /**
105 | * Return first item that matches key/value pair
106 | * @return object|FALSE
107 | * @param $key string
108 | * @param $val mixed
109 | **/
110 | function findone($key,$val) {
111 | return ($data=$this->find($key,$val))?$data[0]:FALSE;
112 | }
113 |
114 | /**
115 | * Map current item to matching key/value pair
116 | * @return array
117 | * @param $key string
118 | * @param $val mixed
119 | **/
120 | function load($key,$val) {
121 | if ($found=$this->find($key,$val)) {
122 | $this->id=$found[0]->id;
123 | return $this->item=$found[0]->item;
124 | }
125 | $this->reset();
126 | return [];
127 | }
128 |
129 | /**
130 | * Return TRUE if current item is empty/undefined
131 | * @return bool
132 | **/
133 | function dry() {
134 | return !$this->item;
135 | }
136 |
137 | /**
138 | * Return number of items in basket
139 | * @return int
140 | **/
141 | function count() {
142 | return isset($_SESSION[$this->key])?count($_SESSION[$this->key]):0;
143 | }
144 |
145 | /**
146 | * Save current item
147 | * @return array
148 | **/
149 | function save() {
150 | if (!$this->id)
151 | $this->id=uniqid('',TRUE);
152 | $_SESSION[$this->key][$this->id]=$this->item;
153 | return $this->item;
154 | }
155 |
156 | /**
157 | * Erase item matching key/value pair
158 | * @return bool
159 | * @param $key string
160 | * @param $val mixed
161 | **/
162 | function erase($key,$val) {
163 | $found=$this->find($key,$val);
164 | if ($found && $id=$found[0]->id) {
165 | unset($_SESSION[$this->key][$id]);
166 | if ($id==$this->id)
167 | $this->reset();
168 | return TRUE;
169 | }
170 | return FALSE;
171 | }
172 |
173 | /**
174 | * Reset cursor
175 | * @return NULL
176 | **/
177 | function reset() {
178 | $this->id=NULL;
179 | $this->item=[];
180 | }
181 |
182 | /**
183 | * Empty basket
184 | * @return NULL
185 | **/
186 | function drop() {
187 | unset($_SESSION[$this->key]);
188 | }
189 |
190 | /**
191 | * Hydrate item using hive array variable
192 | * @return NULL
193 | * @param $var array|string
194 | **/
195 | function copyfrom($var) {
196 | if (is_string($var))
197 | $var=\Base::instance()->$var;
198 | foreach ($var as $key=>$val)
199 | $this->set($key,$val);
200 | }
201 |
202 | /**
203 | * Populate hive array variable with item contents
204 | * @return NULL
205 | * @param $key string
206 | **/
207 | function copyto($key) {
208 | $var=&\Base::instance()->ref($key);
209 | foreach ($this->item as $key=>$field)
210 | $var[$key]=$field;
211 | }
212 |
213 | /**
214 | * Check out basket contents
215 | * @return array
216 | **/
217 | function checkout() {
218 | if (isset($_SESSION[$this->key])) {
219 | $out=$_SESSION[$this->key];
220 | unset($_SESSION[$this->key]);
221 | return $out;
222 | }
223 | return [];
224 | }
225 |
226 | /**
227 | * Instantiate class
228 | * @return void
229 | * @param $key string
230 | **/
231 | function __construct($key='basket') {
232 | $this->key=$key;
233 | if (session_status()!=PHP_SESSION_ACTIVE)
234 | session_start();
235 | Base::instance()->sync('SESSION');
236 | $this->reset();
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
117 |
--------------------------------------------------------------------------------
/vendor/fatfree/audit.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Data validator
24 | class Audit extends Prefab {
25 |
26 | //@{ User agents
27 | const
28 | UA_Mobile='android|blackberry|phone|ipod|palm|windows\s+ce',
29 | UA_Desktop='bsd|linux|os\s+[x9]|solaris|windows',
30 | UA_Bot='bot|crawl|slurp|spider';
31 | //@}
32 |
33 | /**
34 | * Return TRUE if string is a valid URL
35 | * @return bool
36 | * @param $str string
37 | **/
38 | function url($str) {
39 | return is_string(filter_var($str,FILTER_VALIDATE_URL))
40 | && !preg_match('/^(javascript|php):\/\/.*$/i', $str);
41 | }
42 |
43 | /**
44 | * Return TRUE if string is a valid e-mail address;
45 | * Check DNS MX records if specified
46 | * @return bool
47 | * @param $str string
48 | * @param $mx boolean
49 | **/
50 | function email($str,$mx=TRUE) {
51 | $hosts=[];
52 | return is_string(filter_var($str,FILTER_VALIDATE_EMAIL)) &&
53 | (!$mx || getmxrr(substr($str,strrpos($str,'@')+1),$hosts));
54 | }
55 |
56 | /**
57 | * Return TRUE if string is a valid IPV4 address
58 | * @return bool
59 | * @param $addr string
60 | **/
61 | function ipv4($addr) {
62 | return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV4);
63 | }
64 |
65 | /**
66 | * Return TRUE if string is a valid IPV6 address
67 | * @return bool
68 | * @param $addr string
69 | **/
70 | function ipv6($addr) {
71 | return (bool)filter_var($addr,FILTER_VALIDATE_IP,FILTER_FLAG_IPV6);
72 | }
73 |
74 | /**
75 | * Return TRUE if IP address is within private range
76 | * @return bool
77 | * @param $addr string
78 | **/
79 | function isprivate($addr) {
80 | return (bool)filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)
81 | && !(bool)filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE);
82 | }
83 |
84 | /**
85 | * Return TRUE if IP address is within reserved range
86 | * @return bool
87 | * @param $addr string
88 | **/
89 | function isreserved($addr) {
90 | return !(bool)filter_var($addr,FILTER_VALIDATE_IP,
91 | FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_RES_RANGE);
92 | }
93 |
94 | /**
95 | * Return TRUE if IP address is neither private nor reserved
96 | * @return bool
97 | * @param $addr string
98 | **/
99 | function ispublic($addr) {
100 | return (bool)filter_var($addr,FILTER_VALIDATE_IP,
101 | FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|
102 | FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE);
103 | }
104 |
105 | /**
106 | * Return TRUE if user agent is a desktop browser
107 | * @return bool
108 | * @param $agent string
109 | **/
110 | function isdesktop($agent=NULL) {
111 | if (!isset($agent))
112 | $agent=Base::instance()->AGENT;
113 | return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) &&
114 | !$this->ismobile($agent);
115 | }
116 |
117 | /**
118 | * Return TRUE if user agent is a mobile device
119 | * @return bool
120 | * @param $agent string
121 | **/
122 | function ismobile($agent=NULL) {
123 | if (!isset($agent))
124 | $agent=Base::instance()->AGENT;
125 | return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent);
126 | }
127 |
128 | /**
129 | * Return TRUE if user agent is a Web bot
130 | * @return bool
131 | * @param $agent string
132 | **/
133 | function isbot($agent=NULL) {
134 | if (!isset($agent))
135 | $agent=Base::instance()->AGENT;
136 | return (bool)preg_match('/('.self::UA_Bot.')/i',$agent);
137 | }
138 |
139 | /**
140 | * Return TRUE if specified ID has a valid (Luhn) Mod-10 check digit
141 | * @return bool
142 | * @param $id string
143 | **/
144 | function mod10($id) {
145 | if (!ctype_digit($id))
146 | return FALSE;
147 | $id=strrev($id);
148 | $sum=0;
149 | for ($i=0,$l=strlen($id);$i<$l;++$i)
150 | $sum+=$id[$i]+$i%2*(($id[$i]>4)*-4+$id[$i]%5);
151 | return !($sum%10);
152 | }
153 |
154 | /**
155 | * Return credit card type if number is valid
156 | * @return string|FALSE
157 | * @param $id string
158 | **/
159 | function card($id) {
160 | $id=preg_replace('/[^\d]/','',$id);
161 | if ($this->mod10($id)) {
162 | if (preg_match('/^3[47][0-9]{13}$/',$id))
163 | return 'American Express';
164 | if (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',$id))
165 | return 'Diners Club';
166 | if (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/',$id))
167 | return 'Discover';
168 | if (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$id))
169 | return 'JCB';
170 | if (preg_match('/^5[1-5][0-9]{14}$|'.
171 | '^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)\d{12}$/',$id))
172 | return 'MasterCard';
173 | if (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$id))
174 | return 'Visa';
175 | }
176 | return FALSE;
177 | }
178 |
179 | /**
180 | * Return entropy estimate of a password (NIST 800-63)
181 | * @return int|float
182 | * @param $str string
183 | **/
184 | function entropy($str) {
185 | $len=strlen($str);
186 | return 4*min($len,1)+($len>1?(2*(min($len,8)-1)):0)+
187 | ($len>8?(1.5*(min($len,20)-8)):0)+($len>20?($len-20):0)+
188 | 6*(bool)(preg_match(
189 | '/[A-Z].*?[0-9[:punct:]]|[0-9[:punct:]].*?[A-Z]/',$str));
190 | }
191 |
192 | /**
193 | * Return TRUE if string is a valid MAC address including EUI-64 format
194 | * @return bool
195 | * @param $addr string
196 | **/
197 | function mac($addr) {
198 | return (bool)filter_var($addr,FILTER_VALIDATE_MAC)
199 | || preg_match('/^([0-9a-f]{2}:){3}ff:fe(:[0-9a-f]{2}){3}$/i', $addr);
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/templates/metadata.html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Signature PDF - Éditer les métadonnées
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
'); ?>
19 |
20 |
21 |
22 |
23 |
" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf,image/png,image/jpeg" />
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
79 | true, 'pdf.js' => true]; include('components/common.html.php'); ?>
80 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
111 |
--------------------------------------------------------------------------------
/vendor/fatfree/db/sql/session.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace DB\SQL;
24 |
25 | use ReturnTypeWillChange;
26 | use SessionAdapter;
27 |
28 | //! SQL-managed session handler
29 | class Session extends Mapper {
30 |
31 | protected
32 | //! Session ID
33 | $sid,
34 | //! Anti-CSRF token
35 | $_csrf,
36 | //! User agent
37 | $_agent,
38 | //! IP,
39 | $_ip,
40 | //! Suspect callback
41 | $onsuspect;
42 |
43 | /**
44 | * Open session
45 | * @return TRUE
46 | * @param $path string
47 | * @param $name string
48 | **/
49 | function open(string $path, string $name): bool
50 | {
51 | return TRUE;
52 | }
53 |
54 | /**
55 | * Close session
56 | * @return TRUE
57 | **/
58 | function close(): bool
59 | {
60 | $this->reset();
61 | $this->sid=NULL;
62 | return TRUE;
63 | }
64 |
65 | /**
66 | * Return session data in serialized format
67 | * @return string|false
68 | * @param $id string
69 | **/
70 | #[ReturnTypeWillChange]
71 | function read(string $id)
72 | {
73 | $this->load(['session_id=?',$this->sid=$id]);
74 | if ($this->dry())
75 | return '';
76 | if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
77 | $fw=\Base::instance();
78 | if (!isset($this->onsuspect) ||
79 | $fw->call($this->onsuspect,[$this,$id])===FALSE) {
80 | //NB: `session_destroy` can't be called at that stage (`session_start` not completed)
81 | $this->destroy($id);
82 | $this->close();
83 | unset($fw->{'COOKIE.'.session_name()});
84 | $fw->error(403);
85 | }
86 | }
87 | return $this->get('data');
88 | }
89 |
90 | /**
91 | * Write session data
92 | * @return TRUE
93 | * @param $id string
94 | * @param $data string
95 | **/
96 | function write(string $id, string $data): bool
97 | {
98 | $this->set('session_id',$id);
99 | $this->set('data',$data);
100 | $this->set('ip',$this->_ip);
101 | $this->set('agent',$this->_agent);
102 | $this->set('stamp',time());
103 | $this->save();
104 | return TRUE;
105 | }
106 |
107 | /**
108 | * Destroy session
109 | * @return TRUE
110 | * @param $id string
111 | **/
112 | function destroy($id): bool
113 | {
114 | $this->erase(['session_id=?',$id]);
115 | return TRUE;
116 | }
117 |
118 | /**
119 | * Garbage collector
120 | **/
121 | #[ReturnTypeWillChange]
122 | function gc(int $max_lifetime): int
123 | {
124 | return (int) $this->erase(['stamp+?',$max_lifetime,time()]);
125 | }
126 |
127 | /**
128 | * Return session id (if session has started)
129 | * @return string|NULL
130 | **/
131 | function sid() {
132 | return $this->sid;
133 | }
134 |
135 | /**
136 | * Return anti-CSRF token
137 | * @return string
138 | **/
139 | function csrf() {
140 | return $this->_csrf;
141 | }
142 |
143 | /**
144 | * Return IP address
145 | * @return string
146 | **/
147 | function ip() {
148 | return $this->_ip;
149 | }
150 |
151 | /**
152 | * Return Unix timestamp
153 | * @return string|FALSE
154 | **/
155 | function stamp() {
156 | if (!$this->sid)
157 | session_start();
158 | return $this->dry()?FALSE:$this->get('stamp');
159 | }
160 |
161 | /**
162 | * Return HTTP user agent
163 | * @return string
164 | **/
165 | function agent() {
166 | return $this->_agent;
167 | }
168 |
169 | /**
170 | * Instantiate class
171 | * @param $db \DB\SQL
172 | * @param $table string
173 | * @param $force bool
174 | * @param $onsuspect callback
175 | * @param $key string
176 | * @param $type string, column type for data field
177 | **/
178 | function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL,$type='TEXT') {
179 | if ($force) {
180 | $eol="\n";
181 | $tab="\t";
182 | $sqlsrv=preg_match('/mssql|sqlsrv|sybase/',$db->driver());
183 | $db->exec(
184 | ($sqlsrv?
185 | ('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '.
186 | 'name='.$db->quote($table).' AND xtype=\'U\') '.
187 | 'CREATE TABLE dbo.'):
188 | ('CREATE TABLE IF NOT EXISTS '.
189 | ((($name=$db->name())&&$db->driver()!='pgsql')?
190 | ($db->quotekey($name,FALSE).'.'):''))).
191 | $db->quotekey($table,FALSE).' ('.$eol.
192 | ($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:'').
193 | $tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol.
194 | $tab.$db->quotekey('data').' '.$type.','.$eol.
195 | $tab.$db->quotekey('ip').' VARCHAR(45),'.$eol.
196 | $tab.$db->quotekey('agent').' VARCHAR(300),'.$eol.
197 | $tab.$db->quotekey('stamp').' INTEGER,'.$eol.
198 | $tab.'PRIMARY KEY ('.$db->quotekey($sqlsrv?'id':'session_id').')'.$eol.
199 | ($sqlsrv?',CONSTRAINT [UK_session_id] UNIQUE(session_id)':'').
200 | ');'
201 | );
202 | }
203 | parent::__construct($db,$table);
204 | $this->onsuspect=$onsuspect;
205 | if (version_compare(PHP_VERSION, '8.4.0')>=0) {
206 | // TODO: remove this when php7 support is dropped
207 | session_set_save_handler(new SessionAdapter($this));
208 | } else {
209 | session_set_save_handler(
210 | [$this,'open'],
211 | [$this,'close'],
212 | [$this,'read'],
213 | [$this,'write'],
214 | [$this,'destroy'],
215 | [$this,'gc']
216 | );
217 | }
218 | register_shutdown_function('session_commit');
219 | $fw=\Base::instance();
220 | $headers=$fw->HEADERS;
221 | $this->_csrf=$fw->hash($fw->SEED.
222 | extension_loaded('openssl')?
223 | implode(unpack('L',openssl_random_pseudo_bytes(4))):
224 | mt_rand()
225 | );
226 | if ($key)
227 | $fw->$key=$this->_csrf;
228 | $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
229 | if (strlen($this->_agent) > 300) {
230 | $this->_agent = substr($this->_agent, 0, 300);
231 | }
232 | $this->_ip=$fw->IP;
233 | }
234 |
235 | }
236 |
--------------------------------------------------------------------------------
/public/logo-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
123 |
--------------------------------------------------------------------------------
/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## [Debian/Ubuntu](#debian-ubuntu)
4 |
5 | Dependencies:
6 |
7 | - php >= 5.6
8 | - librsvg2-bin (rsvg-convert)
9 | - pdftk
10 | - imagemagick
11 | - potrace
12 | - ghostcript
13 | - gpg
14 |
15 | Installing dependencies:
16 | ```
17 | sudo apt-get install php librsvg2-bin pdftk imagemagick potrace ghostscript locales gpg
18 | ```
19 |
20 | Getting the source code:
21 |
22 | ```
23 | git clone https://github.com/24eme/signaturepdf.git
24 | ```
25 |
26 | To run it:
27 |
28 | ```
29 | php -S localhost:8000 -t public
30 | ```
31 |
32 | ### PHP Configuration
33 |
34 | ```
35 | upload_max_filesize = 24M # Maximum size of the PDF file to sign
36 | post_max_size = 24M # Maximum size of the PDF file to sign
37 | max_file_uploads = 201 # Maximum number of pages in the PDF, here 200 pages + the original PDF
38 | ```
39 |
40 | ### Apache Configuration
41 |
42 | ```
43 | DocumentRoot /path/to/signaturepdf/public
44 |
45 | Require all granted
46 | FallbackResource /index.php
47 | php_value max_file_uploads 201
48 | php_value upload_max_filesize 24M
49 | php_value post_max_size 24M
50 |
51 | ```
52 | ### Troubleshooting
53 |
54 | #### The translation is not done
55 |
56 | The language remains in English in the interface.
57 |
58 | Check that your locales are properly installed:
59 |
60 | ```
61 | sudo apt-get install locales
62 | sudo dpkg-reconfigure locales
63 | ```
64 |
65 | Then if you use apache, you have to restart it:
66 |
67 | ```
68 | sudo service apache2 restart
69 | ```
70 |
71 | ## [Deploy with Docker](#docker)
72 |
73 | ### Running a container
74 |
75 | ```bash
76 | docker run -d --name=signaturepdf -p 8080:80 xgaia/signaturepdf
77 | ```
78 |
79 | [localhost:8080](http://localhost:8080)
80 |
81 | ### Configuration
82 |
83 | The following variables can be used to configure the deployment:
84 |
85 | | Variable | description | exemple | defaut |
86 | |------------------------|-----------------------------------------------------------------------|----------------------------------|-------------|
87 | | `SERVERNAME` | Deployment URL | `pdf.24eme.fr` | localhost |
88 | | `UPLOAD_MAX_FILESIZE` | Maximum size of the PDF file to sign | 48M | 24M |
89 | | `POST_MAX_SIZE` | Maximum size of the PDF file to sign | 48M | 24M |
90 | | `MAX_FILE_UPLOADS` | Maximum number of pages in the PDF, here 200 pages + the original PDF | 401 | 201 |
91 | | `PDF_STORAGE_PATH` | Path where uploaded PDF files can be stored | /data | /data |
92 | | `DISABLE_ORGANIZATION` | Disable the Organize route | true | false |
93 | | `PDF_DEMO_LINK` | Show, hide, or change the demo PDF link | false, `link` or `relative path` | true |
94 | | `DEFAULT_LANGUAGE` | Default language for the application | en_US.UTF-8 | fr_FR.UTF-8 |
95 | | `PDF_STORAGE_ENCRYPTION` | Activate PDF storage encryption option (GPG needs to be installed) | true | false |
96 |
97 | ```bash
98 | docker run -d --name=signaturepdf -p 8080:80 -e SERVERNAME=pdf.example.org -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 -e PDF_STORAGE_PATH=/data signaturepdf
99 | ```
100 |
101 | ### Building the image from source
102 |
103 | You can also build the Docker image from source if necessary:
104 |
105 | ```bash
106 | git clone https://github.com/24eme/signaturepdf.git
107 | cd signaturepdf
108 | docker build -t signaturepdf .
109 | docker run -d --name=signaturepdf -p 8080:80 signaturepdf
110 | ```
111 |
112 | ## [Alpine](#alpine)
113 |
114 | Here is a script to install the solution on Linux Alpine (tested with version 3.15).
115 | Remember to edit the "domain" variable at the beginning of the script to match the URL it will be called with.
116 |
117 | The main components are:
118 |
119 | - php 8 + php-fpm
120 | - Nginx
121 | - pdftk ("manual" installation requiring openjdk8)
122 | - imagick
123 | - potrace
124 | - librsvg
125 | - ghostscript
126 | - gpg
127 |
128 | What the script does:
129 |
130 | - Installs dependencies
131 | - Configures php and php-fpm
132 | - Configures Nginx
133 | - Configures the config.ini
134 | - Clones the repo
135 |
136 | ```
137 | #!/bin/sh
138 |
139 | domain='sign.example.com'
140 |
141 | apk update
142 | apk add bash nginx git php8 php8-fpm php8-session php8-gd php8-fileinfo openjdk8 imagemagick potrace librsvg locales gpg ghostcript
143 |
144 | cd /tmp
145 | wget https://gitlab.com/pdftk-java/pdftk/-/jobs/924565145/artifacts/raw/build/libs/pdftk-all.jar
146 | mv pdftk-all.jar pdftk.jar
147 |
148 | cat <>pdftk
149 | #!/usr/bin/env bash
150 | /usr/bin/java -jar "\$0.jar" "\$@"
151 | EOF
152 |
153 | chmod 775 pdftk*
154 | mv pdftk* /usr/bin
155 |
156 | sed -i 's/user = nobody/user = nginx/g' /etc/php8/php-fpm.d/www.conf
157 | sed -i 's/;listen.owner = nginx/listen.owner = nginx/g' /etc/php8/php-fpm.d/www.conf
158 |
159 | sed -i 's/post_max_size = 8M/post_max_size = 50M/g' /etc/php8/php.ini
160 | sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 50M/g' /etc/php8/php.ini
161 | sed -i 's/max_file_uploads = 20 /max_file_uploads = 300/g' /etc/php8/php.ini
162 |
163 | service php-fpm8 restart
164 |
165 | cd /var/www
166 | git clone https://github.com/24eme/signaturepdf.git
167 |
168 | cat <>/etc/nginx/http.d/signaturepdf.conf
169 | server {
170 |
171 | listen 80 default_server;
172 | listen [::]:80 default_server;
173 |
174 | server_name ${domain};
175 |
176 | client_max_body_size 0;
177 |
178 | root /var/www/signaturepdf/public/;
179 |
180 | index index.php index.html;
181 |
182 | location / {
183 | # URLs to attempt, including pretty ones.
184 | try_files \$uri \$uri/ /index.php?\$query_string;
185 | }
186 |
187 | location ~ [^/]\.php(/|$) {
188 | root /var/www/signaturepdf/public/;
189 |
190 | fastcgi_split_path_info ^(.+\.php)(/.+)$;
191 | fastcgi_index index.php;
192 | include fastcgi_params;
193 |
194 | fastcgi_buffer_size 128k;
195 | fastcgi_buffers 128 128k;
196 | fastcgi_param PATH_INFO \$fastcgi_path_info;
197 | fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
198 | fastcgi_pass 127.0.0.1:9000;
199 |
200 | }
201 |
202 | }
203 | EOF
204 |
205 | rm /etc/nginx/http.d/default.conf
206 | rm -R /var/www/localhost
207 |
208 | service nginx restart
209 |
210 | rc-update add nginx
211 | rc-update add php-fpm8
212 |
213 | mkdir /var/www/signaturepdf/tmp
214 | chown nginx /var/www/signaturepdf/tmp
215 |
216 | cat <>/var/www/signaturepdf/config/config.ini
217 | PDF_STORAGE_PATH=/var/www/signaturepdf/tmp
218 | EOF
219 | ```
220 |
221 | ## [Package](#package)
222 |
223 | You can also build a Debian (or Debian-like) package (`.deb`) by issuing the following command :
224 | ```
225 | make deb
226 | ```
227 |
228 | Make sure you have the `devscripts` package installed. The file will be in the newly created `target` directory.
229 | You can also customize some parameters in the [Makefile](./Makefile)
230 |
--------------------------------------------------------------------------------
/vendor/fatfree/auth.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | //! Authorization/authentication plug-in
24 | class Auth {
25 |
26 | //@{ Error messages
27 | const
28 | E_LDAP='LDAP connection failure',
29 | E_SMTP='SMTP connection failure';
30 | //@}
31 |
32 | protected
33 | //! Auth storage
34 | $storage,
35 | //! Mapper object
36 | $mapper,
37 | //! Storage options
38 | $args,
39 | //! Custom compare function
40 | $func;
41 |
42 | /**
43 | * Jig storage handler
44 | * @return bool
45 | * @param $id string
46 | * @param $pw string
47 | * @param $realm string
48 | **/
49 | protected function _jig($id,$pw,$realm) {
50 | $success = (bool)
51 | call_user_func_array(
52 | [$this->mapper,'load'],
53 | [
54 | array_merge(
55 | [
56 | '@'.$this->args['id'].'==?'.
57 | ($this->func?'':' AND @'.$this->args['pw'].'==?').
58 | (isset($this->args['realm'])?
59 | (' AND @'.$this->args['realm'].'==?'):''),
60 | $id
61 | ],
62 | ($this->func?[]:[$pw]),
63 | (isset($this->args['realm'])?[$realm]:[])
64 | )
65 | ]
66 | );
67 | if ($success && $this->func)
68 | $success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
69 | return $success;
70 | }
71 |
72 | /**
73 | * MongoDB storage handler
74 | * @return bool
75 | * @param $id string
76 | * @param $pw string
77 | * @param $realm string
78 | **/
79 | protected function _mongo($id,$pw,$realm) {
80 | $success = (bool)
81 | $this->mapper->load(
82 | [$this->args['id']=>$id]+
83 | ($this->func?[]:[$this->args['pw']=>$pw])+
84 | (isset($this->args['realm'])?
85 | [$this->args['realm']=>$realm]:[])
86 | );
87 | if ($success && $this->func)
88 | $success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
89 | return $success;
90 | }
91 |
92 | /**
93 | * SQL storage handler
94 | * @return bool
95 | * @param $id string
96 | * @param $pw string
97 | * @param $realm string
98 | **/
99 | protected function _sql($id,$pw,$realm) {
100 | $success = (bool)
101 | call_user_func_array(
102 | [$this->mapper,'load'],
103 | [
104 | array_merge(
105 | [
106 | $this->args['id'].'=?'.
107 | ($this->func?'':' AND '.$this->args['pw'].'=?').
108 | (isset($this->args['realm'])?
109 | (' AND '.$this->args['realm'].'=?'):''),
110 | $id
111 | ],
112 | ($this->func?[]:[$pw]),
113 | (isset($this->args['realm'])?[$realm]:[])
114 | )
115 | ]
116 | );
117 | if ($success && $this->func)
118 | $success = call_user_func($this->func,$pw,$this->mapper->get($this->args['pw']));
119 | return $success;
120 | }
121 |
122 | /**
123 | * LDAP storage handler
124 | * @return bool
125 | * @param $id string
126 | * @param $pw string
127 | **/
128 | protected function _ldap($id,$pw) {
129 | $port=(int)($this->args['port']?:389);
130 | $filter=$this->args['filter']=$this->args['filter']?:"uid=".$id;
131 | $this->args['attr']=$this->args['attr']?:["uid"];
132 | array_walk($this->args['attr'],
133 | function($attr)use(&$filter,$id) {
134 | $filter=str_ireplace($attr."=*",$attr."=".$id,$filter);});
135 | $dc=@ldap_connect($this->args['dc'],$port);
136 | if ($dc &&
137 | ldap_set_option($dc,LDAP_OPT_PROTOCOL_VERSION,3) &&
138 | ldap_set_option($dc,LDAP_OPT_REFERRALS,0) &&
139 | ldap_bind($dc,$this->args['rdn'],$this->args['pw']) &&
140 | ($result=ldap_search($dc,$this->args['base_dn'],
141 | $filter,$this->args['attr'])) &&
142 | ldap_count_entries($dc,$result) &&
143 | ($info=ldap_get_entries($dc,$result)) &&
144 | $info['count']==1 &&
145 | @ldap_bind($dc,$info[0]['dn'],$pw) &&
146 | @ldap_close($dc)) {
147 | return in_array($id,(array_map(function($value){return $value[0];},
148 | array_intersect_key($info[0],
149 | array_flip($this->args['attr'])))),TRUE);
150 | }
151 | user_error(self::E_LDAP,E_USER_ERROR);
152 | }
153 |
154 | /**
155 | * SMTP storage handler
156 | * @return bool
157 | * @param $id string
158 | * @param $pw string
159 | **/
160 | protected function _smtp($id,$pw) {
161 | $socket=@fsockopen(
162 | (strtolower($this->args['scheme'])=='ssl'?
163 | 'ssl://':'').$this->args['host'],
164 | $this->args['port']);
165 | $dialog=function($cmd=NULL) use($socket) {
166 | if (!is_null($cmd))
167 | fputs($socket,$cmd."\r\n");
168 | $reply='';
169 | while (!feof($socket) &&
170 | ($info=stream_get_meta_data($socket)) &&
171 | !$info['timed_out'] && $str=fgets($socket,4096)) {
172 | $reply.=$str;
173 | if (preg_match('/(?:^|\n)\d{3} .+\r\n/s',
174 | $reply))
175 | break;
176 | }
177 | return $reply;
178 | };
179 | if ($socket) {
180 | stream_set_blocking($socket,TRUE);
181 | $dialog();
182 | $fw=Base::instance();
183 | $dialog('EHLO '.$fw->HOST);
184 | if (strtolower($this->args['scheme'])=='tls') {
185 | $dialog('STARTTLS');
186 | stream_socket_enable_crypto(
187 | $socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
188 | $dialog('EHLO '.$fw->HOST);
189 | }
190 | // Authenticate
191 | $dialog('AUTH LOGIN');
192 | $dialog(base64_encode($id));
193 | $reply=$dialog(base64_encode($pw));
194 | $dialog('QUIT');
195 | fclose($socket);
196 | return (bool)preg_match('/^235 /',$reply);
197 | }
198 | user_error(self::E_SMTP,E_USER_ERROR);
199 | }
200 |
201 | /**
202 | * Login auth mechanism
203 | * @return bool
204 | * @param $id string
205 | * @param $pw string
206 | * @param $realm string
207 | **/
208 | function login($id,$pw,$realm=NULL) {
209 | return $this->{'_'.$this->storage}($id,$pw,$realm);
210 | }
211 |
212 | /**
213 | * HTTP basic auth mechanism
214 | * @return bool
215 | * @param $func callback
216 | **/
217 | function basic($func=NULL) {
218 | $fw=Base::instance();
219 | $realm=$fw->REALM;
220 | $hdr=NULL;
221 | if (isset($_SERVER['HTTP_AUTHORIZATION']))
222 | $hdr=$_SERVER['HTTP_AUTHORIZATION'];
223 | elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
224 | $hdr=$_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
225 | if (!empty($hdr))
226 | list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])=
227 | explode(':',base64_decode(substr($hdr,6)));
228 | if (isset($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) &&
229 | $this->login(
230 | $_SERVER['PHP_AUTH_USER'],
231 | $func?
232 | $fw->call($func,$_SERVER['PHP_AUTH_PW']):
233 | $_SERVER['PHP_AUTH_PW'],
234 | $realm
235 | ))
236 | return TRUE;
237 | if (PHP_SAPI!='cli')
238 | header('WWW-Authenticate: Basic realm="'.$realm.'"');
239 | $fw->status(401);
240 | return FALSE;
241 | }
242 |
243 | /**
244 | * Instantiate class
245 | * @return object
246 | * @param $storage string|object
247 | * @param $args array
248 | * @param $func callback
249 | **/
250 | function __construct($storage,?array $args=NULL,$func=NULL) {
251 | if (is_object($storage) && is_a($storage,'DB\Cursor')) {
252 | $this->storage=$storage->dbtype();
253 | $this->mapper=$storage;
254 | unset($ref);
255 | }
256 | else
257 | $this->storage=$storage;
258 | $this->args=$args;
259 | $this->func=$func;
260 | }
261 |
262 | }
263 |
--------------------------------------------------------------------------------
/vendor/fatfree/web/openid.php:
--------------------------------------------------------------------------------
1 | .
20 |
21 | */
22 |
23 | namespace Web;
24 |
25 | //! OpenID consumer
26 | class OpenID extends \Magic {
27 |
28 | protected
29 | //! OpenID provider endpoint URL
30 | $url,
31 | //! HTTP request parameters
32 | $args=[];
33 |
34 | /**
35 | * Determine OpenID provider
36 | * @return string|FALSE
37 | * @param $proxy string
38 | **/
39 | protected function discover($proxy) {
40 | // Normalize
41 | if (!preg_match('/https?:\/\//i',$this->args['endpoint']))
42 | $this->args['endpoint']='http://'.$this->args['endpoint'];
43 | $url=parse_url($this->args['endpoint']);
44 | // Remove fragment; reconnect parts
45 | $this->args['endpoint']=$url['scheme'].'://'.
46 | (isset($url['user'])?
47 | ($url['user'].
48 | (isset($url['pass'])?(':'.$url['pass']):'').'@'):'').
49 | strtolower($url['host']).(isset($url['path'])?$url['path']:'/').
50 | (isset($url['query'])?('?'.$url['query']):'');
51 | // HTML-based discovery of OpenID provider
52 | $req=\Web::instance()->
53 | request($this->args['endpoint'],['proxy'=>$proxy]);
54 | if (!$req)
55 | return FALSE;
56 | $type=array_values(preg_grep('/Content-Type:/',$req['headers']));
57 | if ($type &&
58 | preg_match('/application\/xrds\+xml|text\/xml/',$type[0]) &&
59 | ($sxml=simplexml_load_string($req['body'])) &&
60 | ($xrds=json_decode(json_encode($sxml),TRUE)) &&
61 | isset($xrds['XRD'])) {
62 | // XRDS document
63 | $svc=$xrds['XRD']['Service'];
64 | if (isset($svc[0]))
65 | $svc=$svc[0];
66 | $svc_type=is_array($svc['Type'])?$svc['Type']:array($svc['Type']);
67 | if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'.
68 | '(?:server|signon)/',$svc_type)) {
69 | $this->args['provider']=$svc['URI'];
70 | if (isset($svc['LocalID']))
71 | $this->args['localidentity']=$svc['LocalID'];
72 | elseif (isset($svc['CanonicalID']))
73 | $this->args['localidentity']=$svc['CanonicalID'];
74 | }
75 | $this->args['server']=$svc['URI'];
76 | if (isset($svc['Delegate']))
77 | $this->args['delegate']=$svc['Delegate'];
78 | }
79 | else {
80 | $len=strlen($req['body']);
81 | $ptr=0;
82 | // Parse document
83 | while ($ptr<$len)
84 | if (preg_match(
85 | '/^/is',
87 | substr($req['body'],$ptr),$parts)) {
88 | if ($parts[1] &&
89 | // Process attributes
90 | preg_match_all('/\b(rel|href)\h*=\h*'.
91 | '(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr,
92 | PREG_SET_ORDER)) {
93 | $node=[];
94 | foreach ($attr as $kv)
95 | $node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3];
96 | if (isset($node['rel']) &&
97 | preg_match('/openid2?\.(\w+)/',
98 | $node['rel'],$var) &&
99 | isset($node['href']))
100 | $this->args[$var[1]]=$node['href'];
101 |
102 | }
103 | $ptr+=strlen($parts[0]);
104 | }
105 | else
106 | ++$ptr;
107 | }
108 | // Get OpenID provider's endpoint URL
109 | if (isset($this->args['provider'])) {
110 | // OpenID 2.0
111 | $this->args['ns']='http://specs.openid.net/auth/2.0';
112 | if (isset($this->args['localidentity']))
113 | $this->args['identity']=$this->args['localidentity'];
114 | if (isset($this->args['trust_root']))
115 | $this->args['realm']=$this->args['trust_root'];
116 | }
117 | elseif (isset($this->args['server'])) {
118 | // OpenID 1.1
119 | $this->args['ns']='http://openid.net/signon/1.1';
120 | if (isset($this->args['delegate']))
121 | $this->args['identity']=$this->args['delegate'];
122 | }
123 | if (isset($this->args['provider'])) {
124 | // OpenID 2.0
125 | if (empty($this->args['claimed_id']))
126 | $this->args['claimed_id']=$this->args['identity'];
127 | return $this->args['provider'];
128 | }
129 | elseif (isset($this->args['server']))
130 | // OpenID 1.1
131 | return $this->args['server'];
132 | else
133 | return FALSE;
134 | }
135 |
136 | /**
137 | * Initiate OpenID authentication sequence; Return FALSE on failure
138 | * or redirect to OpenID provider URL
139 | * @return bool
140 | * @param $proxy string
141 | * @param $attr array
142 | * @param $reqd string|array
143 | **/
144 | function auth($proxy=NULL,$attr=[],?array $reqd=NULL) {
145 | $fw=\Base::instance();
146 | $root=$fw->SCHEME.'://'.$fw->HOST;
147 | if (empty($this->args['trust_root']))
148 | $this->args['trust_root']=$root.$fw->BASE.'/';
149 | if (empty($this->args['return_to']))
150 | $this->args['return_to']=$root.$_SERVER['REQUEST_URI'];
151 | $this->args['mode']='checkid_setup';
152 | if ($this->url=$this->discover($proxy)) {
153 | if ($attr) {
154 | $this->args['ns.ax']='http://openid.net/srv/ax/1.0';
155 | $this->args['ax.mode']='fetch_request';
156 | foreach ($attr as $key=>$val)
157 | $this->args['ax.type.'.$key]=$val;
158 | $this->args['ax.required']=is_string($reqd)?
159 | $reqd:implode(',',$reqd);
160 | }
161 | $var=[];
162 | foreach ($this->args as $key=>$val)
163 | $var['openid.'.$key]=$val;
164 | $fw->reroute($this->url.'?'.http_build_query($var));
165 | }
166 | return FALSE;
167 | }
168 |
169 | /**
170 | * Return TRUE if OpenID verification was successful
171 | * @return bool
172 | * @param $proxy string
173 | **/
174 | function verified($proxy=NULL) {
175 | preg_match_all('/(?<=^|&)openid\.([^=]+)=([^&]+)/',
176 | $_SERVER['QUERY_STRING'],$matches,PREG_SET_ORDER);
177 | foreach ($matches as $match)
178 | $this->args[$match[1]]=urldecode($match[2]);
179 | if (isset($this->args['mode']) &&
180 | $this->args['mode']!='error' &&
181 | $this->url=$this->discover($proxy)) {
182 | $this->args['mode']='check_authentication';
183 | $var=[];
184 | foreach ($this->args as $key=>$val)
185 | $var['openid.'.$key]=$val;
186 | $req=\Web::instance()->request(
187 | $this->url,
188 | [
189 | 'method'=>'POST',
190 | 'content'=>http_build_query($var),
191 | 'proxy'=>$proxy
192 | ]
193 | );
194 | return (bool)preg_match('/is_valid:true/i',$req['body']);
195 | }
196 | return FALSE;
197 | }
198 |
199 | /**
200 | * Return OpenID response fields
201 | * @return array
202 | **/
203 | function response() {
204 | return $this->args;
205 | }
206 |
207 | /**
208 | * Return TRUE if OpenID request parameter exists
209 | * @return bool
210 | * @param $key string
211 | **/
212 | function exists($key) {
213 | return isset($this->args[$key]);
214 | }
215 |
216 | /**
217 | * Bind value to OpenID request parameter
218 | * @return string
219 | * @param $key string
220 | * @param $val string
221 | **/
222 | function set($key,$val) {
223 | return $this->args[$key]=$val;
224 | }
225 |
226 | /**
227 | * Return value of OpenID request parameter
228 | * @return mixed
229 | * @param $key string
230 | **/
231 | function &get($key) {
232 | if (isset($this->args[$key]))
233 | $val=&$this->args[$key];
234 | else
235 | $val=NULL;
236 | return $val;
237 | }
238 |
239 | /**
240 | * Remove OpenID request parameter
241 | * @return NULL
242 | * @param $key
243 | **/
244 | function clear($key) {
245 | unset($this->args[$key]);
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/lib/PDFSignature.class.php:
--------------------------------------------------------------------------------
1 | symmetricKey = $symmetricKey;
15 | $this->pathHash = $pathHash;
16 | $this->hash = basename($this->pathHash);
17 | $this->gpg = new GPGCryptography($symmetricKey, $pathHash);
18 | $this->lockFile = $this->pathHash.'/.lock';
19 | }
20 |
21 | public function createShare($originalFile, $originFileBaseName, $duration) {
22 | mkdir($this->pathHash);
23 | $expireFile = $this->pathHash.".expire";
24 | file_put_contents($expireFile, $duration);
25 | touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
26 | rename($originalFile, $this->pathHash.'/original.pdf');
27 | file_put_contents($this->pathHash.'/filename.txt', $originFileBaseName);
28 | if($this->symmetricKey) {
29 | $this->gpg->encrypt();
30 | }
31 | }
32 |
33 | public function createAdminKey()
34 | {
35 | $link = $this->gpg->createSymmetricKey(20);
36 | file_put_contents($this->pathHash.'.admin', $link);
37 |
38 | return $link;
39 | }
40 |
41 | public function verifyEncryption() {
42 | if(!$this->isEncrypted()) {
43 |
44 | return true;
45 | }
46 |
47 | return file_exists($this->getDecryptFile($this->pathHash."/filename.txt"));
48 | }
49 |
50 | public function isEncrypted() {
51 | return $this->gpg->isEncrypted();
52 | }
53 |
54 | public function getDecryptFile($file) {
55 | if(!$this->isEncrypted()) {
56 |
57 | return $file;
58 | }
59 | $file = preg_replace("/\.gpg$/", "", $file);
60 | if(file_exists($file)) {
61 | return $file;
62 | }
63 |
64 | if(array_key_exists($file, $this->cacheDecryptFiles)) {
65 | return $this->cacheDecryptFiles[$file];
66 | }
67 | $decryptFile = $this->gpg->decryptFile($file);
68 | $this->toClean[] = $decryptFile;
69 | $this->cacheDecryptFiles[$file] = $decryptFile;
70 |
71 | return $decryptFile;
72 | }
73 |
74 | public function getPDF() {
75 | $this->compile();
76 | return $this->getDecryptFile($this->pathHash.'/final.pdf');
77 | }
78 |
79 | public function needToCompile() {
80 | $needToCompile = false;
81 | foreach($this->getLayers() as $layerFile) {
82 | if(!file_exists(str_replace('.svg.pdf', '.sign.pdf', $layerFile))) {
83 | $needToCompile = true;
84 | }
85 | }
86 | if(!$this->isEncrypted() && !file_exists($this->pathHash.'/final.pdf')) {
87 | $needToCompile = true;
88 | }
89 | if($this->isEncrypted() && !file_exists($this->pathHash.'/final.pdf.gpg')) {
90 | $needToCompile = true;
91 | }
92 |
93 | return $needToCompile;
94 | }
95 |
96 | protected function isCompileLock() {
97 | if(file_exists($this->lockFile) && filemtime($this->lockFile) > time() + 30) {
98 | unlink($this->lockFile);
99 | }
100 |
101 | return file_exists($this->lockFile);
102 | }
103 |
104 | protected function lockCompile() {
105 | touch($this->lockFile);
106 | }
107 |
108 | protected function unlockCompile() {
109 | unlink($this->lockFile);
110 | }
111 |
112 | public function compile() {
113 | if(!$this->needToCompile()) {
114 | return;
115 | }
116 |
117 | if($this->isCompileLock()) {
118 | return $this->compile();
119 | }
120 |
121 | $this->lockCompile();
122 |
123 | $layers = $this->getLayers($this->pathHash);
124 | $currentSignedFile = $this->pathHash.'/original.pdf';
125 | $signedFileToCopy = [];
126 | foreach($layers as $layerFile) {
127 | $signedFile = str_replace('.svg.pdf', '.sign.pdf', $layerFile);
128 | if(!file_exists($signedFile)) {
129 | $signedFile = preg_replace("/\.gpg$/", '', $signedFile);
130 | self::addSvgToPDF($this->getDecryptFile($currentSignedFile), $this->getDecryptFile($layerFile), $signedFile, false);
131 | }
132 | $currentSignedFile = $signedFile;
133 | }
134 | copy($this->getDecryptFile($currentSignedFile), $this->pathHash.'/final.pdf');
135 |
136 | if($this->isEncrypted()) {
137 | $this->gpg->encrypt();
138 | }
139 |
140 | $this->unlockCompile();
141 | }
142 |
143 | public function getPublicFilename() {
144 | $filename = $this->hash.'.pdf';
145 |
146 | $file = $this->getDecryptFile($this->pathHash."/filename.txt");
147 |
148 | if(file_exists($file)) {
149 | $filename = file_get_contents($file);
150 | }
151 |
152 | $filename = str_replace('.pdf', '_signe-'.count($this->getLayers()).'x.pdf', $filename);
153 |
154 | return $filename;
155 | }
156 |
157 | public function getLayers($pathHash = null) {
158 | if(is_null($pathHash)) {
159 | $pathHash = $this->pathHash;
160 | }
161 | if(!file_exists($pathHash)) {
162 | return [];
163 | }
164 | $files = scandir($pathHash);
165 | $layers = [];
166 | foreach($files as $file) {
167 | if(strpos($file, '.svg.pdf') !== false) {
168 | $layers[] = $pathHash.'/'.$file;
169 | }
170 | }
171 | return $layers;
172 | }
173 |
174 | public function addSignature(array $svgFiles) {
175 | $expireFile = $this->pathHash.".expire";
176 | touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
177 |
178 | do {
179 | if(isset($svgPDFFile)) { usleep(1); }
180 | $svgPDFFile = $this->pathHash."/".(new DateTime())->format('YmdHisu').'.svg.pdf';
181 | } while (file_exists($svgPDFFile));
182 |
183 | self::createPDFFromSvg($svgFiles, $svgPDFFile);
184 |
185 | if($this->isEncrypted()) {
186 | $this->gpg->encrypt();
187 | }
188 | $this->toClean = array_merge($this->toClean, $svgFiles);
189 | $this->compile();
190 | }
191 |
192 | public static function createPDFFromSvg(array $svgFiles, $outputPdfFile) {
193 | shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $outputPdfFile, implode(" ", $svgFiles)));
194 | }
195 |
196 | public static function addSvgToPDF($pdfOrigin, $pdfSvg, $pdfOutput, $digitalSignature = true) {
197 | shell_exec(sprintf("pdftk %s multistamp %s output %s", $pdfOrigin, $pdfSvg, $pdfOutput));
198 | if (NSSCryptography::getInstance()->isEnabled() && $digitalSignature) {
199 | try {
200 | NSSCryptography::getInstance()->addSignature($pdfOutput, 'Signed with SignaturePDF');
201 | } catch(Exception $e) {
202 | error_log($e->getMessage());
203 | }
204 | }
205 | }
206 |
207 | public static function flatten($pdf)
208 | {
209 | // check version of imagick
210 | $command = (null === shell_exec("command -v magick")) ? 'convert' : 'magick';
211 |
212 | shell_exec(sprintf(
213 | '%s -density 200 -units PixelsPerInch %s_signe.pdf -compress zip %s_signe.pdf'
214 | , $command, escapeshellarg($pdf), escapeshellarg($pdf)));
215 | }
216 |
217 | public function clean() {
218 | foreach($this->toClean as $path) {
219 | if(strpos($path, $this->pathHash) !== false) {
220 | continue;
221 | }
222 | GPGCryptography::hardUnlink($path);
223 | }
224 | }
225 |
226 | public static function isrsvgConvertInstalled() {
227 | $output = null;
228 | $returnCode = null;
229 |
230 | exec('rsvg-convert --version', $output, $returnCode);
231 |
232 | if (!$output) {
233 | return array(false);
234 | }
235 | return $output;
236 | }
237 |
238 | public static function ispdftkInstalled() {
239 | $output = null;
240 | $returnCode = null;
241 |
242 | exec('pdftk --version', $output, $returnCode);
243 |
244 | if (!$output) {
245 | return array(false);
246 | }
247 | return $output;
248 | }
249 |
250 | }
251 |
--------------------------------------------------------------------------------
/templates/index.html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Signature PDF - Signer et manipuler des PDF en ligne librement
6 |
7 |
8 |
9 |
14 |
15 |
16 |

17 |
Signature PDF
18 |
19 |
20 |
21 |
22 |
23 | ">
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
67 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
102 |
103 |
104 |
--------------------------------------------------------------------------------