├── .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 |
15 | 16 |
17 |
18 |

'); ?>

19 |

20 |
21 | 22 | " class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" onchange="handleFileChange()" /> 23 |

24 | 25 |
26 | 27 | 30 | 35 |
36 |
37 |
38 |
39 |
"> ">
40 |
">
41 | 42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 |
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+?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','&#x\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 | 16 | 18 | 36 | 54 | 66 | 71 | 75 | 81 | 85 | 86 | 104 | 116 | 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 |
34 |
35 |
36 |

37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 | 46 |
47 | " style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"> 48 |
49 | 50 |
51 |
52 |
53 |
54 |
55 | " style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/metadata"> 56 |
57 |
58 | 59 |

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 |
77 |
78 |
79 | true, 'pdf.js' => true]; include('components/common.html.php'); ?> 80 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 37 | 43 | 49 | 68 | 72 | 78 | 90 | 91 | 96 | 103 | 109 | 110 | 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+?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 | 16 | 18 | 36 | 54 | 66 | 71 | 77 | 81 | 87 | 91 | 92 | 110 | 122 | 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 |
60 |
61 | 62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 | 72 |
73 |
74 |
75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 102 | 103 | 104 | --------------------------------------------------------------------------------